feat(cue): enable cross-directory pipelines for subdirectory agents#845
Conversation
Allow Cue pipelines to span agents whose working directories are subdirectories of a common project root (e.g. project/ + project/Digest). Previously the pipeline editor rejected any save where agents had different projectRoot values, even parent/child relationships. Changes: - Add shared path utilities (computeCommonAncestorPath, isDescendantOrEqual) - Add findAncestorCueConfigRoot to walk parent dirs for cue.yaml discovery - Add ancestor config fallback in session init (only targeted subscriptions) - Relax pipeline save validation to allow common-ancestor roots - Refresh descendant sessions after pipeline save Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds ancestor Cue config discovery and adoption when a session's local config is missing, introduces shared path utilities for cross-directory pipelines, updates session state and runtime to track/use ancestor config roots (with subscription filtering), adjusts YAML watch root, and expands pipeline persistence validation to allow common-ancestor pipelines. Changes
Sequence Diagram(s)sequenceDiagram
participant SessionRuntime as SessionRuntimeService
participant Loader as CueYamlLoader
participant Repo as CueConfigRepository
participant FS as FileSystem/Watcher
SessionRuntime->>Loader: loadCueConfigDetailed(session.projectRoot)
alt result === missing
SessionRuntime->>Loader: findAncestorCueConfigRoot(session.projectRoot)
Loader-->>SessionRuntime: ancestorRoot | null
alt ancestorRoot found
SessionRuntime->>Loader: loadCueConfigDetailed(ancestorRoot)
Loader->>Repo: resolveCueConfigPath(ancestorRoot)
Repo-->>Loader: configPath
Loader-->>SessionRuntime: ancestorResult
SessionRuntime->>SessionRuntime: filter ancestorResult.config.subscriptions for session
SessionRuntime-->>FS: watchCueYaml(ancestorRoot, ...)
else no ancestor
SessionRuntime-->>FS: watchCueYaml(session.projectRoot, ...)
end
else config loaded
SessionRuntime-->>FS: watchCueYaml(session.projectRoot, ...)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR enables Cue pipelines to span agents in subdirectories of a common project root by adding ancestor config discovery, a new common-ancestor path utility, and relaxed pipeline-editor save validation. The main process and renderer changes are largely well-constructed, but there is one P1 correctness issue.
Confidence Score: 4/5Not safe to merge until the One P1 logic bug in src/renderer/hooks/cue/usePipelinePersistence.ts — specifically the Important Files Changed
Sequence DiagramsequenceDiagram
participant UI as Pipeline Editor
participant PH as usePipelinePersistence
participant CueService as cueService (IPC)
participant RT as cue-session-runtime-service
participant YL as cue-yaml-loader
Note over UI,YL: Save — cross-directory pipeline
UI->>PH: handleSave()
PH->>PH: computeCommonAncestorPath([/project, /project/Digest]) → /project
PH->>CueService: writeYaml(/project, yaml)
PH->>CueService: refreshSession(subAgent, /project/Digest)
Note over PH: lastWrittenRootsRef = {/project} ✓
Note over UI,YL: Sub-agent session init (ancestor fallback)
RT->>YL: loadCueConfigDetailed(/project/Digest) → missing
RT->>YL: findAncestorCueConfigRoot(/project/Digest) → /project
RT->>YL: loadCueConfigDetailed(/project) → ok
RT->>RT: filter targeted subscriptions only
RT->>RT: watchCueYaml(/project, refresh subAgent)
Note over UI,YL: Discard then Save again (BUG)
UI->>PH: handleDiscard()
PH->>PH: re-derive roots from agent projectRoots
Note over PH: lastWrittenRootsRef = {/project, /project/Digest} ✗
UI->>PH: handleSave()
PH->>PH: currentRoots = {/project}
PH->>CueService: writeYaml(/project/Digest, emptyYaml) ← creates stray file
Note over RT: Next init: /project/Digest has own cue.yaml → no ancestor fallback
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/__tests__/shared/cue-path-utils.test.ts (1)
2-2: Unused import.The
pathmodule is imported but not used in any test assertions.🧹 Remove unused import
import { describe, it, expect } from 'vitest'; -import * as path from 'path'; import { computeCommonAncestorPath, isDescendantOrEqual } from '../../shared/cue-path-utils';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/__tests__/shared/cue-path-utils.test.ts` at line 2, The test file imports the Node 'path' module but never uses it; in src/__tests__/shared/cue-path-utils.test.ts remove the unused import statement "import * as path from 'path';" (or replace it with a used helper if intended) so there are no unused imports flagged — look for the top-level import line referencing path and delete it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/__tests__/shared/cue-path-utils.test.ts`:
- Line 2: The test file imports the Node 'path' module but never uses it; in
src/__tests__/shared/cue-path-utils.test.ts remove the unused import statement
"import * as path from 'path';" (or replace it with a used helper if intended)
so there are no unused imports flagged — look for the top-level import line
referencing path and delete it.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a690099c-c7c5-4307-956e-d4363eede9ba
📒 Files selected for processing (16)
src/__tests__/main/cue/cue-completion-chains.test.tssrc/__tests__/main/cue/cue-concurrency.test.tssrc/__tests__/main/cue/cue-engine.test.tssrc/__tests__/main/cue/cue-ipc-handlers.test.tssrc/__tests__/main/cue/cue-multi-hop-chains.test.tssrc/__tests__/main/cue/cue-session-lifecycle.test.tssrc/__tests__/main/cue/cue-sleep-prevention.test.tssrc/__tests__/main/cue/cue-sleep-wake.test.tssrc/__tests__/main/cue/cue-startup.test.tssrc/__tests__/main/cue/cue-yaml-loader.test.tssrc/__tests__/shared/cue-path-utils.test.tssrc/main/cue/cue-session-runtime-service.tssrc/main/cue/cue-session-state.tssrc/main/cue/cue-yaml-loader.tssrc/renderer/hooks/cue/usePipelinePersistence.tssrc/shared/cue-path-utils.ts
- Remove redundant `|| commonRoot === null` guard in usePipelinePersistence (allDescendants is already false when commonRoot is null) - Remove unused `path` import in cue-path-utils.test.ts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Maestro#847) Fixes RunMaestro#847: a Cue Scenario deleted from the dropdown and saved reappears the next time the user opens the Cue view. Root cause: PR RunMaestro#845 added cross-directory pipeline support. When a pipeline's agents span subdirectories (e.g. /project/a and /project/Digest), handleSave resolves their common ancestor (/project) and writes ONE YAML at /project/.maestro/cue.yaml. But the load path in usePipelineLayout was seeding lastWrittenRootsRef with EACH agent's individual projectRoot — so the common ancestor was missing from the set. That set becomes `previousRoots` in handleSave's clear loop (usePipelinePersistence:254). On delete+save: - currentRoots = {} (the deleted pipeline had no survivors at its root) - previousRoots = {/project/a, /project/Digest} — missing /project - The loop only writes empty YAML at the two subdirectory paths; the real YAML at /project is never touched. On reopen, getGraphData() still reports the pipeline from /project/.maestro/cue.yaml, so the "deleted" pipeline reappears. The same mis-seeding had a secondary effect: on every delete+save, the loop would also write empty cue.yaml files at the subdirectory roots that were never write targets, creating stray config files. Fix: Extract the write-root resolution into a shared pure helper, `resolvePipelineWriteRoot(pipeline, sessionsById, sessionsByName)`, mirroring handleSave's happy-path partitioning: - Single shared root across all agents → that root. - Multiple roots sharing a common ancestor where every agent root is a descendant → the common ancestor. - Otherwise (no agents, any unresolvable, unrelated trees) → null. Use `resolvePipelinesWriteRoots` in usePipelineLayout's load seeding in place of the per-agent-root loop. The persisted `savedLayout.writtenRoots` union is preserved as the authoritative source when agents have since been renamed or deleted. Scope notes: - handleSave's inline partitioning is left unchanged — it carries error-reporting branches the helper intentionally does not replicate. A doc comment on the helper calls out the invariant. - No change to on-disk YAML format, layout storage schema, or the ipc surface. Tests: 13 new cases in `pipelineRoots.test.ts` covering single-root, cross-directory (the RunMaestro#847 regression), unrelated-tree rejection, unresolvable pipelines (empty, unknown sessionId, partial missingRoot, undefined projectRoot), and the set-level behavior of `resolvePipelinesWriteRoots` (union, dedup, skipping unresolvable). Verified: - tsc clean, prettier clean, eslint clean - 13 new + 334 existing Cue pipeline editor tests pass (347 total) - 209 cue hook tests pass
…Maestro#847) Fixes RunMaestro#847: a Cue Scenario deleted from the dropdown and saved reappears the next time the user opens the Cue view. Root cause: PR RunMaestro#845 added cross-directory pipeline support. When a pipeline's agents span subdirectories (e.g. /project/a and /project/Digest), handleSave resolves their common ancestor (/project) and writes ONE YAML at /project/.maestro/cue.yaml. But the load path in usePipelineLayout was seeding lastWrittenRootsRef with EACH agent's individual projectRoot - so the common ancestor was missing from the set. That set becomes `previousRoots` in handleSave's clear loop (usePipelinePersistence.ts:254). On delete+save: - currentRoots = {} (the deleted pipeline had no survivors at its root) - previousRoots = {/project/a, /project/Digest} - missing /project - The loop only writes empty YAML at the two subdirectory paths; the real YAML at /project is never touched. On reopen, getGraphData() still reports the pipeline from /project/.maestro/cue.yaml, so the "deleted" pipeline reappears. The same mis-seeding had a secondary effect: on every delete+save, the loop would also write empty cue.yaml files at the subdirectory roots that were never write targets, creating stray config files. Fix: Extract the write-root resolution into a shared pure helper, `resolvePipelineWriteRoot(pipeline, sessionsById, sessionsByName)`, mirroring handleSave's happy-path partitioning: - Single shared root across all agents -> that root. - Multiple roots sharing a common ancestor where every agent root is a descendant -> the common ancestor. - Otherwise (no agents, any unresolvable, unrelated trees) -> null. Use `resolvePipelinesWriteRoots` in usePipelineLayout's load seeding in place of the per-agent-root loop. The persisted `savedLayout.writtenRoots` union is preserved as the authoritative source when agents have since been renamed or deleted. CodeRabbit review follow-up: aligned sessionsByName construction with handleSave's first-wins rule (handleSave: `if (!has(name)) set(...)`; this file previously used last-wins via `new Map(sessions.map(...))`). Without this change, a pipeline that falls back to sessionName lookup could resolve to a different projectRoot than handleSave actually writes to - defeating the parity invariant the helper exists to guarantee. Scope notes: - handleSave's inline partitioning is left unchanged - it carries error-reporting branches the helper intentionally does not replicate. A doc comment on the helper calls out the invariant. - No change to on-disk YAML format, layout storage schema, or the ipc surface. Tests: 13 new cases in `pipelineRoots.test.ts` covering single-root, cross-directory (the RunMaestro#847 regression), unrelated-tree rejection, unresolvable pipelines (empty, unknown sessionId, partial missingRoot, undefined projectRoot), and the set-level behavior of `resolvePipelinesWriteRoots` (union, dedup, skipping unresolvable). Verified: - tsc clean, prettier clean, eslint clean - 13 new + 334 existing Cue pipeline editor tests pass (347 total) - 209 cue hook tests pass
Summary
project/+project/Digest)cue.yamlwhen they don't have their ownDetails
Problem: When a user has a main agent at
chr1syy/and a sub-agent atchr1syy/Digest, they cannot create a Cue pipeline spanning both agents. The pipeline editor rejects the save with "agents span multiple project roots." Additionally, sub-agents never discover the parent'scue.yaml.Root cause: Two independent constraints blocked this:
projectRootcue.yamlSolution:
src/shared/cue-path-utils.ts— NewcomputeCommonAncestorPath()andisDescendantOrEqual()utilitiessrc/main/cue/cue-yaml-loader.ts— NewfindAncestorCueConfigRoot()walks up to 5 parent directoriessrc/main/cue/cue-session-runtime-service.ts— Ancestor fallback ininitSession(): loads only explicitly targeted subscriptions (viaagent_idorfan_out) to prevent duplicate triggerssrc/main/cue/cue-session-state.ts— AddedconfigRootto track ancestor-loaded configssrc/renderer/hooks/cue/usePipelinePersistence.ts— Relaxed multi-root validation, updated post-save refresh for descendant sessionsKey design decisions:
agent_id) are NOT inherited by child sessions — prevents duplicate trigger sourcesprojectRoot, not the config rootTest plan
computeCommonAncestorPathandisDescendantOrEqualfindAncestorCueConfigRoot(parent found, grandparent, depth limit, legacy path)findAncestorCueConfigRoot: () => null🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Tests