Skip to content

feat(cue): resolve shared-workspace conflicts via owner_agent_id#873

Merged
pedramamini merged 6 commits intoRunMaestro:rcfrom
scriptease:cue-same-workspace
Apr 25, 2026
Merged

feat(cue): resolve shared-workspace conflicts via owner_agent_id#873
pedramamini merged 6 commits intoRunMaestro:rcfrom
scriptease:cue-same-workspace

Conversation

@scriptease
Copy link
Copy Markdown

@scriptease scriptease commented Apr 19, 2026

Summary

Fixes #867 — when two or more agents share a projectRoot, every unowned Cue subscription (one without an explicit agent_id) currently fires once per agent. In the issue reporter's setup (an Obsidian vault registered as both an Opus and a Sonnet agent) the 18:00 summary ran twice, the task.pending research pipeline kicked off two parallel investigations, and their outputs clobbered each other in the same Research/<date>-<slug>/ folder.

This PR resolves the conflict at the engine level rather than asking YAML authors to duplicate configs or pick one agent and remember never to unattended-run the other. It introduces a single optional field, settings.owner_agent_id, and a deterministic fallback — and surfaces the resulting state in the Cue dashboard so users can see which agent took the config and why.

Why this is necessary

  • The engine already knew which agents shared a project root; it just didn't dedupe. Every shared workspace silently multiplied cost and caused output collisions.
  • agent_id on a subscription is for fan-out targeting within the owner, not for "which agent owns the config" — so it couldn't be used as a workaround.
  • Users should not have to duplicate YAML or pick one agent out-of-band. Making this a property of the config keeps the rule next to the rules it governs.

Resolution matrix

The ownership check is a pure utility (computeOwnershipWarning) — its return value is the single source of truth: a non-empty string means "non-owner, show the red ⚠️ in the dashboard and use this text as the tooltip"; undefined means "owner, no indicator".

owner_agent_id Agents in the projectRoot Outcome for this session Dashboard
Set, matches this session's id or name Owner — fires unowned subs No indicator
Set, matches another session in the root This session is a non-match sibling Skips unowned subs Red ⚠️ — tooltip points to the owner
Set, matches nobody in the root Every agent there is a non-match Skips unowned subs Red ⚠️ on every row — tooltip says the value matches no agent
Unset Only one agent in the root Owner — fires unowned subs No indicator
Unset >1 agent in the root, this is first in the session list Owner — fires unowned subs No indicator
Unset >1 agent in the root, this is not first Skips unowned subs Red ⚠️ — tooltip names the winner and points to owner_agent_id as the override
Any value Config was loaded from an ancestor dir Ancestor fallback already filters to targeted subs — gate is a no-op No indicator

Subscriptions that carry an explicit agent_id continue to fan out regardless — the gate applies only to unowned subs.

Files touched

  • src/shared/cue/contracts.tsCueSettings.owner_agent_id, CueSessionStatus.ownershipWarning
  • src/main/cue/config/cue-config-validator.ts — validates the field
  • src/main/cue/cue-session-state.ts — extracts the pure computeOwnershipWarning utility + stores warning on SessionState
  • src/main/cue/cue-session-runtime-service.ts — reduces the ownership gate in initSession to a call to the utility
  • src/renderer/components/CueModal/SessionsTable.tsx — red AlertTriangle + tooltip next to the session name
  • src/prompts/_maestro-cue.md, src/renderer/hooks/cue/useCueAiChat.ts — teach the YAML-editor chat agent about the new field
  • docs/maestro-cue-configuration.md — new "Sharing a workspace" section

Test plan

  • npm run lint — clean
  • npx vitest run src/__tests__/main/cue/ — 984 tests pass (10 new cases in cue-session-state.test.ts covering every row of the matrix above, including ancestor bypass and whitespace-only owner_agent_id)
  • Full validate:push suite via pre-push hook — 25,687 tests pass
  • Manual: register the same vault as two agents; verify non-winner row in the Cue dashboard shows the red ⚠️ with the expected tooltip; verify setting owner_agent_id to the other agent flips which row fires

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • settings.owner_agent_id to pin which agent will run unowned subscriptions in shared project directories.
  • Documentation

    • Added guidance on ownership resolution, accepted owner identifiers, behavior when owner is unset/ambiguous/missing, and recommended usage.
  • Improvements

    • Dashboard shows warning icons/tooltips when a session will not run unowned subscriptions.
    • Non‑owners no longer run unowned subscriptions; explicit per‑subscription agent ids still run as before.
    • Completion/dispatch now targets the resolved owner for shared unowned subscriptions.
    • Validation rejects empty/whitespace owner_agent_id values.
  • Tests

    • Added coverage for shared-workspace ownership scenarios and completion dispatch behavior.

…Maestro#867)

When multiple agents share a projectRoot, unowned subscriptions would fire
once per agent. Adds `settings.owner_agent_id` to pin unowned subs to a
single agent (by id or display name). When unset, the first agent in the
session list wins; non-winners — and every agent in a root whose
owner_agent_id matches nobody — are flagged in the Cue dashboard with a
red warning icon whose tooltip explains who took the config and how to
override. Ownership logic is a pure utility with unit coverage.

Closes RunMaestro#867

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a top-level settings.owner_agent_id to Cue config and implements ownership resolution for agents sharing a project root; non-owner sessions have unowned subscriptions suppressed, an ownershipWarning is persisted on session state, and the UI shows a warning icon when applicable.

Changes

Cohort / File(s) Summary
Docs & Prompts
docs/maestro-cue-configuration.md, src/prompts/_maestro-cue.md, src/renderer/hooks/cue/useCueAiChat.ts
Documented new settings.owner_agent_id, added shared-workspace author guidance, and updated AI prompt guidance to mention owner behavior.
Contracts
src/shared/cue/contracts.ts
Added owner_agent_id?: string to CueSettings and ownershipWarning?: string to CueSessionStatus.
Session State & Ownership Logic
src/main/cue/cue-session-state.ts, src/__tests__/main/cue/cue-session-state.test.ts
Added OwnershipCandidate type, computeOwnershipWarning(...), extended SessionState with ownershipWarning, export ownership-aware status field, and added tests covering explicit owner, implicit first-wins, whitespace handling, ambiguous names, and cross-root filtering.
Config Validation
src/main/cue/config/cue-config-validator.ts
Validate that settings.owner_agent_id, if present, is a non-empty trimmed string.
Runtime / Init Flow
src/main/cue/cue-session-runtime-service.ts, src/main/cue/cue-engine.ts
Compute ownership during session init, persist ownershipWarning on state, suppress unowned subscriptions for non-owners (per-subscription agent_id still honored), and propagate ownership info via getSessionConfigs.
Completion & Engine
src/main/cue/cue-completion-service.ts, src/__tests__/main/cue/cue-completion-chains.test.ts
Completion handling now consumes session views with ownershipWarning and skips unowned agent.completed subscriptions for non-owners; tests added to verify owner-only dispatch and predicate behavior.
UI
src/renderer/components/CueModal/SessionsTable.tsx
Render AlertTriangle icon next to session name when ownershipWarning exists, with tooltip/ARIA text.
Tests / Mocks
src/__tests__/main/cue/*
Updated/added tests and mocked config repo to exercise ownership gating across runtime and completion flows.

Sequence Diagram(s)

sequenceDiagram
    participant AgentA as Agent A\n(Session A)
    participant AgentB as Agent B\n(Session B)
    participant Config as Config\nLoader
    participant Ownership as Ownership\nResolver
    participant Runtime as Runtime\n(Subscription Engine)

    AgentA->>Config: load .maestro/cue.yaml (projectRoot)
    AgentB->>Config: load .maestro/cue.yaml (projectRoot)
    Config->>Config: validate settings.owner_agent_id (trim & non-empty)
    Config-->>AgentA: config
    Config-->>AgentB: config

    AgentA->>Ownership: computeOwnershipWarning(session=A, candidates=[A,B], config, configFromAncestor)
    AgentB->>Ownership: computeOwnershipWarning(session=B, candidates=[A,B], config, configFromAncestor)
    alt owner_agent_id matches A
        Ownership-->>AgentA: undefined
        Ownership-->>AgentB: "ownershipWarning: not owner — unowned subs suppressed"
    else owner_agent_id unset (first-wins)
        Ownership-->>AgentA: undefined
        Ownership-->>AgentB: "ownershipWarning: implicit non-owner — unowned subs suppressed"
    else owner_agent_id matches none
        Ownership-->>AgentA: "ownershipWarning: no matching owner (dead config)"
        Ownership-->>AgentB: "ownershipWarning: no matching owner (dead config)"
    end

    AgentA->>Runtime: initSession(owner=true) — wire subscriptions
    AgentB->>Runtime: initSession(owner=false) — skip unowned subs
    Runtime-->>AgentA: subscriptions wired/executed
    Runtime-->>AgentB: unowned subscriptions skipped, log warning
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • RunMaestro/Maestro#367: Shares the same multi-agent/shared-workspace ownership objective—this PR implements owner selection and suppression that address that issue.

Possibly related PRs

  • #750: Refactor of session/runtime surfaces that this change extends with ownership fields and logic.
  • #582: Modifies Cue engine/session config surfaces; related to the session-config view changes here.
  • #850: Changes the completion path; ownership-based filtering interacts with the completion adjustments made in that PR.

Poem

🐰 I found two burrows, both claiming the key,

One hops to duty, one naps under a tree.
Now one burrow owns the moonlit run,
The other sleeps till morning sun.
Single hop, no duplicate spree — hooray for tidy glee!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements core features from #867 but omits critical fixes raised in pedramamini's review: implicit ownership candidate scope (non-Cue sessions should be excluded), ambiguous owner resolution (handle display-name collisions), accessibility (warning text unreachable), and prompt wording (should say 'recommended', not 'required'). Address the four actionable review issues: scope ownership candidates to Cue-capable sessions, disambiguate multi-candidate scenarios, improve accessibility of ownership warnings, and revise prompt language to reflect the deterministic fallback behavior.
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(cue): resolve shared-workspace conflicts via owner_agent_id' directly describes the main feature addition: implementing owner_agent_id to resolve shared-workspace conflicts when multiple agents share the same project root.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing owner_agent_id functionality: contracts, validation, session state, runtime gating, UI indicators, documentation, and prompt updates. No unrelated refactoring or auxiliary changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/cue/cue-session-runtime-service.ts (1)

229-283: ⚠️ Potential issue | 🟠 Major

Use the ownership-filtered subscription set for downstream runtime state.

Lines 232 and 259 skip unowned subscriptions for non-owners, but Line 276 still calls hasTimeBasedSubscriptions(config, session.id) on the full config. A non-owner with only skipped unowned timers can still call onPreventSleep, even though it created no timer source.

🔧 Proposed fix
+		const runnableSubscriptions = isConfigOwner
+			? config.subscriptions
+			: config.subscriptions.filter((sub) => Boolean(sub.agent_id));
+		const runnableConfig = { ...config, subscriptions: runnableSubscriptions };
+
 		// Wire each subscription up to its trigger source. Each source owns its
 		// own timer/watcher/poller and emits events through the `emit` callback,
 		// which centralizes the dispatch path: passesFilter → state.lastTriggered
 		// → dispatchSubscription. Sources never touch session state directly.
-		for (const sub of config.subscriptions) {
+		for (const sub of runnableSubscriptions) {
 			if (sub.enabled === false) continue;
 			if (sub.agent_id && sub.agent_id !== session.id) continue;
-			if (!isConfigOwner && !sub.agent_id) continue;
 
 			const source: CueTriggerSource | null = createTriggerSource(sub.event, {
 				session,
 				subscription: sub,
@@
 		// app.startup subscriptions fire exactly once per process lifecycle, and
 		// only when the engine is starting because of a real system boot. Toggling
 		// Cue off/on or hot-reloading a YAML must NOT re-fire startup events.
 		if (opts.reason === 'system-boot') {
-			for (const sub of config.subscriptions) {
+			for (const sub of runnableSubscriptions) {
 				if (sub.enabled === false) continue;
 				if (sub.agent_id && sub.agent_id !== session.id) continue;
-				if (!isConfigOwner && !sub.agent_id) continue;
 				if (sub.event !== 'app.startup') continue;
@@
-		state.sleepPrevented = hasTimeBasedSubscriptions(config, session.id);
+		state.sleepPrevented = hasTimeBasedSubscriptions(runnableConfig, session.id);
 		if (state.sleepPrevented) {
 			deps.onPreventSleep?.(`cue:schedule:${session.id}`);
 		}
@@
 			'cue',
-			`[CUE] Initialized session "${session.name}" with ${countActiveSubscriptions(config.subscriptions, session.id, session.name)} active subscription(s)`
+			`[CUE] Initialized session "${session.name}" with ${countActiveSubscriptions(runnableSubscriptions, session.id, session.name)} active subscription(s)`
 		);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/cue/cue-session-runtime-service.ts` around lines 229 - 283, The
runtime currently checks ownership when creating trigger sources but later calls
hasTimeBasedSubscriptions(config, session.id) and
countActiveSubscriptions(config.subscriptions, ...) against the full
subscription list, causing non-owners to wrongly appear to prevent sleep;
compute a filtered subscription list (apply the same predicates: sub.enabled !==
false, sub.agent_id match or unset, and if !isConfigOwner skip sub.agent_id-less
entries) — use that filtered array in hasTimeBasedSubscriptions(...) and
countActiveSubscriptions(...) (and any downstream logic like deps.onPreventSleep
or the initialized-log) instead of the raw config.subscriptions so only
actually-instantiated subscriptions affect runtime state (refer to
createTriggerSource usage and state.sleepPrevented).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/cue/cue-session-state.ts`:
- Around line 130-143: The current logic uses owner_agent_id string matches
against id OR name which can allow multiple owners when names duplicate; change
the boolean ownerExists check to find the list of matchingSessions =
allSessions.filter(s => s.projectRoot === session.projectRoot && (s.id ===
explicitOwner || s.name === explicitOwner)), then handle three cases: if
matchingSessions.length === 0 return the existing "does not match any agent"
message; if matchingSessions.length > 1 return an ambiguity error indicating the
owner_agent_id matches multiple agents (so unowned subscriptions remain
disabled); if matchingSessions.length === 1 compute isOwner by checking
matchingSessions[0].id === session.id (i.e., only treat this session as owner
when the matched unique candidate's id equals this session.id) and preserve the
existing "targets ... run on that agent instead" message when not owner.

In `@src/prompts/_maestro-cue.md`:
- Line 86: Update the prompt in src/prompts/_maestro-cue.md to remove any
suggestion that unowned subscriptions can run on all agents; instead instruct
users to proactively set owner_agent_id in cue.yaml (mention using
{{MAESTRO_CLI_PATH}} list agents to find ids/names) and guide them to specify
explicit agent targets for multi-agent execution rather than implying a
shared-root "all-agent" option; ensure references to "unowned subs" are changed
to state they are pinned or disabled unless an owner_agent_id is provided.

In `@src/renderer/components/CueModal/SessionsTable.tsx`:
- Around line 75-87: The ownership warning is only placed in the title and the
aria-label is a generic "Cue ownership conflict", so screen readers don't get
the actual warning text; update the JSX where s.ownershipWarning is rendered
(the span wrapping AlertTriangle) to include the full warning in the accessible
name — e.g., set aria-label or aria-labelledby to include s.ownershipWarning (or
concatenate it with the existing label) so the AlertTriangle's accessible name
exposes the warning text to assistive tech rather than only a generic label.

In `@src/renderer/hooks/cue/useCueAiChat.ts`:
- Line 31: In useCueAiChat update the prompt text that currently marks
owner_agent_id as "required" so it instead indicates owner_agent_id is
optional/recommended and that the runtime will deterministically pick a fallback
when unset; find the prompt/message string referencing "owner_agent_id" inside
the useCueAiChat hook and change wording to something like "optional —
recommended to choose the owner; if unset, the runtime uses a deterministic
fallback" so it no longer claims the field is required.

---

Outside diff comments:
In `@src/main/cue/cue-session-runtime-service.ts`:
- Around line 229-283: The runtime currently checks ownership when creating
trigger sources but later calls hasTimeBasedSubscriptions(config, session.id)
and countActiveSubscriptions(config.subscriptions, ...) against the full
subscription list, causing non-owners to wrongly appear to prevent sleep;
compute a filtered subscription list (apply the same predicates: sub.enabled !==
false, sub.agent_id match or unset, and if !isConfigOwner skip sub.agent_id-less
entries) — use that filtered array in hasTimeBasedSubscriptions(...) and
countActiveSubscriptions(...) (and any downstream logic like deps.onPreventSleep
or the initialized-log) instead of the raw config.subscriptions so only
actually-instantiated subscriptions affect runtime state (refer to
createTriggerSource usage and state.sleepPrevented).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 74a48f20-81cd-44bd-b9c1-a369379849bb

📥 Commits

Reviewing files that changed from the base of the PR and between dbd1501 and 0199b9d.

📒 Files selected for processing (9)
  • docs/maestro-cue-configuration.md
  • src/__tests__/main/cue/cue-session-state.test.ts
  • src/main/cue/config/cue-config-validator.ts
  • src/main/cue/cue-session-runtime-service.ts
  • src/main/cue/cue-session-state.ts
  • src/prompts/_maestro-cue.md
  • src/renderer/components/CueModal/SessionsTable.tsx
  • src/renderer/hooks/cue/useCueAiChat.ts
  • src/shared/cue/contracts.ts

Comment thread src/main/cue/cue-session-state.ts Outdated
Comment thread src/prompts/_maestro-cue.md Outdated
Comment thread src/renderer/components/CueModal/SessionsTable.tsx
Comment thread src/renderer/hooks/cue/useCueAiChat.ts Outdated
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 19, 2026

Greptile Summary

This PR introduces settings.owner_agent_id in cue.yaml to deterministically pin unowned subscription execution to a single agent when multiple agents share a projectRoot, resolving the duplication bug from #867. The two previously flagged P1 issues — config-less agents winning the implicit first-in-list race (addressed by the resolveCueConfigPath pre-filter in the runtime service) and duplicate display names silently re-enabling dual execution (handled by the matches.length > 1 ambiguous path returning a warning for all candidates) — are now properly resolved. Remaining findings are P2 style concerns around sync filesystem I/O scaling and the unguarded caller contract on computeOwnershipWarning.

Confidence Score: 5/5

Safe to merge — all P1 issues from prior review rounds are resolved; only P2 style and performance suggestions remain.

Both previously raised P1 defects are addressed: config-less agent ownership race is fixed by filtering candidates through resolveCueConfigPath, and display-name collisions now produce an ambiguous warning that disables all matches rather than silently duplicating execution. The ownership resolution matrix is fully unit-tested with 10 new cases. Remaining findings are O(N²) sync filesystem I/O at startup and an unguarded caller contract — neither affects correctness or data integrity at typical session counts.

No files require special attention for merge; cue-session-runtime-service.ts and cue-session-state.ts have P2 improvement opportunities that can be addressed in a follow-up.

Important Files Changed

Filename Overview
src/main/cue/cue-session-state.ts Core logic: adds computeOwnershipWarning pure utility and ownershipWarning field on SessionState; correctly handles explicit owner, ambiguous name, no-match, implicit first-wins, and ancestor-bypass cases — but caller contract for pre-filtering candidates is unguarded.
src/main/cue/cue-session-runtime-service.ts Applies candidate pre-filtering via resolveCueConfigPath before calling computeOwnershipWarning; correctly gates trigger source wiring and app.startup dispatch; synchronous O(N²) filesystem calls on startup are a performance concern.
src/shared/cue/contracts.ts Adds owner_agent_id?: string to CueSettings and ownershipWarning?: string to CueSessionStatus; well-documented with JSDoc explaining semantics.
src/main/cue/config/cue-config-validator.ts Adds validation for owner_agent_id rejecting empty/whitespace strings; consistent with existing settings validation patterns.
src/renderer/components/CueModal/SessionsTable.tsx Renders AlertTriangle with role="img" and aria-label alongside the session name when ownershipWarning is set; tooltip text falls back to title attribute for pointer users.
src/tests/main/cue/cue-session-state.test.ts 10 new test cases covering every row of the ownership resolution matrix including ancestor bypass, whitespace-only owner, ambiguous name collision, and config-less caller contract — comprehensive and well-structured.
src/renderer/hooks/cue/useCueAiChat.ts Updates AI system prompt to describe owner_agent_id; no behavioral changes to hook logic.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[initSession called] --> B{configFromAncestor?}
    B -- yes --> C[return undefined\nancestor subs already targeted]
    B -- no --> D{owner_agent_id set\nand non-empty?}
    D -- yes --> E[filter sameRoot candidates\nby id OR name]
    E --> F{matches.length}
    F -- 0 --> G[return warning:\n'does not match any agent']
    F -- gt 1 --> H[return warning:\n'ambiguous — N agents matched']
    F -- 1 --> I{owner.id === session.id?}
    I -- yes --> J[return undefined\nthis session is owner]
    I -- no --> K[return warning:\n'owner_agent_id targets X']
    D -- no --> L{sameRoot.length gt 1?}
    L -- no --> M[return undefined\nsingle agent, no contest]
    L -- yes --> N{firstForRoot.id === session.id?}
    N -- yes --> O[return undefined\nthis session won first-in-list]
    N -- no --> P[return warning:\n'X was selected as owner\nset owner_agent_id to override']
Loading

Reviews (2): Last reviewed commit: "fix(cue): tighten owner-agent semantics ..." | Re-trigger Greptile

Comment thread src/main/cue/cue-session-state.ts Outdated
Comment thread src/main/cue/cue-session-state.ts Outdated
Comment thread src/renderer/components/CueModal/SessionsTable.tsx
…colliding candidates

Review follow-up on RunMaestro#867:
- P1: computeOwnershipWarning's candidate list is now pre-filtered by the
  caller to sessions whose projectRoot actually has a cue.yaml. A
  config-less agent used to be able to win the implicit first-in-list
  race, making itself the "owner" with nothing to run while the real
  Cue-configured agent was warned off — silently stopping automation
  for the whole projectRoot.
- P2: When owner_agent_id is set to a display name and multiple agents
  in the same projectRoot share that name, the first matching candidate
  now deterministically wins; later matches get a warning telling them
  to use the full id to disambiguate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pedramamini
Copy link
Copy Markdown
Collaborator

Thanks for tackling this @scriptease — the matrix in the PR description is a great framing, and consolidating the ownership gate behind a single pure utility is the right call. A few concerns surfaced by the automated reviews (coderabbit / greptile) that I think are worth addressing before merge:

1. Implicit ownership can be won by a non-Cue agent (src/main/cue/cue-session-state.ts:149)
allSessions is sourced from deps.getSessions(), which includes every application session — not just those that have loaded a cue.yaml. If a non-Cue agent happens to sort first for the projectRoot, firstForRoot picks it and the actually-configured Cue session gets a warning and has its unowned subs suppressed. The candidate list for the implicit fallback should be scoped to Cue-configured sessions (or at minimum to sessions that could own the config).

2. Explicit owner lookup can resolve to multiple sessions (src/main/cue/cue-session-state.ts:143)
ownerExists matches on id or name, and isOwner is then computed the same way against the current session. That means:

  • If two agents share a display name, both can satisfy isOwner and both fire unowned subs.
  • If owner_agent_id matches another agent's id but this session's name happens to equal that id string, this session wrongly claims ownership.

Recommend resolving a single owner candidate first (prefer id match, fall back to unique name match; treat ambiguous name matches as "unresolvable" with a warning), then comparing the current session against that resolved candidate's id.

3. Ownership warning not reachable by keyboard / touch / screen readers (src/renderer/components/CueModal/SessionsTable.tsx:78-87)
The actionable text is only in title, and aria-label is the generic "Cue ownership conflict". Since this is a red error indicator, users who most need the detail can't get at it. Either fold the warning text into the accessible name (aria-label={s.ownershipWarning} with role=\"img\") or render a real tooltip component.

4. Minor: prompt wording (src/renderer/hooks/cue/useCueAiChat.ts:31, src/prompts/_maestro-cue.md:86)
The chat prompt describes owner_agent_id as "required" when >1 agent shares a projectRoot, but the runtime has a deterministic first-wins fallback. "Recommended" / "used to override the default winner" is more accurate and keeps the AI editor from over-prescribing.

No merge conflicts — base is clean against rc. Once the two ownership-resolution bugs are sorted I'm happy to take another look.

…stro#873

- cue-session-state.ts: display-name collisions on owner_agent_id are now
  reported as ambiguous and disable unowned subscriptions for every
  matching agent. Previously the first match silently won, which could
  route unowned runs to the wrong model. Using a full id still resolves
  unambiguously.
- SessionsTable.tsx: the red warning icon's accessible name now contains
  the actual ownership-warning text, not just the generic label
  "Cue ownership conflict". The underlying Lucide icon is marked
  aria-hidden so the span's role=img + aria-label is the single
  accessible name.
- useCueAiChat.ts: the inline YAML-editor prompt no longer calls
  owner_agent_id "required" — it's optional-but-recommended, with the
  deterministic first-in-list fallback noted explicitly.
- _maestro-cue.md: rewrote the "Shared Workspaces" section to remove
  any wording implying unowned subs broadcast to every agent in a root
  (never true in this implementation) and to steer authors toward
  explicit per-subscription agent_id for multi-agent targeting.
- Tests: updated the display-name collision test to the new
  "ambiguous → disabled" semantics and added a case confirming that a
  full UUID disambiguates even when names collide.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/cue/cue-session-runtime-service.ts (1)

213-240: ⚠️ Potential issue | 🟠 Major

Apply ownership gating to agent.completed dispatches too.

This registers the full config for non-owner sessions and only skips trigger-source creation here. src/main/cue/cue-completion-service.ts:47-120 still iterates deps.getSessionConfigs() and dispatches agent.completed subscriptions without checking ownershipWarning, so unowned completion subscriptions can still run once per shared-root agent.

Please route completion dispatch through an ownership-aware state/config iterator, or skip state.ownershipWarning && !sub.agent_id there before dispatching.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/cue/cue-session-runtime-service.ts` around lines 213 - 240, The
completion dispatch path must respect ownership gating: update the code that
iterates session configs and dispatches agent.completed (currently in
cue-completion-service.ts where deps.getSessionConfigs() is used) to skip
subscriptions belonging to shared-root non-owner sessions by checking the
session state's ownershipWarning and subscription agent_id (e.g., skip when
state.ownershipWarning && !sub.agent_id), or refactor the iterator to use the
ownership-aware SessionState/config instance you register (the SessionState with
ownershipWarning in cue-session-runtime-service.ts) so only owner-permitted
subscriptions are dispatched; ensure the same ownership check used when creating
trigger sources (the loop over config.subscriptions that checks sub.agent_id and
isConfigOwner) is applied before emitting agent.completed.
🧹 Nitpick comments (1)
src/main/cue/cue-session-runtime-service.ts (1)

284-291: Use the ownership-filtered subscription set for sleep prevention and active counts.

For a non-owner, unowned time-based subscriptions are skipped at Lines 240 and 267, but hasTimeBasedSubscriptions(config, session.id) and countActiveSubscriptions(...) still see the full config. That can keep sleep prevention enabled and report active subscriptions for work this session will never run.

♻️ Suggested refactor
+		const ownedOrExplicitSubscriptions = config.subscriptions.filter(
+			(sub) => isConfigOwner || sub.agent_id
+		);
+
-		state.sleepPrevented = hasTimeBasedSubscriptions(config, session.id);
+		state.sleepPrevented = hasTimeBasedSubscriptions(
+			{ ...config, subscriptions: ownedOrExplicitSubscriptions },
+			session.id
+		);
 		if (state.sleepPrevented) {
 			deps.onPreventSleep?.(`cue:schedule:${session.id}`);
 		}
 
 		deps.onLog(
 			'cue',
-			`[CUE] Initialized session "${session.name}" with ${countActiveSubscriptions(config.subscriptions, session.id, session.name)} active subscription(s)`
+			`[CUE] Initialized session "${session.name}" with ${countActiveSubscriptions(ownedOrExplicitSubscriptions, session.id, session.name)} active subscription(s)`
 		);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/cue/cue-session-runtime-service.ts` around lines 284 - 291, The
sleep-prevention and active-subscription count are using the full config instead
of the ownership-filtered subscriptions; update the calls so they use the
already-created ownership-filtered subscription set (the same
filteredSubscriptions used earlier when skipping unowned time-based
subscriptions) instead of config.subscriptions: replace
hasTimeBasedSubscriptions(config, session.id) with
hasTimeBasedSubscriptions(filteredSubscriptions, session.id) (or equivalent) and
change countActiveSubscriptions(config.subscriptions, session.id, session.name)
to countActiveSubscriptions(filteredSubscriptions, session.id, session.name),
keeping state.sleepPrevented and deps.onPreventSleep logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/main/cue/cue-session-runtime-service.ts`:
- Around line 213-240: The completion dispatch path must respect ownership
gating: update the code that iterates session configs and dispatches
agent.completed (currently in cue-completion-service.ts where
deps.getSessionConfigs() is used) to skip subscriptions belonging to shared-root
non-owner sessions by checking the session state's ownershipWarning and
subscription agent_id (e.g., skip when state.ownershipWarning && !sub.agent_id),
or refactor the iterator to use the ownership-aware SessionState/config instance
you register (the SessionState with ownershipWarning in
cue-session-runtime-service.ts) so only owner-permitted subscriptions are
dispatched; ensure the same ownership check used when creating trigger sources
(the loop over config.subscriptions that checks sub.agent_id and isConfigOwner)
is applied before emitting agent.completed.

---

Nitpick comments:
In `@src/main/cue/cue-session-runtime-service.ts`:
- Around line 284-291: The sleep-prevention and active-subscription count are
using the full config instead of the ownership-filtered subscriptions; update
the calls so they use the already-created ownership-filtered subscription set
(the same filteredSubscriptions used earlier when skipping unowned time-based
subscriptions) instead of config.subscriptions: replace
hasTimeBasedSubscriptions(config, session.id) with
hasTimeBasedSubscriptions(filteredSubscriptions, session.id) (or equivalent) and
change countActiveSubscriptions(config.subscriptions, session.id, session.name)
to countActiveSubscriptions(filteredSubscriptions, session.id, session.name),
keeping state.sleepPrevented and deps.onPreventSleep logic unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6d000d05-51e6-4d5d-a1d2-e7f3fdc2c4ae

📥 Commits

Reviewing files that changed from the base of the PR and between 0199b9d and 72e475a.

📒 Files selected for processing (6)
  • src/__tests__/main/cue/cue-session-state.test.ts
  • src/main/cue/cue-session-runtime-service.ts
  • src/main/cue/cue-session-state.ts
  • src/prompts/_maestro-cue.md
  • src/renderer/components/CueModal/SessionsTable.tsx
  • src/renderer/hooks/cue/useCueAiChat.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/renderer/hooks/cue/useCueAiChat.ts
  • src/renderer/components/CueModal/SessionsTable.tsx
  • src/prompts/_maestro-cue.md
  • src/main/cue/cue-session-state.ts
  • src/tests/main/cue/cue-session-state.test.ts

@scriptease
Copy link
Copy Markdown
Author

@greptile plz review

Review follow-up on RunMaestro#873:
- cue-session-runtime-service.ts: the implicit first-in-list candidate
  list now also excludes toolType === 'terminal'. A terminal agent at
  a shared projectRoot could previously win implicit ownership and
  silently block the real Cue agent.
- cue-session-state.ts: when owner_agent_id is set, resolution prefers
  a unique id match first and only falls back to name matching when no
  id hit exists. A session whose display name happens to equal some
  other agent's id string can no longer misclaim ownership. Name-only
  matches remain protected against ambiguity.
- SessionsTable.tsx: the ownership warning span is now keyboard-focusable
  (tabIndex=0) and its aria-label is exactly the warning text so the
  accessible name carries the actionable detail — not the prior generic
  "Cue ownership conflict" label.
- Tests: updated ambiguity assertion to the refined "by display name"
  wording and added a case verifying that id match takes precedence
  even when another session's name equals that id string.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/cue/cue-session-runtime-service.ts (1)

239-293: ⚠️ Potential issue | 🟠 Major

Apply the ownership gate to derived state as well.

Line 242 skips non-owner unowned subscriptions for trigger wiring, but Line 286 still prevents sleep from the full config and Lines 293 / 382-386 still count the full config. A non-owner with only skipped unowned time subscriptions can still show active subscriptions and keep the machine awake without any trigger source running.

🐛 Proposed fix
+		const ownershipEligibleSubscriptions = isConfigOwner
+			? config.subscriptions
+			: config.subscriptions.filter((sub) => sub.agent_id);
+
 		for (const sub of config.subscriptions) {
+		for (const sub of ownershipEligibleSubscriptions) {
 			if (sub.enabled === false) continue;
 			if (sub.agent_id && sub.agent_id !== session.id) continue;
-			if (!isConfigOwner && !sub.agent_id) continue;
 
 			const source: CueTriggerSource | null = createTriggerSource(sub.event, {
 				session,
@@
 		if (opts.reason === 'system-boot') {
-			for (const sub of config.subscriptions) {
+			for (const sub of ownershipEligibleSubscriptions) {
 				if (sub.enabled === false) continue;
 				if (sub.agent_id && sub.agent_id !== session.id) continue;
-				if (!isConfigOwner && !sub.agent_id) continue;
 				if (sub.event !== 'app.startup') continue;
@@
-		state.sleepPrevented = hasTimeBasedSubscriptions(config, session.id);
+		state.sleepPrevented = hasTimeBasedSubscriptions(
+			{ ...config, subscriptions: ownershipEligibleSubscriptions },
+			session.id
+		);
@@
 			'cue',
-			`[CUE] Initialized session "${session.name}" with ${countActiveSubscriptions(config.subscriptions, session.id, session.name)} active subscription(s)`
+			`[CUE] Initialized session "${session.name}" with ${countActiveSubscriptions(ownershipEligibleSubscriptions, session.id, session.name)} active subscription(s)`
 		);

For refresh counts, apply the same ownership-aware view:

+			const countableSubscriptions = newState.ownershipWarning
+				? newState.config.subscriptions.filter((sub) => sub.agent_id)
+				: newState.config.subscriptions;
 			const activeCount = countActiveSubscriptions(
-				newState.config.subscriptions,
+				countableSubscriptions,
 				sessionId,
 				session.name
 			);

Also applies to: 382-386

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/cue/cue-session-runtime-service.ts` around lines 239 - 293, The
ownership gate used when wiring trigger sources (the isConfigOwner checks over
config.subscriptions) must also be applied to derived state and counts: ensure
hasTimeBasedSubscriptions and countActiveSubscriptions operate over the same
ownership-filtered view (only include subscriptions where sub.agent_id ===
session.id or isConfigOwner is true), and update the code paths that set
state.sleepPrevented, the final deps.onLog message, and the other occurrences
you noted (the block around lines 382-386) to use that filtered list (or add a
helper like getVisibleSubscriptions(session.id, isConfigOwner) and call
hasTimeBasedSubscriptions/getVisibleSubscriptions/countActiveSubscriptions with
it) so non-owners do not keep the machine awake or see counts for subscriptions
they were correctly excluded from when wiring triggers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/main/cue/cue-session-runtime-service.ts`:
- Around line 239-293: The ownership gate used when wiring trigger sources (the
isConfigOwner checks over config.subscriptions) must also be applied to derived
state and counts: ensure hasTimeBasedSubscriptions and countActiveSubscriptions
operate over the same ownership-filtered view (only include subscriptions where
sub.agent_id === session.id or isConfigOwner is true), and update the code paths
that set state.sleepPrevented, the final deps.onLog message, and the other
occurrences you noted (the block around lines 382-386) to use that filtered list
(or add a helper like getVisibleSubscriptions(session.id, isConfigOwner) and
call hasTimeBasedSubscriptions/getVisibleSubscriptions/countActiveSubscriptions
with it) so non-owners do not keep the machine awake or see counts for
subscriptions they were correctly excluded from when wiring triggers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 882d8734-a8fa-400b-8770-9c1ef95e6e80

📥 Commits

Reviewing files that changed from the base of the PR and between 72e475a and 5a76c28.

📒 Files selected for processing (4)
  • src/__tests__/main/cue/cue-session-state.test.ts
  • src/main/cue/cue-session-runtime-service.ts
  • src/main/cue/cue-session-state.ts
  • src/renderer/components/CueModal/SessionsTable.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/renderer/components/CueModal/SessionsTable.tsx
  • src/main/cue/cue-session-state.ts

Two follow-ups from CodeRabbit review on RunMaestro#873:

- cue-session-runtime-service: filter `config.subscriptions` once into
  `runnableSubscriptions` (drops unowned subs for non-owners) and reuse
  that view for trigger wiring, app.startup, `hasTimeBasedSubscriptions`,
  the init log, and the refresh activeCount. Without the filter a non-
  owner with only unowned time triggers would still call `onPreventSleep`
  and report active subscriptions for work it would never run.

- cue-completion-service: extend `getSessionConfigs` to expose the per-
  session `ownershipWarning` and skip unowned `agent.completed` subs on
  non-owner sessions in both `notifyAgentCompleted` and
  `hasCompletionSubscribers`. This closes the last duplication path in
  shared-projectRoot setups — without it, completion chains would still
  fire once per agent.

Adds two regression tests in cue-completion-chains.test.ts covering
the duplicate-completion case and the explicit-owner case.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/__tests__/main/cue/cue-completion-chains.test.ts`:
- Around line 1408-1449: The test currently returns the same sharedConfig for
both owner and non-owner (both at '/vault'), so the owner may also be counted;
change the fixture so only the non-owner session loads the subscription
configuration (e.g., give owner a different projectRoot like '/owner-vault' and
keep mockLoadCueConfig returning sharedConfig only for '/vault'), or
alternatively add an explicit assertion that the non-owner session alone does
not satisfy hasCompletionSubscribers by calling engine.hasCompletionSubscribers
scoped to that session; update the createMockSession/projectRoot values and
mockLoadCueConfig implementation and keep references to createMockSession,
createMockConfig, mockLoadCueConfig, createMockDeps, CueEngine, and
engine.hasCompletionSubscribers when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d7388c86-b278-4a27-9f0d-526ecdea02e6

📥 Commits

Reviewing files that changed from the base of the PR and between 5a76c28 and d8294d5.

📒 Files selected for processing (4)
  • src/__tests__/main/cue/cue-completion-chains.test.ts
  • src/main/cue/cue-completion-service.ts
  • src/main/cue/cue-engine.ts
  • src/main/cue/cue-session-runtime-service.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/cue/cue-session-runtime-service.ts

Comment on lines +1408 to +1449
it('hasCompletionSubscribers returns false when only the non-owner has the unowned sub', () => {
const sessions = [
createMockSession({ id: 'owner', name: 'Owner', projectRoot: '/vault' }),
createMockSession({ id: 'non-owner', name: 'NonOwner', projectRoot: '/vault' }),
createMockSession({ id: 'src', name: 'Source', projectRoot: '/projects/src' }),
];
const sharedConfig = createMockConfig({
// Pin owner explicitly so the test doesn't depend on first-wins.
settings: {
timeout_minutes: 30,
timeout_on_fail: 'break',
max_concurrent: 1,
queue_size: 10,
owner_agent_id: 'owner',
},
subscriptions: [
{
name: 'on-source-done',
event: 'agent.completed',
enabled: true,
prompt: 'react',
source_session: 'Source',
},
],
});

mockLoadCueConfig.mockImplementation((projectRoot) => {
if (projectRoot === '/vault') return sharedConfig;
return null;
});

const deps = createMockDeps({ getSessions: vi.fn(() => sessions) });
const engine = new CueEngine(deps);
engine.start();

// Owner has the sub wired; reporting hasCompletionSubscribers from
// the perspective of the source should remain true overall, but the
// non-owner alone must not satisfy the predicate.
expect(engine.hasCompletionSubscribers('src')).toBe(true);

engine.stop();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Strengthen the negative coverage here.

This assertion can still pass if the non-owner session is incorrectly counted, because the owner session loads the same subscription too. Split the fixture so only the non-owner can satisfy the source, or assert the non-owner contribution directly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/main/cue/cue-completion-chains.test.ts` around lines 1408 -
1449, The test currently returns the same sharedConfig for both owner and
non-owner (both at '/vault'), so the owner may also be counted; change the
fixture so only the non-owner session loads the subscription configuration
(e.g., give owner a different projectRoot like '/owner-vault' and keep
mockLoadCueConfig returning sharedConfig only for '/vault'), or alternatively
add an explicit assertion that the non-owner session alone does not satisfy
hasCompletionSubscribers by calling engine.hasCompletionSubscribers scoped to
that session; update the createMockSession/projectRoot values and
mockLoadCueConfig implementation and keep references to createMockSession,
createMockConfig, mockLoadCueConfig, createMockDeps, CueEngine, and
engine.hasCompletionSubscribers when making the change.

# Conflicts:
#	src/__tests__/main/cue/cue-completion-chains.test.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/__tests__/main/cue/cue-completion-chains.test.ts (1)

1614-1655: ⚠️ Potential issue | 🟡 Minor

Test name and assertion are inconsistent; coverage is still weak.

The test is named "returns false when only the non-owner has the unowned sub" but the fixture wires the same subscription on the owner as well (both sessions share /vault, owner pinned to 'owner'), and the assertion is toBe(true). As written, this test would pass even if the ownership gate did nothing, because the owner alone satisfies hasCompletionSubscribers('src'). To actually exercise the negative path implied by the name, either:

  • give the owner a different projectRoot (e.g. /owner-vault with no shared sub) so only the non-owner could satisfy the predicate, and assert toBe(false); or
  • keep the current name/intent but additionally introduce a probe that scopes the predicate to the non-owner session and asserts that probe is false.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/main/cue/cue-completion-chains.test.ts` around lines 1614 -
1655, The test name and assertion mismatch: the fixture currently gives both
owner and non-owner the same subscription via sharedConfig so the ownership gate
isn't exercised; update the fixture so only the non-owner has the unowned
subscription (e.g., change the owner session created by createMockSession to use
a different projectRoot like '/owner-vault' or remove owner_agent_id pinning)
and then assert expect(engine.hasCompletionSubscribers('src')).toBe(false);
reference symbols: hasCompletionSubscribers, createMockSession, sharedConfig
(owner_agent_id), createMockDeps, CueEngine.start/stop.
🧹 Nitpick comments (1)
src/__tests__/main/cue/cue-completion-chains.test.ts (1)

1554-1612: Pin owner_agent_id explicitly to decouple from default-policy ordering.

This case relies on the deterministic-default policy picking 'owner' purely from session array order. That makes the test fragile against legitimate changes to the fallback heuristic (e.g. switching from registration order to lexicographic id, or filtering on Cue-capable sessions per the issue's open thread). Setting settings.owner_agent_id: 'owner' on sharedConfig keeps this test focused on "non-owner is gated" rather than implicitly re-testing the fallback selection — and you already have separate cases covering the fallback matrix.

♻️ Suggested change
 		const sharedConfig = createMockConfig({
+			settings: {
+				timeout_minutes: 30,
+				timeout_on_fail: 'break',
+				max_concurrent: 1,
+				queue_size: 10,
+				owner_agent_id: 'owner',
+			},
 			subscriptions: [
 				{
 					name: 'on-source-done',
 					event: 'agent.completed',
 					enabled: true,
 					prompt: 'react',
 					source_session: 'Source',
 				},
 			],
 		});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/main/cue/cue-completion-chains.test.ts` around lines 1554 -
1612, The test relies on implicit default-owner selection; explicitly set the
owner by adding settings.owner_agent_id: 'owner' to the sharedConfig used in
this test (the object created via createMockConfig assigned to sharedConfig) so
the test no longer depends on fallback ordering; update the sharedConfig
definition in the test that sets subscriptions (the "skips unowned
agent.completed subs for non-owner sessions" case) to include a settings object
with owner_agent_id 'owner' and keep the rest of the test logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/__tests__/main/cue/cue-completion-chains.test.ts`:
- Around line 1614-1655: The test name and assertion mismatch: the fixture
currently gives both owner and non-owner the same subscription via sharedConfig
so the ownership gate isn't exercised; update the fixture so only the non-owner
has the unowned subscription (e.g., change the owner session created by
createMockSession to use a different projectRoot like '/owner-vault' or remove
owner_agent_id pinning) and then assert
expect(engine.hasCompletionSubscribers('src')).toBe(false); reference symbols:
hasCompletionSubscribers, createMockSession, sharedConfig (owner_agent_id),
createMockDeps, CueEngine.start/stop.

---

Nitpick comments:
In `@src/__tests__/main/cue/cue-completion-chains.test.ts`:
- Around line 1554-1612: The test relies on implicit default-owner selection;
explicitly set the owner by adding settings.owner_agent_id: 'owner' to the
sharedConfig used in this test (the object created via createMockConfig assigned
to sharedConfig) so the test no longer depends on fallback ordering; update the
sharedConfig definition in the test that sets subscriptions (the "skips unowned
agent.completed subs for non-owner sessions" case) to include a settings object
with owner_agent_id 'owner' and keep the rest of the test logic unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3d10d965-eb9e-4d56-b944-018426e69d2c

📥 Commits

Reviewing files that changed from the base of the PR and between d8294d5 and f13e811.

📒 Files selected for processing (1)
  • src/__tests__/main/cue/cue-completion-chains.test.ts

@pedramamini pedramamini merged commit 3c67c25 into RunMaestro:rc Apr 25, 2026
3 checks passed
@pedramamini
Copy link
Copy Markdown
Collaborator

Merged — thank you for this @scriptease! 🙏

The matrix-driven framing made the review trivial to reason about, and consolidating the gate behind a single pure utility (computeOwnershipWarning) is exactly the kind of factoring that keeps this code reviewable as more shared-workspace edge cases inevitably surface. Solid work all around.

I tacked on one follow-up commit covering the two outside-diff items CodeRabbit flagged on later passes — extending the ownership gate to hasTimeBasedSubscriptions / countActiveSubscriptions / refresh activeCount, and to the agent.completed dispatch path in cue-completion-service. Without them a non-owner could still keep the machine awake or fire duplicate completion chains. Two regression tests added in cue-completion-chains.test.ts.

Looking forward to your next one.

reachrazamair added a commit that referenced this pull request Apr 25, 2026
…stry dedup

RC brought two Cue-related commits since the cue-polish merge point:

  #849 — resolvePipelinesWriteRoots: seed lastWrittenRootsRef with per-pipeline
  write roots (common ancestor) rather than each agent's individual projectRoot,
  so delete+save clears YAML at the correct location.

  #873 — owner_agent_id: computeOwnershipWarning + runnableSubscriptions filter
  in cue-session-runtime-service resolves shared-workspace trigger duplication
  at init time, surfaces a dashboard warning, and respects explicit YAML config.

Our cue-polish commit contained a registry-level first-registered-wins approach
for the same #867 problem. That approach is now superseded: runnableSubscriptions
already filters unowned subs for non-owner sessions before the trigger-source
loop runs, so the claimSharedTriggerOwner check was unreachable for non-owners
and redundant for the owner.

Resolution:
- Keep RC's ownershipWarning/runnableSubscriptions (#873) — more complete,
  user-visible, handles explicit owner_agent_id YAML config
- Remove sharedTriggerOwners map + 3 methods from cue-session-registry.ts
- Remove claimSharedTriggerOwner + releaseSharedTriggersForSession calls from
  cue-session-runtime-service.ts
- Delete cue-shared-trigger-dedup.test.ts (tests the removed registry approach;
  cue-session-state.test.ts from #873 covers computeOwnershipWarning instead)
- Keep all other cue-polish fixes: #866 dormant visibility, #868 Run Now dedup,
  #847 error-node pipeline filter, SessionsTable ownershipWarning indicator

26 371 tests passing, 0 failures.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants