@mycompbox/compbox-cli 1.17.2 → 1.19.0

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 (48) hide show
  1. package/.tsbuildinfo +1 -1
  2. package/commands/configure.d.ts.map +1 -1
  3. package/commands/configure.js +31 -11
  4. package/commands/configure.js.map +1 -1
  5. package/commands/doctor.d.ts.map +1 -1
  6. package/commands/doctor.js +1 -1
  7. package/commands/doctor.js.map +1 -1
  8. package/commands/login.d.ts +2 -0
  9. package/commands/login.d.ts.map +1 -1
  10. package/commands/login.js +28 -6
  11. package/commands/login.js.map +1 -1
  12. package/commands/run.d.ts.map +1 -1
  13. package/commands/run.js +37 -2
  14. package/commands/run.js.map +1 -1
  15. package/index.js +887 -184
  16. package/package.json +1 -1
  17. package/utils/agent-invoker.d.ts +31 -58
  18. package/utils/agent-invoker.d.ts.map +1 -1
  19. package/utils/agent-invoker.js +95 -186
  20. package/utils/agent-invoker.js.map +1 -1
  21. package/utils/autostart.d.ts +41 -0
  22. package/utils/autostart.d.ts.map +1 -0
  23. package/utils/autostart.js +122 -0
  24. package/utils/autostart.js.map +1 -0
  25. package/utils/key-run.d.ts +32 -0
  26. package/utils/key-run.d.ts.map +1 -0
  27. package/utils/key-run.js +77 -0
  28. package/utils/key-run.js.map +1 -0
  29. package/utils/loopback.d.ts +3 -3
  30. package/utils/loopback.d.ts.map +1 -1
  31. package/utils/loopback.js +3 -3
  32. package/utils/loopback.js.map +1 -1
  33. package/utils/mailbox-client.d.ts +21 -0
  34. package/utils/mailbox-client.d.ts.map +1 -1
  35. package/utils/mailbox-client.js +34 -0
  36. package/utils/mailbox-client.js.map +1 -1
  37. package/utils/mcp-config-writer.d.ts +2 -0
  38. package/utils/mcp-config-writer.d.ts.map +1 -1
  39. package/utils/mcp-config-writer.js +9 -1
  40. package/utils/mcp-config-writer.js.map +1 -1
  41. package/utils/repo-setup.d.ts +44 -0
  42. package/utils/repo-setup.d.ts.map +1 -0
  43. package/utils/repo-setup.js +178 -0
  44. package/utils/repo-setup.js.map +1 -0
  45. package/utils/run-workspace.d.ts +114 -0
  46. package/utils/run-workspace.d.ts.map +1 -0
  47. package/utils/run-workspace.js +264 -0
  48. package/utils/run-workspace.js.map +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mycompbox/compbox-cli",
3
- "version": "1.17.2",
3
+ "version": "1.19.0",
4
4
  "description": "CompBox CLI - Install, update, and manage Skill Packs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,7 +32,11 @@
32
32
  */
33
33
  import Anthropic from '@anthropic-ai/sdk';
34
34
  import { spawn } from 'node:child_process';
35
- import { type PxpipeRouting, type InterviewSeedPayload } from '@mycompbox/shared';
35
+ import { type PxpipeRouting, type InterviewSeedPayload, extractCompletionJson, looksLikeCompletionAttempt, meetsComplianceTarget, buildAuditFeedback, COMPLETION_REPAIR_INSTRUCTION, type ComplianceSummary, type EpicRequestDraft } from '@mycompbox/shared';
36
+ export { extractCompletionJson, looksLikeCompletionAttempt, meetsComplianceTarget, buildAuditFeedback, COMPLETION_REPAIR_INSTRUCTION, };
37
+ export type { ComplianceSummary, EpicRequestDraft };
38
+ import { type KeyRun } from './key-run.js';
39
+ import type { FinishResult, RunWorkspaceManager } from './run-workspace.js';
36
40
  import type { MailboxAttachment, MailboxClient, MailboxJob, MailboxJobTurn } from './mailbox-client.js';
37
41
  /**
38
42
  * pxpipe routing (ENG-136). Anthropic SDK options carrying the proxy `baseURL`
@@ -94,30 +98,6 @@ export interface InvokeResult {
94
98
  status: 'done' | 'failed';
95
99
  result: Record<string, unknown>;
96
100
  }
97
- export interface EpicRequestDraft {
98
- title: string;
99
- problemStatement: string;
100
- proposedSolution: string;
101
- impactAssessment: string;
102
- /**
103
- * Plain-text markdown description synthesized by the model from the whole
104
- * conversation — sent as the Epic Request's top-level `description` field
105
- * (distinct from structuredDesc). REQUIRED: compliance-validation's
106
- * ER_NO_DESCRIPTION rule (see .github/skills/internal/compliance-validation)
107
- * fails critical if this is empty, regardless of how good structuredDesc
108
- * is — verified live: an epic request created with only the 3 structured
109
- * fields populated (no description) scored 12/100, critical-fail, on
110
- * ER_NO_DESCRIPTION, ER_NO_PROBLEM_STATEMENT, and ER_NO_IMPACT_ASSESSMENT
111
- * (the latter two because a single terse summary sentence fell just under
112
- * the rubric's 50-word/30-word minimums — 47 and 25 words respectively).
113
- */
114
- description: string;
115
- successMetrics?: string;
116
- targetAudience?: string;
117
- alternatives?: string;
118
- dependencies?: string;
119
- estimatedEffort?: string;
120
- }
121
101
  /**
122
102
  * ENG-149 — parse a claimed job's `payload` as an interview-CONTINUATION seed
123
103
  * (the ENG-148 dispatch shape), or return null for a normal fresh-start job
@@ -132,15 +112,6 @@ export declare function parseSeedPayload(payload: unknown): InterviewSeedPayload
132
112
  * prompt for a continuation, otherwise the module-level fresh-start prompt.
133
113
  */
134
114
  export declare function resolveSystemPrompt(seed: InterviewSeedPayload | null): string;
135
- /** The server-computed compliance summary the daemon gates the interview on. */
136
- export interface ComplianceSummary {
137
- overall: number;
138
- tier: 'pass' | 'advisory' | 'critical-fail';
139
- issues: {
140
- message: string;
141
- tier: 'critical' | 'advisory';
142
- }[];
143
- }
144
115
  /**
145
116
  * Submit the current draft and read back the server-computed compliance score
146
117
  * (bug #2 — audit-aware finishing). Fresh interview: the FIRST submit
@@ -156,30 +127,6 @@ export declare function submitAndScore(ctx: {
156
127
  erId: string;
157
128
  compliance: ComplianceSummary | null;
158
129
  }>;
159
- /**
160
- * Whether the request is good enough to finish. Reaching the target tier
161
- * (`pass` = >=90 with no criticals) finishes early. A null score is fail-open:
162
- * the gate can only act on a score the server actually returned.
163
- */
164
- export declare function meetsComplianceTarget(compliance: ComplianceSummary | null): boolean;
165
- /**
166
- * Turn a failing compliance score into a corrective instruction: fix by
167
- * EXPANDING what's already known where possible, otherwise ask ONE question.
168
- */
169
- export declare function buildAuditFeedback(compliance: ComplianceSummary): string;
170
- /**
171
- * Extracts a completion draft from a model reply, tolerant of the common LLM
172
- * JSON defects (fenced/plain/bare, trailing commas, and — the big one — literal
173
- * newlines inside string values). Returns null only when no repair strategy
174
- * yields a VALID {@link isEpicRequestDraft} object.
175
- */
176
- export declare function extractCompletionJson(text: string): EpicRequestDraft | null;
177
- /**
178
- * True when a reply is clearly an ATTEMPT to finish (a JSON completion) even if
179
- * it failed to parse/validate — so the daemon can correct the model instead of
180
- * relaying raw JSON to the employee as if it were a question (which loops).
181
- */
182
- export declare function looksLikeCompletionAttempt(text: string): boolean;
183
130
  /**
184
131
  * Runs the epic_request interview end-to-end: converses with the user via
185
132
  * post-turn/next-job until it has a title + the 3 required structured-desc
@@ -226,6 +173,19 @@ export declare const PLANNER_RUN_TIMEOUT_MS: number;
226
173
  * connected" exactly while an agent was running.
227
174
  */
228
175
  export declare const PLANNER_HEARTBEAT_INTERVAL_MS: number;
176
+ /**
177
+ * Finishes a run made on the person's API key: reads Claude Code's JSON
178
+ * result, reports the run's usage to CompBox (best-effort), and returns the
179
+ * plain text the run would otherwise have printed.
180
+ */
181
+ export declare function takeKeyRunResult(ctx: {
182
+ mailboxClient: MailboxClient;
183
+ machineToken: string;
184
+ jobId: string;
185
+ }, keyRun: KeyRun, stdout: string): {
186
+ text: string;
187
+ reported: Promise<void>;
188
+ };
229
189
  /**
230
190
  * Build the `claude` argv for a headless planner run (H1). Pure + exported so a
231
191
  * unit test can assert the exact command shape: the `@planner` routing prompt,
@@ -280,6 +240,8 @@ export interface StartExecutionPlanCtx {
280
240
  epicRequestId: string;
281
241
  /** Resolved project repo dir (may be undefined/invalid — validated here). */
282
242
  projectDir: string | undefined;
243
+ /** Run on the person's saved API key instead of this computer's Claude sign-in. */
244
+ keyRun?: KeyRun;
283
245
  }
284
246
  export interface StartExecutionPlanDeps {
285
247
  /** Injectable child spawner (defaults to node's `spawn`) — tests never spawn a real `claude`. */
@@ -361,6 +323,8 @@ export interface StartExecutionOrchestrateCtx {
361
323
  epicId: string;
362
324
  /** Resolved project repo dir (may be undefined/invalid — validated here). */
363
325
  projectDir: string | undefined;
326
+ /** Run on the person's saved API key instead of this computer's Claude sign-in. */
327
+ keyRun?: KeyRun;
364
328
  }
365
329
  export interface StartExecutionOrchestrateDeps {
366
330
  /** Injectable child spawner (defaults to node's `spawn`) — tests never spawn a real `claude`. */
@@ -377,7 +341,16 @@ export interface StartExecutionOrchestrateDeps {
377
341
  heartbeat?: () => void | Promise<void>;
378
342
  heartbeatIntervalMs?: number;
379
343
  timeoutMs?: number;
344
+ /**
345
+ * Runs the orchestrator on its own new branch in a separate git worktree and
346
+ * finishes with a pushed branch + pull request (see run-workspace.ts). The
347
+ * daemon always passes one; when omitted the run happens directly in the
348
+ * project folder, as before.
349
+ */
350
+ workspace?: Pick<RunWorkspaceManager, 'prepare' | 'finish'>;
380
351
  }
352
+ /** Plain-language summary of where a run's code ended up. */
353
+ export declare function describeRunBranch(finish: FinishResult): string;
381
354
  /**
382
355
  * Turn a detected needs-review marker into the job result the web renders
383
356
  * (ENG-165-3). Names the halting gate (and its reason/phase/feature when the
@@ -1 +1 @@
1
- {"version":3,"file":"agent-invoker.d.ts","sourceRoot":"","sources":["../../src/utils/agent-invoker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-invoker.d.ts","sourceRoot":"","sources":["../../src/utils/agent-invoker.ts"],"names":[],"mappings":"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"}
@@ -35,9 +35,13 @@ import { spawn, spawnSync } from 'node:child_process';
35
35
  import { mkdirSync } from 'node:fs';
36
36
  import { homedir } from 'node:os';
37
37
  import { join } from 'node:path';
38
- import { resolveRouting, buildInterviewSystemPrompt, INTERVIEW_DEFINITION, } from '@mycompbox/shared';
38
+ import { resolveRouting, buildInterviewSystemPrompt, INTERVIEW_DEFINITION, extractCompletionJson, looksLikeCompletionAttempt, meetsComplianceTarget, buildAuditFeedback, COMPLETION_REPAIR_INSTRUCTION, } from '@mycompbox/shared';
39
+ // Completion parsing lives in @mycompbox/shared so CompBox's server-side
40
+ // (API-key) interview finishes the same way; re-exported for existing callers.
41
+ export { extractCompletionJson, looksLikeCompletionAttempt, meetsComplianceTarget, buildAuditFeedback, COMPLETION_REPAIR_INSTRUCTION, };
39
42
  import { claudeChildEnvPath, claudeNotFoundMessage, resolveClaudeBinary, } from './claude-runtime.js';
40
43
  import { isGitRepoDir } from './config-store.js';
44
+ import { keyRunArgs, keyRunEnv, parseClaudeJsonResult } from './key-run.js';
41
45
  // Fable is the model CompBox agents run on (ENG-E26): it is pxpipe's 100/100
42
46
  // image reader, so routing it through the proxy cuts input tokens without
43
47
  // quality loss. Opus/Sonnet are deliberately NOT used here — pxpipe passes
@@ -55,7 +59,6 @@ const MAX_INTERVIEW_TURNS = 18;
55
59
  // many re-submit/re-audit rounds we allow before finalizing with the best draft
56
60
  // (so we never loop forever, and never strand the request at a failing score).
57
61
  const MAX_QUALITY_ROUNDS = 4;
58
- const TARGET_COMPLIANCE_TIER = 'pass';
59
62
  const REPLY_POLL_INTERVAL_MS = 3_000;
60
63
  const REPLY_TIMEOUT_MS = 10 * 60 * 1_000; // 10 minutes
61
64
  /** Per-turn wall-clock budget for a single `claude -p` child process. */
@@ -287,182 +290,6 @@ export async function submitAndScore(ctx, currentErId, draft) {
287
290
  const created = await createPersonalEpicRequest(ctx.apiBaseUrl, ctx.userApiToken, draft);
288
291
  return { erId: created.id, compliance: created.compliance };
289
292
  }
290
- /**
291
- * Whether the request is good enough to finish. Reaching the target tier
292
- * (`pass` = >=90 with no criticals) finishes early. A null score is fail-open:
293
- * the gate can only act on a score the server actually returned.
294
- */
295
- export function meetsComplianceTarget(compliance) {
296
- return !compliance || compliance.tier === TARGET_COMPLIANCE_TIER;
297
- }
298
- /**
299
- * Turn a failing compliance score into a corrective instruction: fix by
300
- * EXPANDING what's already known where possible, otherwise ask ONE question.
301
- */
302
- export function buildAuditFeedback(compliance) {
303
- const ordered = [
304
- ...compliance.issues.filter((i) => i.tier === 'critical'),
305
- ...compliance.issues.filter((i) => i.tier === 'advisory'),
306
- ].slice(0, 8);
307
- const list = ordered.length > 0
308
- ? ordered.map((i) => `- ${i.message}`).join('\n')
309
- : '- (no specific issues returned; expand the required fields with more concrete detail)';
310
- return `The epic request you just produced scored ${String(compliance.overall)}/100 (tier: ${compliance.tier}). It must reach 90/100 with ZERO critical issues before the interview can finish. Fix these:
311
- ${list}
312
-
313
- If you can resolve these by EXPANDING what the employee already told you — a longer, more complete and concrete problemStatement / proposedSolution / impactAssessment / description that comfortably clear the length minimums — re-emit the completion JSON now with richer fields. Do NOT re-ask something you already have an answer for. Only if you genuinely need NEW information from the employee, ask ONE focused question to get it.`;
314
- }
315
- /** The optional draft keys, kept in one place so the guard and parser agree. */
316
- const OPTIONAL_DRAFT_KEYS = [
317
- 'successMetrics',
318
- 'targetAudience',
319
- 'alternatives',
320
- 'dependencies',
321
- 'estimatedEffort',
322
- ];
323
- /** Type guard validating the model's completion JSON has the required shape. */
324
- function isEpicRequestDraft(value) {
325
- if (typeof value !== 'object' || value === null)
326
- return false;
327
- const v = value;
328
- // 🔴 We deliberately do NOT require a `done: true` marker. Fable frequently
329
- // emits a complete, valid completion object WITHOUT `done` (verified against a
330
- // real stuck interview), which caused the daemon to reject a finished request
331
- // and relay the raw JSON back to the user forever. The completion signal is the
332
- // PRESENCE of all required fields as non-empty strings — a plain-text question
333
- // never carries these, so this is unambiguous.
334
- const nonEmpty = (x) => typeof x === 'string' && x.trim().length > 0;
335
- const requiredOk = nonEmpty(v.title) &&
336
- nonEmpty(v.problemStatement) &&
337
- nonEmpty(v.proposedSolution) &&
338
- nonEmpty(v.impactAssessment) &&
339
- nonEmpty(v.description);
340
- if (!requiredOk)
341
- return false;
342
- // Optional fields, when present, must be strings (a wrong-typed optional is a
343
- // malformed completion, not a silently-ignored one).
344
- for (const key of OPTIONAL_DRAFT_KEYS) {
345
- if (key in v && v[key] !== undefined && typeof v[key] !== 'string')
346
- return false;
347
- }
348
- return true;
349
- }
350
- /** Removes trailing commas before } or ] — a common LLM JSON defect. */
351
- function stripTrailingCommas(json) {
352
- return json.replace(/,(\s*[}\]])/g, '$1');
353
- }
354
- /**
355
- * Escapes raw control chars (newline/carriage-return/tab) that appear INSIDE a
356
- * JSON string literal — the single most common reason a model's completion JSON
357
- * fails `JSON.parse` (long multi-line description/problem fields written with
358
- * literal line breaks). Walks the text tracking string state so control chars
359
- * OUTSIDE strings (formatting whitespace) are left untouched.
360
- */
361
- function escapeRawControlCharsInStrings(json) {
362
- let out = '';
363
- let inString = false;
364
- let escaped = false;
365
- for (const ch of json) {
366
- if (escaped) {
367
- out += ch;
368
- escaped = false;
369
- continue;
370
- }
371
- if (ch === '\\') {
372
- out += ch;
373
- escaped = true;
374
- continue;
375
- }
376
- if (ch === '"') {
377
- inString = !inString;
378
- out += ch;
379
- continue;
380
- }
381
- if (inString && (ch === '\n' || ch === '\r' || ch === '\t')) {
382
- out += ch === '\n' ? '\\n' : ch === '\r' ? '\\r' : '\\t';
383
- continue;
384
- }
385
- out += ch;
386
- }
387
- return out;
388
- }
389
- /** Candidate JSON substrings to try, in priority order, from a model reply. */
390
- function completionJsonCandidates(text) {
391
- const candidates = [];
392
- // A fenced block (```json … ``` or a plain ``` … ``` fence), case-insensitive.
393
- const fenced = /```(?:json)?\s*([\s\S]*?)```/i.exec(text);
394
- if (fenced?.[1])
395
- candidates.push(fenced[1]);
396
- const trimmed = text.trim();
397
- if (trimmed.startsWith('{'))
398
- candidates.push(trimmed);
399
- // Last resort: the widest {...} span (handles prose wrapped around the JSON).
400
- const first = text.indexOf('{');
401
- const last = text.lastIndexOf('}');
402
- if (first !== -1 && last > first)
403
- candidates.push(text.slice(first, last + 1));
404
- return candidates;
405
- }
406
- /** Builds the draft from a validated parse (required fields + non-empty optionals). */
407
- function toDraft(parsed) {
408
- const draft = {
409
- title: parsed.title,
410
- problemStatement: parsed.problemStatement,
411
- proposedSolution: parsed.proposedSolution,
412
- impactAssessment: parsed.impactAssessment,
413
- description: parsed.description,
414
- };
415
- for (const key of OPTIONAL_DRAFT_KEYS) {
416
- const raw = parsed[key];
417
- if (typeof raw === 'string' && raw.trim().length > 0) {
418
- draft[key] = raw;
419
- }
420
- }
421
- return draft;
422
- }
423
- /**
424
- * Extracts a completion draft from a model reply, tolerant of the common LLM
425
- * JSON defects (fenced/plain/bare, trailing commas, and — the big one — literal
426
- * newlines inside string values). Returns null only when no repair strategy
427
- * yields a VALID {@link isEpicRequestDraft} object.
428
- */
429
- export function extractCompletionJson(text) {
430
- for (const candidate of completionJsonCandidates(text)) {
431
- for (const variant of [
432
- candidate,
433
- stripTrailingCommas(candidate),
434
- escapeRawControlCharsInStrings(candidate),
435
- escapeRawControlCharsInStrings(stripTrailingCommas(candidate)),
436
- ]) {
437
- try {
438
- const parsed = JSON.parse(variant);
439
- if (isEpicRequestDraft(parsed))
440
- return toDraft(parsed);
441
- }
442
- catch {
443
- // Try the next repair variant / candidate.
444
- }
445
- }
446
- }
447
- return null;
448
- }
449
- /**
450
- * True when a reply is clearly an ATTEMPT to finish (a JSON completion) even if
451
- * it failed to parse/validate — so the daemon can correct the model instead of
452
- * relaying raw JSON to the employee as if it were a question (which loops).
453
- */
454
- export function looksLikeCompletionAttempt(text) {
455
- // Must open a JSON object (fenced or bare) — note we do NOT require a closing
456
- // brace, so a truncated/malformed completion still counts.
457
- const hasObject = /```(?:json)?\s*\{/i.test(text) || text.trim().startsWith('{');
458
- if (!hasObject)
459
- return false;
460
- // ...carrying completion field names (or a `done` marker) — even if malformed
461
- // or missing `done`. A plain-text question never matches this.
462
- return /"(?:done|title|problemStatement|proposedSolution|impactAssessment|description)"\s*:/i.test(text);
463
- }
464
- /** Correction sent to the model when its completion JSON couldn't be used. */
465
- const COMPLETION_REPAIR_INSTRUCTION = 'Your last message looked like a completion but I could NOT parse it as a valid JSON object with all required fields. Re-emit ONLY a single valid JSON object inside a ```json code fence — no other text — with every string value properly escaped (escape any internal double-quotes as \\" and any line breaks as \\n), and make sure title, problemStatement, proposedSolution, impactAssessment, and description are all present and non-empty:\n```json\n{"done": true, "title": "...", "problemStatement": "...", "proposedSolution": "...", "impactAssessment": "...", "description": "..."}\n```';
466
293
  /** How many times to ask the model to fix a malformed completion before failing. */
467
294
  const MAX_JSON_REPAIRS = 3;
468
295
  function sleep(ms) {
@@ -922,6 +749,25 @@ export const PLANNER_HEARTBEAT_INTERVAL_MS = 20 * 1_000;
922
749
  const PLANNER_FINAL_TURN_MAX_CHARS = 4_000;
923
750
  /** The literal marker token the orchestrator emits (ENG-162); kept in sync for compatibility. */
924
751
  const ORCHESTRATOR_REVIEW_MARKER = 'COMPBOX_NEEDS_HUMAN_REVIEW';
752
+ /**
753
+ * Finishes a run made on the person's API key: reads Claude Code's JSON
754
+ * result, reports the run's usage to CompBox (best-effort), and returns the
755
+ * plain text the run would otherwise have printed.
756
+ */
757
+ export function takeKeyRunResult(ctx, keyRun, stdout) {
758
+ const parsed = parseClaudeJsonResult(stdout, keyRun.model);
759
+ if (!parsed)
760
+ return { text: stdout, reported: Promise.resolve() };
761
+ const reported = ctx.mailboxClient
762
+ .reportAiUsage(ctx.machineToken, ctx.jobId, parsed.usage)
763
+ .catch(() => undefined);
764
+ return { text: parsed.text, reported };
765
+ }
766
+ async function settleKeyRun(ctx, keyRun, stdout) {
767
+ const { text, reported } = takeKeyRunResult(ctx, keyRun, stdout);
768
+ await reported;
769
+ return text;
770
+ }
925
771
  /**
926
772
  * Build the `claude` argv for a headless planner run (H1). Pure + exported so a
927
773
  * unit test can assert the exact command shape: the `@planner` routing prompt,
@@ -1099,16 +945,17 @@ export async function invokeStartExecutionPlan(ctx, deps = {}) {
1099
945
  }
1100
946
  const routing = await resolveRoutingImpl();
1101
947
  const command = resolveClaudeBinary() ?? 'claude';
1102
- const args = buildPlannerArgs(ctx.epicRequestId);
948
+ const args = ctx.keyRun ? keyRunArgs(buildPlannerArgs(ctx.epicRequestId)) : buildPlannerArgs(ctx.epicRequestId);
1103
949
  // pxpipe (ENG-136): inject ANTHROPIC_BASE_URL when routed. COMPBOX_NONINTERACTIVE
1104
950
  // forces the planner's headless path even if the prompt flag were dropped. PATH
1105
951
  // is augmented (ENG-135) so `claude` and any helpers it spawns resolve.
1106
952
  const routedEnv = claudeSpawnEnv(process.env, routing);
1107
- const childEnv = {
953
+ const baseEnv = {
1108
954
  ...routedEnv,
1109
955
  COMPBOX_NONINTERACTIVE: '1',
1110
956
  PATH: claudeChildEnvPath(routedEnv.PATH),
1111
957
  };
958
+ const childEnv = ctx.keyRun ? keyRunEnv(baseEnv, ctx.keyRun) : baseEnv;
1112
959
  // H4: relay a START turn immediately so the web shows the run began even if the
1113
960
  // planner produces no stdout for minutes. postTurn failures are swallowed —
1114
961
  // streaming is best-effort and must never fail the job.
@@ -1171,6 +1018,8 @@ export async function invokeStartExecutionPlan(ctx, deps = {}) {
1171
1018
  });
1172
1019
  child.on('close', (code) => {
1173
1020
  void (async () => {
1021
+ if (ctx.keyRun)
1022
+ stdout = await settleKeyRun(ctx, ctx.keyRun, stdout);
1174
1023
  const combined = `${stdout}\n${stderr}`;
1175
1024
  // A needs-review outcome wins regardless of exit code: the planner may
1176
1025
  // exit 0 after emitting the marker. Surface it to the web (not silent).
@@ -1356,6 +1205,18 @@ export function defaultToolchainPresent(_dir) {
1356
1205
  }
1357
1206
  return false;
1358
1207
  }
1208
+ /** Plain-language summary of where a run's code ended up. */
1209
+ export function describeRunBranch(finish) {
1210
+ if (finish.prUrl)
1211
+ return `Pull request: ${finish.prUrl}`;
1212
+ if (finish.compareUrl)
1213
+ return `Branch ${finish.branch} was pushed. Open the pull request here: ${finish.compareUrl}`;
1214
+ if (finish.commits === 0)
1215
+ return 'No code changes were made.';
1216
+ if (finish.pushed)
1217
+ return `Branch ${finish.branch} was pushed.`;
1218
+ return `The work is on branch ${finish.branch}${finish.keptWorktree ? ` (in ${finish.keptWorktree})` : ''}.`;
1219
+ }
1359
1220
  /** Read a string field off a parsed marker object, or undefined. */
1360
1221
  function markerString(marker, key) {
1361
1222
  const v = marker?.[key];
@@ -1427,23 +1288,39 @@ export async function invokeStartExecutionOrchestrate(ctx, deps = {}) {
1427
1288
  if (!toolchainPresent(projectDir)) {
1428
1289
  return { status: 'failed', result: { error: toolchainMissingMessage(projectDir) } };
1429
1290
  }
1291
+ // Each run gets its own branch + worktree, so it never touches the person's
1292
+ // checkout and always ends as a reviewable pull request.
1293
+ let workspace = null;
1294
+ let workspaceNotes = [];
1295
+ if (deps.workspace) {
1296
+ const prepared = await deps.workspace.prepare(projectDir, ctx.epicId);
1297
+ if (!prepared.ok) {
1298
+ return { status: 'failed', result: { error: prepared.error } };
1299
+ }
1300
+ workspace = prepared.workspace;
1301
+ workspaceNotes = prepared.notes;
1302
+ }
1303
+ const runDir = workspace?.dir ?? projectDir;
1430
1304
  const routing = await resolveRoutingImpl();
1431
1305
  const command = resolveClaudeBinary() ?? 'claude';
1432
- const args = buildOrchestratorArgs(ctx.epicId);
1306
+ const args = ctx.keyRun ? keyRunArgs(buildOrchestratorArgs(ctx.epicId)) : buildOrchestratorArgs(ctx.epicId);
1433
1307
  // pxpipe (ENG-136): inject ANTHROPIC_BASE_URL when routed. COMPBOX_NONINTERACTIVE
1434
1308
  // forces the orchestrator's headless serial-in-process path (H3) even if the
1435
1309
  // prompt flag were dropped. PATH is augmented (ENG-135) so `claude` and any
1436
1310
  // helpers it spawns resolve on a launchd/GUI-spawned daemon.
1437
1311
  const routedEnv = claudeSpawnEnv(process.env, routing);
1438
- const childEnv = {
1312
+ const baseEnv = {
1439
1313
  ...routedEnv,
1440
1314
  COMPBOX_NONINTERACTIVE: '1',
1441
1315
  PATH: claudeChildEnvPath(routedEnv.PATH),
1442
1316
  };
1317
+ const childEnv = ctx.keyRun ? keyRunEnv(baseEnv, ctx.keyRun) : baseEnv;
1443
1318
  // H4: relay a START turn immediately so the web shows the run began even if the
1444
1319
  // orchestrator produces no stdout for minutes. postTurn failures are swallowed.
1445
1320
  try {
1446
- await ctx.mailboxClient.postTurn(ctx.machineToken, ctx.jobId, `Started the orchestrator for epic ${ctx.epicId} (headless, non-interactive). This executes the full epic — every phase and feature — and may take a long time.`);
1321
+ await ctx.mailboxClient.postTurn(ctx.machineToken, ctx.jobId, `Started the orchestrator for epic ${ctx.epicId} (headless, non-interactive). This executes the full epic — every phase and feature — and may take a long time.` +
1322
+ (workspace ? ` Working on a new branch, ${workspace.branch}, which will end as a pull request into ${workspace.baseBranch}.` : '') +
1323
+ (workspaceNotes.length > 0 ? ` Note: ${workspaceNotes.join(' ')}` : ''));
1447
1324
  }
1448
1325
  catch {
1449
1326
  // Best-effort start turn.
@@ -1453,7 +1330,7 @@ export async function invokeStartExecutionOrchestrate(ctx, deps = {}) {
1453
1330
  let stderr = '';
1454
1331
  let settled = false;
1455
1332
  const child = spawnImpl(command, args, {
1456
- cwd: projectDir,
1333
+ cwd: runDir,
1457
1334
  stdio: ['ignore', 'pipe', 'pipe'],
1458
1335
  env: childEnv,
1459
1336
  });
@@ -1502,6 +1379,9 @@ export async function invokeStartExecutionOrchestrate(ctx, deps = {}) {
1502
1379
  });
1503
1380
  });
1504
1381
  child.on('close', (code) => {
1382
+ // Usage is reported in the background; the run's outcome doesn't wait on it.
1383
+ if (ctx.keyRun)
1384
+ stdout = takeKeyRunResult(ctx, ctx.keyRun, stdout).text;
1505
1385
  const combined = `${stdout}\n${stderr}`;
1506
1386
  // ENG-165-3: a HARD-gate needs-review outcome wins regardless of exit code
1507
1387
  // — the orchestrator emits the marker and STOPs, and may exit 0. Surface
@@ -1529,11 +1409,40 @@ export async function invokeStartExecutionOrchestrate(ctx, deps = {}) {
1529
1409
  finish({ status: 'done', result: { epicId: ctx.epicId } });
1530
1410
  });
1531
1411
  });
1412
+ // Commit what's left, push the branch and open the pull request (draft when
1413
+ // the run stopped early), then attach where the code is to the job result.
1414
+ let branchLine = '';
1415
+ if (deps.workspace && workspace) {
1416
+ const resultError = outcome.result;
1417
+ const stopReason = typeof resultError.message === 'string'
1418
+ ? resultError.message
1419
+ : typeof resultError.error === 'string'
1420
+ ? resultError.error.split('\n')[0]
1421
+ : undefined;
1422
+ const finished = await deps.workspace.finish({
1423
+ workspace,
1424
+ epicId: ctx.epicId,
1425
+ jobId: ctx.jobId,
1426
+ completed: outcome.status === 'done',
1427
+ ...(stopReason ? { stopReason } : {}),
1428
+ });
1429
+ branchLine = describeRunBranch(finished);
1430
+ outcome.result = {
1431
+ ...outcome.result,
1432
+ branch: finished.branch,
1433
+ commits: finished.commits,
1434
+ pushed: finished.pushed,
1435
+ ...(finished.prUrl ? { prUrl: finished.prUrl } : {}),
1436
+ ...(finished.compareUrl ? { compareUrl: finished.compareUrl } : {}),
1437
+ ...(finished.notes.length > 0 ? { branchNotes: finished.notes } : {}),
1438
+ };
1439
+ }
1532
1440
  // H4: relay a FINAL turn summarizing the outcome (best-effort).
1533
1441
  try {
1534
- const summary = outcome.status === 'done'
1442
+ const summary = (outcome.status === 'done'
1535
1443
  ? `Execution complete — epic ${ctx.epicId} finished all phases.`
1536
- : `Orchestrator run ended: ${JSON.stringify(outcome.result).slice(0, ORCHESTRATOR_FINAL_TURN_MAX_CHARS)}`;
1444
+ : `Orchestrator run ended: ${JSON.stringify(outcome.result).slice(0, ORCHESTRATOR_FINAL_TURN_MAX_CHARS)}`) +
1445
+ (branchLine ? `\n${branchLine}` : '');
1537
1446
  await ctx.mailboxClient.postTurn(ctx.machineToken, ctx.jobId, summary);
1538
1447
  }
1539
1448
  catch {