rivet-design 0.8.6 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/bin/rivet.js +11 -0
  2. package/dist/config/evaluateFlags.d.ts +6 -2
  3. package/dist/config/evaluateFlags.d.ts.map +1 -1
  4. package/dist/config/evaluateFlags.js +4 -4
  5. package/dist/config/evaluateFlags.js.map +1 -1
  6. package/dist/config/featureFlagUserKey.d.ts +11 -0
  7. package/dist/config/featureFlagUserKey.d.ts.map +1 -0
  8. package/dist/config/featureFlagUserKey.js +21 -0
  9. package/dist/config/featureFlagUserKey.js.map +1 -0
  10. package/dist/config/flags.d.ts +6 -0
  11. package/dist/config/flags.d.ts.map +1 -1
  12. package/dist/config/flags.js +2 -0
  13. package/dist/config/flags.js.map +1 -1
  14. package/dist/config/proxy.d.ts +1 -0
  15. package/dist/config/proxy.d.ts.map +1 -1
  16. package/dist/config/proxy.js +9 -1
  17. package/dist/config/proxy.js.map +1 -1
  18. package/dist/index.js +198 -12
  19. package/dist/index.js.map +1 -1
  20. package/dist/mcp/agent-variants/SessionStore.d.ts +121 -0
  21. package/dist/mcp/agent-variants/SessionStore.d.ts.map +1 -0
  22. package/dist/mcp/agent-variants/SessionStore.js +480 -0
  23. package/dist/mcp/agent-variants/SessionStore.js.map +1 -0
  24. package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts +187 -0
  25. package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -0
  26. package/dist/mcp/agent-variants/WorktreeOrchestrator.js +482 -0
  27. package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -0
  28. package/dist/mcp/agent-variants/contracts.d.ts +373 -0
  29. package/dist/mcp/agent-variants/contracts.d.ts.map +1 -0
  30. package/dist/mcp/agent-variants/contracts.js +134 -0
  31. package/dist/mcp/agent-variants/contracts.js.map +1 -0
  32. package/dist/mcp/agent-variants/errors.d.ts +8 -0
  33. package/dist/mcp/agent-variants/errors.d.ts.map +1 -0
  34. package/dist/mcp/agent-variants/errors.js +30 -0
  35. package/dist/mcp/agent-variants/errors.js.map +1 -0
  36. package/dist/mcp/agent-variants/index.d.ts +11 -0
  37. package/dist/mcp/agent-variants/index.d.ts.map +1 -0
  38. package/dist/mcp/agent-variants/index.js +15 -0
  39. package/dist/mcp/agent-variants/index.js.map +1 -0
  40. package/dist/mcp/agent-variants/pendingChangesAdapter.d.ts +38 -0
  41. package/dist/mcp/agent-variants/pendingChangesAdapter.d.ts.map +1 -0
  42. package/dist/mcp/agent-variants/pendingChangesAdapter.js +79 -0
  43. package/dist/mcp/agent-variants/pendingChangesAdapter.js.map +1 -0
  44. package/dist/mcp/agent-variants/tools.d.ts +8 -0
  45. package/dist/mcp/agent-variants/tools.d.ts.map +1 -0
  46. package/dist/mcp/agent-variants/tools.js +221 -0
  47. package/dist/mcp/agent-variants/tools.js.map +1 -0
  48. package/dist/mcp/server.d.ts.map +1 -1
  49. package/dist/mcp/server.js +51 -34
  50. package/dist/mcp/server.js.map +1 -1
  51. package/dist/proxy-middleware/proxy-config.d.ts +4 -1
  52. package/dist/proxy-middleware/proxy-config.d.ts.map +1 -1
  53. package/dist/proxy-middleware/proxy-config.js +34 -5
  54. package/dist/proxy-middleware/proxy-config.js.map +1 -1
  55. package/dist/routes/agentVariants.d.ts +16 -0
  56. package/dist/routes/agentVariants.d.ts.map +1 -0
  57. package/dist/routes/agentVariants.js +234 -0
  58. package/dist/routes/agentVariants.js.map +1 -0
  59. package/dist/server.d.ts +3 -0
  60. package/dist/server.d.ts.map +1 -1
  61. package/dist/server.js +24 -8
  62. package/dist/server.js.map +1 -1
  63. package/dist/services/ConfigManager.d.ts +8 -0
  64. package/dist/services/ConfigManager.d.ts.map +1 -1
  65. package/dist/services/ConfigManager.js +13 -0
  66. package/dist/services/ConfigManager.js.map +1 -1
  67. package/dist/services/FeatureFlagService.d.ts +9 -5
  68. package/dist/services/FeatureFlagService.d.ts.map +1 -1
  69. package/dist/services/FeatureFlagService.js +18 -9
  70. package/dist/services/FeatureFlagService.js.map +1 -1
  71. package/dist/services/SessionBridgeService.d.ts +22 -0
  72. package/dist/services/SessionBridgeService.d.ts.map +1 -1
  73. package/dist/services/SessionBridgeService.js +58 -1
  74. package/dist/services/SessionBridgeService.js.map +1 -1
  75. package/dist/services/TelemetryService.d.ts +5 -1
  76. package/dist/services/TelemetryService.d.ts.map +1 -1
  77. package/dist/services/TelemetryService.js +19 -7
  78. package/dist/services/TelemetryService.js.map +1 -1
  79. package/dist/services/WorktreeManager.d.ts +42 -3
  80. package/dist/services/WorktreeManager.d.ts.map +1 -1
  81. package/dist/services/WorktreeManager.js +149 -6
  82. package/dist/services/WorktreeManager.js.map +1 -1
  83. package/dist/types/change-request-types.d.ts +36 -1
  84. package/dist/types/change-request-types.d.ts.map +1 -1
  85. package/dist/utils/devServerCommand.d.ts +19 -0
  86. package/dist/utils/devServerCommand.d.ts.map +1 -0
  87. package/dist/utils/devServerCommand.js +38 -0
  88. package/dist/utils/devServerCommand.js.map +1 -0
  89. package/dist/utils/skills/claude-skill.d.ts +2 -2
  90. package/dist/utils/skills/claude-skill.d.ts.map +1 -1
  91. package/dist/utils/skills/claude-skill.js +87 -4
  92. package/dist/utils/skills/claude-skill.js.map +1 -1
  93. package/dist/utils/skills/cursor-rules.d.ts +2 -2
  94. package/dist/utils/skills/cursor-rules.d.ts.map +1 -1
  95. package/dist/utils/skills/cursor-rules.js +87 -4
  96. package/dist/utils/skills/cursor-rules.js.map +1 -1
  97. package/dist/utils/variantPreviewPort.d.ts +18 -0
  98. package/dist/utils/variantPreviewPort.d.ts.map +1 -0
  99. package/dist/utils/variantPreviewPort.js +28 -0
  100. package/dist/utils/variantPreviewPort.js.map +1 -0
  101. package/package.json +1 -1
  102. package/src/ui/dist/assets/main-Bv0LuxKz.js +382 -0
  103. package/src/ui/dist/assets/main-BzmseUDd.css +1 -0
  104. package/src/ui/dist/index.html +2 -2
  105. package/src/ui/dist/assets/main-DpcUh_b_.js +0 -382
  106. package/src/ui/dist/assets/main-wC0IkT-I.css +0 -1
@@ -0,0 +1,187 @@
1
+ import type { ChildProcess } from 'child_process';
2
+ import { AgentVariantsError } from './errors';
3
+ import { SessionStore, type ApprovedBriefSelection, type ApproveResult, type ProposeArgs, type ProposeResult, type ReportCompleteResult, type RequestWorkResult } from './SessionStore';
4
+ import type { Brief, Stage, TerminalSummary, WorkItemStatus } from './contracts';
5
+ import type { PendingChangesAdapter } from './pendingChangesAdapter';
6
+ /**
7
+ * Subset of WorktreeManager that the orchestrator depends on. Defined as an
8
+ * interface so unit tests can mock it without spinning up real git/processes.
9
+ *
10
+ * Each succeeded variant gets its own dev server so the user can cycle
11
+ * between live, running variants in the iframe via a proxy retarget.
12
+ */
13
+ export interface WorktreeManagerLike {
14
+ createWorktrees(sessionId: string, count: number): Promise<string[]>;
15
+ getDiff(worktreePath: string): Promise<string>;
16
+ cleanupSession(sessionId: string): Promise<void>;
17
+ getFreePort(): Promise<number>;
18
+ startDevServer(worktreePath: string, port: number, cmd: string, args: string[], env: Record<string, string>): Promise<ChildProcess>;
19
+ stopDevServer(proc: ChildProcess, timeoutMs?: number): Promise<void>;
20
+ getProjectCwdInWorktree(worktreePath: string): Promise<string>;
21
+ }
22
+ /**
23
+ * Per-session resolved environment. Used to populate destinationPath on the
24
+ * variant envelope and (in fresh-project flows) the workspace target. Also
25
+ * feeds buildDevServerCommand so each variant's dev server gets the correct
26
+ * port flag for its framework.
27
+ */
28
+ export interface ProjectEnvironment {
29
+ projectPath: string;
30
+ framework: string;
31
+ packageManager: string;
32
+ devCommand: string;
33
+ /** Builds (cmd, args, env) for spawning a dev server in a worktree on a
34
+ * specific port. Defaults to a generic { packageManager, [devCommand,
35
+ * '--port', port] } shape if not provided, but production wires this to
36
+ * buildDevServerCommand from utils/devServerCommand.ts. */
37
+ buildDevCommand?: (port: number) => {
38
+ cmd: string;
39
+ args: string[];
40
+ env: Record<string, string>;
41
+ };
42
+ }
43
+ export interface OrchestratorDeps {
44
+ store: SessionStore;
45
+ worktreeManager: WorktreeManagerLike;
46
+ pendingChangesAdapter: PendingChangesAdapter;
47
+ resolveProjectEnvironment: (sessionId: string) => Promise<ProjectEnvironment>;
48
+ telemetry?: VariantsTelemetry;
49
+ }
50
+ /**
51
+ * Minimal telemetry surface the orchestrator depends on. Decoupled from
52
+ * TelemetryService so unit tests can pass a stub. Production wires this
53
+ * to the singleton TelemetryService instance.
54
+ */
55
+ export interface VariantsTelemetry {
56
+ track(event: string, properties?: Record<string, unknown>): void;
57
+ }
58
+ /** Snapshot shape consumed by the iframe chip, both via SSE and the legacy
59
+ * GET /api/variants/active endpoint. Single source of truth lives on the
60
+ * orchestrator (`buildActiveSnapshot`) so both transports are consistent. */
61
+ export type ActiveSnapshot = {
62
+ active: false;
63
+ } | {
64
+ active: true;
65
+ sessionId: string;
66
+ stage: Stage;
67
+ briefs: Brief[];
68
+ progress: {
69
+ ready: number;
70
+ total: number;
71
+ };
72
+ summary: TerminalSummary | null;
73
+ variants: Array<{
74
+ workItemId: string;
75
+ status: import('./contracts').WorkItemStatus;
76
+ label?: string;
77
+ previewPort?: number;
78
+ worktreePath?: string;
79
+ }>;
80
+ };
81
+ /**
82
+ * Wraps SessionStore for the operations that have side effects: approve
83
+ * (provision worktrees), reportComplete (capture diff + auto-enqueue to
84
+ * pending changes), cancel (teardown). Pure read paths bypass and call
85
+ * SessionStore directly.
86
+ *
87
+ * This is the terminal-native variant of the orchestrator: the user picks
88
+ * a single brief upfront, so there's only ever one code-gen item per
89
+ * session and no live preview / proxy retarget. Once the agent reports
90
+ * the variant succeeded, the diff auto-enqueues to bridge — no separate
91
+ * pick step.
92
+ */
93
+ export declare class AgentVariantsOrchestrator {
94
+ private readonly store;
95
+ private readonly worktrees;
96
+ private readonly adapter;
97
+ private readonly resolveEnv;
98
+ private readonly telemetry;
99
+ private readonly resources;
100
+ /** Most recent agent-variants sessionId — read by the iframe chip via
101
+ * GET /api/variants/active. Cleared on cancel or commit. */
102
+ private activeSessionId;
103
+ /** Push channel for the iframe chip's SSE subscription. Emits an
104
+ * ActiveSnapshot on every state change so the chip never has to poll. */
105
+ private readonly events;
106
+ constructor(deps: OrchestratorDeps);
107
+ propose(args: ProposeArgs): ProposeResult;
108
+ /** Returns the most recent agent-variants sessionId (or null). */
109
+ getActiveSessionId(): string | null;
110
+ /** Subscribe to active-snapshot pushes. Returns an unsubscribe fn. */
111
+ subscribeToEvents(listener: (snapshot: ActiveSnapshot) => void): () => void;
112
+ /** Build the snapshot the chip cares about. Reads from SessionStore +
113
+ * per-resource state; safe to call at any time. */
114
+ buildActiveSnapshot(): ActiveSnapshot;
115
+ private emitChange;
116
+ reportBriefs(args: {
117
+ sessionId: string;
118
+ workItemId: string;
119
+ attempt: number;
120
+ leaseId?: string;
121
+ briefs: Brief[];
122
+ }): {
123
+ briefs: Brief[];
124
+ };
125
+ requestWork(args: {
126
+ sessionId: string;
127
+ leaseOwner: string;
128
+ requestedLeaseCount?: number;
129
+ }): RequestWorkResult;
130
+ getStage(sessionId: string): Stage;
131
+ getBriefs(sessionId: string): Brief[];
132
+ getProgress(sessionId: string): {
133
+ ready: number;
134
+ total: number;
135
+ };
136
+ getSummary(sessionId: string): TerminalSummary;
137
+ hasSession(sessionId: string): boolean;
138
+ /** Resolve a worktree path for a code-gen work item, if provisioned. */
139
+ getWorktreePath(sessionId: string, workItemId: string): string | undefined;
140
+ /** Resolve the dev server port for a code-gen work item, if running. */
141
+ getDevServerPort(sessionId: string, workItemId: string): number | undefined;
142
+ approve(args: {
143
+ sessionId: string;
144
+ selections: ApprovedBriefSelection[];
145
+ }): Promise<ApproveResult>;
146
+ reportComplete(args: {
147
+ sessionId: string;
148
+ workItemId: string;
149
+ leaseId: string;
150
+ attempt: number;
151
+ status: WorkItemStatus;
152
+ output?: unknown;
153
+ error?: {
154
+ code: string;
155
+ message: string;
156
+ };
157
+ }): Promise<ReportCompleteResult>;
158
+ cancel(args: {
159
+ sessionId: string;
160
+ reason?: string;
161
+ }): Promise<{
162
+ stage: 'cancelled';
163
+ cleanupStatus: 'pending' | 'complete' | 'partial';
164
+ }>;
165
+ /**
166
+ * User has reviewed the rendered variants in chat and picked one. Look up
167
+ * the captured diff, build a VariantPickEnvelope, and enqueue to the
168
+ * pending-changes channel. Idempotent on (sessionId, variantId): repeating
169
+ * the call returns duplicate=true without re-enqueueing.
170
+ */
171
+ commitVariant(args: {
172
+ sessionId: string;
173
+ variantId: string;
174
+ }): Promise<{
175
+ enqueued: boolean;
176
+ duplicate: boolean;
177
+ changedFilesCount: number;
178
+ }>;
179
+ /** Read the captured diff for a code-gen variant, if available. */
180
+ getVariantDiff(sessionId: string, variantId: string): string | undefined;
181
+ private provisionWorktrees;
182
+ private handleSucceededReport;
183
+ private teardownSession;
184
+ private ensureResources;
185
+ }
186
+ export type { AgentVariantsError };
187
+ //# sourceMappingURL=WorktreeOrchestrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorktreeOrchestrator.d.ts","sourceRoot":"","sources":["../../../src/mcp/agent-variants/WorktreeOrchestrator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,482 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentVariantsOrchestrator = void 0;
4
+ const events_1 = require("events");
5
+ const logger_1 = require("../../utils/logger");
6
+ const errors_1 = require("./errors");
7
+ const contracts_1 = require("./contracts");
8
+ const log = (0, logger_1.createLogger)('AgentVariantsOrchestrator');
9
+ const NOOP_TELEMETRY = { track: () => undefined };
10
+ /**
11
+ * Wraps SessionStore for the operations that have side effects: approve
12
+ * (provision worktrees), reportComplete (capture diff + auto-enqueue to
13
+ * pending changes), cancel (teardown). Pure read paths bypass and call
14
+ * SessionStore directly.
15
+ *
16
+ * This is the terminal-native variant of the orchestrator: the user picks
17
+ * a single brief upfront, so there's only ever one code-gen item per
18
+ * session and no live preview / proxy retarget. Once the agent reports
19
+ * the variant succeeded, the diff auto-enqueues to bridge — no separate
20
+ * pick step.
21
+ */
22
+ class AgentVariantsOrchestrator {
23
+ store;
24
+ worktrees;
25
+ adapter;
26
+ resolveEnv;
27
+ telemetry;
28
+ resources = new Map();
29
+ /** Most recent agent-variants sessionId — read by the iframe chip via
30
+ * GET /api/variants/active. Cleared on cancel or commit. */
31
+ activeSessionId = null;
32
+ /** Push channel for the iframe chip's SSE subscription. Emits an
33
+ * ActiveSnapshot on every state change so the chip never has to poll. */
34
+ events = new events_1.EventEmitter();
35
+ constructor(deps) {
36
+ this.store = deps.store;
37
+ this.worktrees = deps.worktreeManager;
38
+ this.adapter = deps.pendingChangesAdapter;
39
+ this.resolveEnv = deps.resolveProjectEnvironment;
40
+ this.telemetry = deps.telemetry ?? NOOP_TELEMETRY;
41
+ }
42
+ // --- Pure delegations (no side effects) ---------------------------------
43
+ propose(args) {
44
+ const result = this.store.propose(args);
45
+ this.activeSessionId = result.sessionId;
46
+ this.ensureResources(result.sessionId);
47
+ this.telemetry.track('agent_variants.session_started', {
48
+ source: 'mcp',
49
+ sessionId: result.sessionId,
50
+ count: args.count ?? 4,
51
+ hasTarget: Boolean(args.target),
52
+ targetType: args.target?.type ?? null,
53
+ projectContextKind: args.projectContext?.kind ?? 'existing',
54
+ });
55
+ this.emitChange();
56
+ return result;
57
+ }
58
+ /** Returns the most recent agent-variants sessionId (or null). */
59
+ getActiveSessionId() {
60
+ return this.activeSessionId;
61
+ }
62
+ /** Subscribe to active-snapshot pushes. Returns an unsubscribe fn. */
63
+ subscribeToEvents(listener) {
64
+ this.events.on('change', listener);
65
+ return () => {
66
+ this.events.off('change', listener);
67
+ };
68
+ }
69
+ /** Build the snapshot the chip cares about. Reads from SessionStore +
70
+ * per-resource state; safe to call at any time. */
71
+ buildActiveSnapshot() {
72
+ const sessionId = this.activeSessionId;
73
+ if (!sessionId || !this.store.hasSession(sessionId)) {
74
+ return { active: false };
75
+ }
76
+ const stage = this.store.getStage(sessionId);
77
+ const briefs = this.store.getBriefs(sessionId);
78
+ const progress = this.store.getProgress(sessionId);
79
+ const summary = stage === 'ready' ||
80
+ stage === 'degraded' ||
81
+ stage === 'failed' ||
82
+ stage === 'cancelled'
83
+ ? this.store.getSummary(sessionId)
84
+ : null;
85
+ const variants = (summary?.variants ?? []).map((v) => ({
86
+ workItemId: v.workItemId,
87
+ status: v.status,
88
+ label: v.label,
89
+ previewPort: this.getDevServerPort(sessionId, v.workItemId),
90
+ worktreePath: this.getWorktreePath(sessionId, v.workItemId),
91
+ }));
92
+ return {
93
+ active: true,
94
+ sessionId,
95
+ stage,
96
+ briefs,
97
+ progress,
98
+ summary,
99
+ variants,
100
+ };
101
+ }
102
+ emitChange() {
103
+ try {
104
+ this.events.emit('change', this.buildActiveSnapshot());
105
+ }
106
+ catch (err) {
107
+ log.warn('emitChange listener threw', err);
108
+ }
109
+ }
110
+ reportBriefs(args) {
111
+ const result = this.store.reportBriefs(args);
112
+ this.emitChange();
113
+ return result;
114
+ }
115
+ requestWork(args) {
116
+ const result = this.store.requestWork(args);
117
+ // Record lease times so variant_completed can report lease→report
118
+ // duration. (We don't fire a separate code_gen_leased event — the
119
+ // funnel signal we care about is per-variant completion duration.)
120
+ const resources = this.resources.get(args.sessionId);
121
+ if (resources) {
122
+ const now = Date.now();
123
+ for (const item of result.leasedWorkItems) {
124
+ resources.leasedAt.set(item.id, now);
125
+ }
126
+ }
127
+ // Decorate each leased item's input with the worktreePath provisioned
128
+ // by approve(). The skill instructs agents to write files to
129
+ // `input.worktreePath` — without this decoration that field would be
130
+ // missing and the agent would have no idea where to edit.
131
+ const decorated = {
132
+ ...result,
133
+ leasedWorkItems: result.leasedWorkItems.map((item) => {
134
+ const worktreePath = this.getWorktreePath(args.sessionId, item.id);
135
+ const baseInput = item.input && typeof item.input === 'object'
136
+ ? item.input
137
+ : {};
138
+ return {
139
+ ...item,
140
+ input: { ...baseInput, worktreePath },
141
+ };
142
+ }),
143
+ };
144
+ this.emitChange();
145
+ return decorated;
146
+ }
147
+ getStage(sessionId) {
148
+ return this.store.getStage(sessionId);
149
+ }
150
+ getBriefs(sessionId) {
151
+ return this.store.getBriefs(sessionId);
152
+ }
153
+ getProgress(sessionId) {
154
+ return this.store.getProgress(sessionId);
155
+ }
156
+ getSummary(sessionId) {
157
+ return this.store.getSummary(sessionId);
158
+ }
159
+ hasSession(sessionId) {
160
+ return this.store.hasSession(sessionId);
161
+ }
162
+ /** Resolve a worktree path for a code-gen work item, if provisioned. */
163
+ getWorktreePath(sessionId, workItemId) {
164
+ return this.resources.get(sessionId)?.worktrees.get(workItemId)
165
+ ?.worktreePath;
166
+ }
167
+ /** Resolve the dev server port for a code-gen work item, if running. */
168
+ getDevServerPort(sessionId, workItemId) {
169
+ return this.resources.get(sessionId)?.worktrees.get(workItemId)?.port;
170
+ }
171
+ // --- Mutation wrappers (state + side effects) ---------------------------
172
+ async approve(args) {
173
+ const result = this.store.approve(args);
174
+ this.ensureResources(args.sessionId);
175
+ this.telemetry.track('agent_variants.approved', {
176
+ source: 'mcp',
177
+ sessionId: args.sessionId,
178
+ approvedCount: result.approvedCount,
179
+ totalBriefs: result.totalCount,
180
+ isFresh: Boolean(result.scaffoldBaseWorkItemId),
181
+ });
182
+ this.emitChange();
183
+ // Await provisioning before returning so request_work always sees a
184
+ // populated worktreePath on each leased item. Provisioning is fast
185
+ // (`git worktree add` per variant + node_modules symlink), and the
186
+ // agent has nothing useful to do between approve and request_work
187
+ // anyway — running them serially is the contract the skill assumes.
188
+ try {
189
+ await this.provisionWorktrees(args.sessionId, result);
190
+ this.emitChange();
191
+ }
192
+ catch (err) {
193
+ log.error(`provisionWorktrees failed for session ${args.sessionId}`, err);
194
+ throw err;
195
+ }
196
+ return result;
197
+ }
198
+ async reportComplete(args) {
199
+ const result = this.store.reportComplete(args);
200
+ this.emitChange();
201
+ // Per-variant telemetry on terminal item statuses (skip 'running'
202
+ // heartbeats and the brief work item — only code_gen / scaffold_base
203
+ // matter for the funnel).
204
+ if (args.status === 'succeeded' ||
205
+ args.status === 'failed' ||
206
+ args.status === 'cancelled') {
207
+ const resources = this.resources.get(args.sessionId);
208
+ const leasedAt = resources?.leasedAt.get(args.workItemId);
209
+ const durationMs = leasedAt !== undefined ? Date.now() - leasedAt : null;
210
+ this.telemetry.track('agent_variants.variant_completed', {
211
+ source: 'mcp',
212
+ sessionId: args.sessionId,
213
+ workItemId: args.workItemId,
214
+ status: args.status,
215
+ attempt: args.attempt,
216
+ durationMs,
217
+ hasError: Boolean(args.error),
218
+ errorCode: args.error?.code ?? null,
219
+ isScaffold: resources?.scaffoldBaseWorkItemId === args.workItemId,
220
+ });
221
+ }
222
+ if (contracts_1.TERMINAL_STAGES.has(result.stage)) {
223
+ const resources = this.resources.get(args.sessionId);
224
+ if (resources && resources.terminalAt === undefined) {
225
+ resources.terminalAt = Date.now();
226
+ const totalLatencyMs = resources.terminalAt - resources.startedAt;
227
+ const summary = result.summary ?? this.store.getSummary(args.sessionId);
228
+ this.telemetry.track('agent_variants.terminal_state', {
229
+ source: 'mcp',
230
+ sessionId: args.sessionId,
231
+ terminalStage: result.stage,
232
+ successCount: summary.successCount,
233
+ failureCount: summary.failureCount,
234
+ cancelledCount: summary.cancelledCount,
235
+ totalLatencyMs,
236
+ });
237
+ }
238
+ }
239
+ if (args.status === 'succeeded') {
240
+ void this.handleSucceededReport(args.sessionId, args.workItemId).catch((err) => {
241
+ log.error(`handleSucceededReport failed for ${args.sessionId}/${args.workItemId}`, err);
242
+ });
243
+ }
244
+ // Teardown is deferred to commitVariant / cancel. Terminal stage just
245
+ // means all variants are done; the user still needs to review and pick
246
+ // one. Resources (worktrees + captured diffs) live until that pick lands
247
+ // on the bridge.
248
+ return result;
249
+ }
250
+ async cancel(args) {
251
+ const stageBefore = this.store.hasSession(args.sessionId)
252
+ ? this.store.getStage(args.sessionId)
253
+ : null;
254
+ const result = this.store.cancel(args);
255
+ if (this.activeSessionId === args.sessionId) {
256
+ this.activeSessionId = null;
257
+ }
258
+ this.telemetry.track('agent_variants.cancelled', {
259
+ source: 'mcp',
260
+ sessionId: args.sessionId,
261
+ reason: args.reason ?? null,
262
+ fromStage: stageBefore,
263
+ });
264
+ this.emitChange();
265
+ void this.teardownSession(args.sessionId, 'cancel').catch((err) => {
266
+ log.error(`teardownSession failed for ${args.sessionId}`, err);
267
+ });
268
+ return result;
269
+ }
270
+ /**
271
+ * User has reviewed the rendered variants in chat and picked one. Look up
272
+ * the captured diff, build a VariantPickEnvelope, and enqueue to the
273
+ * pending-changes channel. Idempotent on (sessionId, variantId): repeating
274
+ * the call returns duplicate=true without re-enqueueing.
275
+ */
276
+ async commitVariant(args) {
277
+ // Idempotent path — SessionStore.recordVariantPick is the source of truth
278
+ // for which variant the user picked. If the same variant is being
279
+ // committed again, replay the previously stored envelope's metadata
280
+ // without going back through resources (which may have been torn down).
281
+ const existingPick = this.store.getVariantPick(args.sessionId);
282
+ if (existingPick && existingPick.variantId === args.variantId) {
283
+ return {
284
+ enqueued: false,
285
+ duplicate: true,
286
+ changedFilesCount: existingPick.changedFilesCount,
287
+ };
288
+ }
289
+ const resources = this.resources.get(args.sessionId);
290
+ if (!resources) {
291
+ throw new errors_1.AgentVariantsError('SESSION_NOT_FOUND', `No live resources for session ${args.sessionId} (already torn down?)`);
292
+ }
293
+ const record = resources.worktrees.get(args.variantId);
294
+ if (!record) {
295
+ throw new errors_1.AgentVariantsError('WORK_ITEM_NOT_FOUND', `Unknown variantId ${args.variantId} for session ${args.sessionId}`);
296
+ }
297
+ if (record.diff === undefined) {
298
+ throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `Variant ${args.variantId} has no captured diff yet — wait for report_variant_complete(succeeded) first`);
299
+ }
300
+ const env = await this.resolveEnv(args.sessionId);
301
+ const input = this.store.getWorkItemInput(args.sessionId, args.variantId);
302
+ const projectContext = this.store.getProjectContext(args.sessionId);
303
+ const changedFilesCount = countDiffFiles(record.diff);
304
+ const payload = projectContext.kind === 'fresh'
305
+ ? {
306
+ kind: 'new-project',
307
+ destinationPath: projectContext.workspacePath,
308
+ sourceWorktreePath: record.worktreePath,
309
+ changedFilesCount,
310
+ }
311
+ : {
312
+ kind: 'diff',
313
+ diff: record.diff,
314
+ target: input.target,
315
+ changedFilesCount,
316
+ };
317
+ const envelope = {
318
+ sourceSessionId: args.sessionId,
319
+ variantId: args.variantId,
320
+ variantLabel: input.briefLabel,
321
+ destinationPath: projectContext.kind === 'fresh'
322
+ ? projectContext.workspacePath
323
+ : env.projectPath,
324
+ changedFilesCount,
325
+ payload,
326
+ };
327
+ // Record the pick in the SessionStore so future commitVariant calls with
328
+ // the same variantId are idempotent and a different variantId returns
329
+ // PENDING_CHANGE_CONFLICT (already picked).
330
+ this.store.recordVariantPick({
331
+ sessionId: args.sessionId,
332
+ envelope,
333
+ });
334
+ const enqueueResult = this.adapter.enqueue(envelope);
335
+ resources.committedVariantIds.add(args.variantId);
336
+ if (this.activeSessionId === args.sessionId) {
337
+ this.activeSessionId = null;
338
+ }
339
+ const dwellMsFromTerminal = resources.terminalAt
340
+ ? Date.now() - resources.terminalAt
341
+ : null;
342
+ this.telemetry.track('agent_variants.committed', {
343
+ source: 'mcp',
344
+ sessionId: args.sessionId,
345
+ workItemId: args.variantId,
346
+ payloadKind: payload.kind,
347
+ changedFilesCount,
348
+ dwellMsFromTerminal,
349
+ isFresh: projectContext.kind === 'fresh',
350
+ });
351
+ this.emitChange();
352
+ log.info(`Variant ${args.variantId} (${input.briefLabel}) committed by user → enqueued=${enqueueResult.enqueued}`);
353
+ // Once a variant is committed, the unpicked worktrees are no longer
354
+ // useful. Schedule teardown in the background so the agent's
355
+ // commit_variant call returns immediately.
356
+ void this.teardownSession(args.sessionId, 'committed').catch((err) => {
357
+ log.warn(`teardownSession after commit failed for ${args.sessionId}`, err);
358
+ });
359
+ return {
360
+ enqueued: enqueueResult.enqueued,
361
+ duplicate: enqueueResult.duplicate,
362
+ changedFilesCount,
363
+ };
364
+ }
365
+ /** Read the captured diff for a code-gen variant, if available. */
366
+ getVariantDiff(sessionId, variantId) {
367
+ return this.resources.get(sessionId)?.worktrees.get(variantId)?.diff;
368
+ }
369
+ // --- Side-effect implementations ----------------------------------------
370
+ async provisionWorktrees(sessionId, approveResult) {
371
+ const resources = this.ensureResources(sessionId);
372
+ const totalCount = approveResult.codeGenWorkItemIds.length +
373
+ (approveResult.scaffoldBaseWorkItemId ? 1 : 0);
374
+ if (totalCount === 0)
375
+ return;
376
+ log.info(`Provisioning ${totalCount} worktree(s) for session ${sessionId}`);
377
+ const paths = await this.worktrees.createWorktrees(sessionId, totalCount);
378
+ let cursor = 0;
379
+ if (approveResult.scaffoldBaseWorkItemId) {
380
+ resources.scaffoldBaseWorkItemId = approveResult.scaffoldBaseWorkItemId;
381
+ resources.worktrees.set(approveResult.scaffoldBaseWorkItemId, {
382
+ workItemId: approveResult.scaffoldBaseWorkItemId,
383
+ worktreePath: paths[cursor],
384
+ });
385
+ cursor += 1;
386
+ }
387
+ for (const id of approveResult.codeGenWorkItemIds) {
388
+ const path = paths[cursor];
389
+ cursor += 1;
390
+ resources.worktrees.set(id, { workItemId: id, worktreePath: path });
391
+ }
392
+ }
393
+ async handleSucceededReport(sessionId, workItemId) {
394
+ const resources = this.resources.get(sessionId);
395
+ const record = resources?.worktrees.get(workItemId);
396
+ if (!resources || !record) {
397
+ log.warn(`No worktree record for ${sessionId}/${workItemId}; skipping diff capture`);
398
+ return;
399
+ }
400
+ // scaffold_base sets up the base for dependent code_gen items. No diff
401
+ // to capture from it — its result is the disk state of the worktree.
402
+ if (resources.scaffoldBaseWorkItemId === workItemId) {
403
+ log.info(`scaffold_base ${workItemId} succeeded; awaiting dependent code-gen`);
404
+ return;
405
+ }
406
+ try {
407
+ record.diff = await this.worktrees.getDiff(record.worktreePath);
408
+ log.info(`Variant ${workItemId} diff captured (${countDiffFiles(record.diff)} files)`);
409
+ this.emitChange();
410
+ }
411
+ catch (err) {
412
+ log.warn(`getDiff failed for ${record.worktreePath}`, err);
413
+ }
414
+ // Bring up a dev server in the variant's worktree so the user can cycle
415
+ // through live variants in the iframe via the chip. Failures here are
416
+ // logged but non-fatal — the user can still pick by reading the diff.
417
+ try {
418
+ const env = await this.resolveEnv(sessionId);
419
+ const port = await this.worktrees.getFreePort();
420
+ const cwd = await this.worktrees.getProjectCwdInWorktree(record.worktreePath);
421
+ const { cmd, args, env: spawnEnv } = env.buildDevCommand
422
+ ? env.buildDevCommand(port)
423
+ : {
424
+ cmd: env.packageManager,
425
+ args: [env.devCommand, '--port', String(port)],
426
+ env: { PORT: String(port) },
427
+ };
428
+ const proc = await this.worktrees.startDevServer(cwd, port, cmd, args, spawnEnv);
429
+ record.port = port;
430
+ record.devServerProcess = proc;
431
+ this.emitChange();
432
+ log.info(`Variant ${workItemId} dev server up on port ${port} (worktree ${record.worktreePath}; cmd: ${cmd} ${args.join(' ')})`);
433
+ }
434
+ catch (err) {
435
+ log.warn(`Failed to start dev server for variant ${workItemId}; live preview disabled for this variant`, err);
436
+ }
437
+ }
438
+ async teardownSession(sessionId, reason) {
439
+ const resources = this.resources.get(sessionId);
440
+ if (!resources)
441
+ return;
442
+ if (resources.cleanupStarted)
443
+ return;
444
+ resources.cleanupStarted = true;
445
+ log.info(`Tearing down session ${sessionId} (reason: ${reason})`);
446
+ // Stop dev servers in parallel; ignore individual failures.
447
+ const stops = [...resources.worktrees.values()]
448
+ .filter((r) => r.devServerProcess)
449
+ .map((r) => this.worktrees
450
+ .stopDevServer(r.devServerProcess)
451
+ .catch((err) => log.warn(`stopDevServer failed for ${sessionId}/${r.workItemId}`, err)));
452
+ await Promise.all(stops);
453
+ try {
454
+ await this.worktrees.cleanupSession(sessionId);
455
+ }
456
+ catch (err) {
457
+ log.warn(`cleanupSession (worktree removal) failed for ${sessionId}`, err);
458
+ }
459
+ this.resources.delete(sessionId);
460
+ }
461
+ ensureResources(sessionId) {
462
+ let r = this.resources.get(sessionId);
463
+ if (!r) {
464
+ r = {
465
+ sessionId,
466
+ worktrees: new Map(),
467
+ cleanupStarted: false,
468
+ committedVariantIds: new Set(),
469
+ startedAt: Date.now(),
470
+ leasedAt: new Map(),
471
+ };
472
+ this.resources.set(sessionId, r);
473
+ }
474
+ return r;
475
+ }
476
+ }
477
+ exports.AgentVariantsOrchestrator = AgentVariantsOrchestrator;
478
+ function countDiffFiles(diff) {
479
+ // Each file in a unified diff starts with "diff --git ".
480
+ return (diff.match(/^diff --git /gm) ?? []).length;
481
+ }
482
+ //# sourceMappingURL=WorktreeOrchestrator.js.map