@fitlab-ai/agent-infra 0.9.15 → 0.10.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 (184) hide show
  1. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +6 -2
  2. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
  3. package/dist/lib/fs/durable-write.js +2 -0
  4. package/dist/lib/internal/cli-route-inventory.js +1 -1
  5. package/dist/lib/internal/manual-validation.js +17 -8
  6. package/dist/lib/internal/task-artifact.js +1 -1
  7. package/dist/lib/internal/task-lifecycle.js +2 -2
  8. package/dist/lib/internal/task-operation-registry.js +0 -1
  9. package/dist/lib/internal/task-review.js +4 -4
  10. package/dist/lib/platform/issue-comments.js +16 -10
  11. package/dist/lib/platform/issues.js +1 -1
  12. package/dist/lib/platform/pr-change-report.js +5 -4
  13. package/dist/lib/platform/pr-summary.js +64 -32
  14. package/dist/lib/platform/pull-requests.js +3 -3
  15. package/dist/lib/platform/task-identities.js +2 -9
  16. package/dist/lib/platform/verification-sync.js +6 -2
  17. package/dist/lib/pr-review/evidence-grading.js +4 -8
  18. package/dist/lib/sandbox/commands/rm.js +21 -2
  19. package/dist/lib/sandbox/control/server.js +29 -9
  20. package/dist/lib/sandbox/control/state.js +7 -1
  21. package/dist/lib/sandbox/control/workflow-executor.js +4 -14
  22. package/dist/lib/sandbox/index.js +1 -2
  23. package/dist/lib/sandbox/removal.js +116 -1011
  24. package/dist/lib/sandbox/workspace-identity.js +17 -1
  25. package/dist/lib/task/artifact-command.js +6 -28
  26. package/dist/lib/task/artifact-operations.js +16 -230
  27. package/dist/lib/task/artifact-recovery.js +430 -0
  28. package/dist/lib/task/artifact-repair-intent.js +73 -39
  29. package/dist/lib/task/commit-operation.js +31 -4
  30. package/dist/lib/task/control-authority.js +38 -7
  31. package/dist/lib/task/control-recovery.js +14 -2
  32. package/dist/lib/task/create-service.js +1 -8
  33. package/dist/lib/task/delegation-receipts.js +64 -3
  34. package/dist/lib/task/events.js +164 -22
  35. package/dist/lib/task/human-override.js +5 -8
  36. package/dist/lib/task/intermediate-cleanup.js +66 -8
  37. package/dist/lib/task/lifecycle-recovery.js +549 -0
  38. package/dist/lib/task/lifecycle.js +12 -3
  39. package/dist/lib/task/local-artifact-finalization.js +148 -123
  40. package/dist/lib/task/manual-validation-receipt.js +5 -1
  41. package/dist/lib/task/orchestration.js +52 -2
  42. package/dist/lib/task/pr-delivery-fact.js +12 -56
  43. package/dist/lib/task/recovery-warning.js +19 -0
  44. package/dist/lib/task/review-command.js +1 -0
  45. package/dist/lib/task/review-finalization.js +104 -77
  46. package/dist/lib/task/short-id.js +15 -7
  47. package/dist/lib/task/task-execution-lock.js +1 -1
  48. package/dist/lib/task/verification-engine.js +3 -14
  49. package/dist/lib/task/workflow-command.js +0 -1
  50. package/dist/lib/version.js +1 -11
  51. package/dist/lifecycle-build-manifest.json +3 -3
  52. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +7 -2
  53. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
  54. package/lib/fs/durable-write.ts +1 -0
  55. package/lib/internal/cli-route-inventory.ts +1 -1
  56. package/lib/internal/manual-validation.ts +17 -8
  57. package/lib/internal/task-artifact.ts +1 -1
  58. package/lib/internal/task-lifecycle.ts +4 -3
  59. package/lib/internal/task-operation-registry.ts +0 -1
  60. package/lib/internal/task-review.ts +4 -4
  61. package/lib/platform/issue-comments.ts +18 -10
  62. package/lib/platform/issues.ts +1 -1
  63. package/lib/platform/pr-change-report.ts +7 -5
  64. package/lib/platform/pr-summary.ts +64 -32
  65. package/lib/platform/pull-requests.ts +3 -3
  66. package/lib/platform/task-identities.ts +3 -14
  67. package/lib/platform/verification-sync.ts +6 -2
  68. package/lib/pr-review/evidence-grading.ts +4 -9
  69. package/lib/sandbox/commands/rm.ts +34 -2
  70. package/lib/sandbox/control/protocol.ts +5 -0
  71. package/lib/sandbox/control/server.ts +30 -10
  72. package/lib/sandbox/control/state.ts +9 -1
  73. package/lib/sandbox/control/workflow-executor.ts +4 -14
  74. package/lib/sandbox/index.ts +1 -2
  75. package/lib/sandbox/removal.ts +126 -1128
  76. package/lib/sandbox/workspace-identity.ts +19 -3
  77. package/lib/task/artifact-command.ts +8 -27
  78. package/lib/task/artifact-operations.ts +21 -251
  79. package/lib/task/artifact-recovery.ts +519 -0
  80. package/lib/task/artifact-repair-intent.ts +99 -51
  81. package/lib/task/commit-operation.ts +35 -4
  82. package/lib/task/control-authority.ts +47 -14
  83. package/lib/task/control-recovery.ts +13 -1
  84. package/lib/task/create-service.ts +1 -10
  85. package/lib/task/delegation-receipts.ts +73 -2
  86. package/lib/task/events.ts +187 -23
  87. package/lib/task/human-override.ts +5 -8
  88. package/lib/task/intermediate-cleanup.ts +50 -12
  89. package/lib/task/lifecycle-recovery.ts +691 -0
  90. package/lib/task/lifecycle.ts +13 -4
  91. package/lib/task/local-artifact-finalization.ts +176 -132
  92. package/lib/task/manual-validation-receipt.ts +5 -1
  93. package/lib/task/orchestration.ts +65 -0
  94. package/lib/task/pr-delivery-fact.ts +15 -62
  95. package/lib/task/recovery-warning.ts +21 -0
  96. package/lib/task/review-command.ts +1 -0
  97. package/lib/task/review-finalization.ts +106 -87
  98. package/lib/task/short-id.ts +27 -5
  99. package/lib/task/task-execution-lock.ts +1 -1
  100. package/lib/task/verification-engine.ts +3 -17
  101. package/lib/task/verification-types.ts +0 -2
  102. package/lib/task/workflow-command.ts +0 -1
  103. package/lib/version.ts +1 -15
  104. package/package.json +1 -1
  105. package/templates/.agents/rules/create-issue.en.md +1 -1
  106. package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
  107. package/templates/.agents/rules/issue-sync.en.md +4 -2
  108. package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
  109. package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
  110. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
  111. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  112. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  113. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
  114. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
  115. package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
  116. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  117. package/templates/.agents/skills/block-task/config/verify.json +1 -1
  118. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  119. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  120. package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
  121. package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
  122. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
  123. package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
  124. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
  125. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  126. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  127. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
  128. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
  129. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  130. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  131. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  132. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  133. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  134. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  135. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
  136. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
  137. package/templates/.agents/skills/create-pr/config/verify.json +1 -1
  138. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
  139. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
  140. package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
  141. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
  142. package/templates/.agents/skills/create-task/config/verify.json +1 -1
  143. package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
  144. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
  145. package/templates/.agents/skills/import-issue/config/verify.json +2 -2
  146. package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
  147. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
  148. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  149. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  150. package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
  151. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
  152. package/templates/.agents/skills/restore-task/config/verify.json +1 -1
  153. package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
  154. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  155. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
  156. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
  157. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
  158. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
  159. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  160. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  161. package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
  162. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
  163. package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
  164. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
  165. package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
  166. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  167. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
  168. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
  169. package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
  170. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
  171. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
  172. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
  173. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
  174. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
  175. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
  176. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
  177. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  178. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  179. package/templates/.agents/templates/task.en.md +1 -1
  180. package/templates/.agents/templates/task.zh-CN.md +1 -1
  181. package/templates/.claude/commands/import-issue.en.md +1 -1
  182. package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
  183. package/templates/.claude/commands/restore-task.en.md +1 -1
  184. package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
@@ -2,12 +2,41 @@ import { parseArgs } from 'node:util';
2
2
  import { loadConfig } from '../config.ts';
3
3
  import { createSandboxCapabilityPlan } from '../agent-client-reconciler.ts';
4
4
  import { resolveSandboxCleanupTarget } from '../workspace-identity.ts';
5
+ import { detectEngine } from '../engine.ts';
6
+ import {
7
+ sandboxBranchLabel,
8
+ sandboxLabel,
9
+ sandboxTaskIdLabel,
10
+ sandboxWorkspaceModeLabel
11
+ } from '../constants.ts';
12
+ import { fetchSandboxRows } from './list-running.ts';
5
13
  import { rmOne, rmPurge, rmUnbound } from '../removal.ts';
6
14
 
7
15
  const USAGE = `Usage:
8
16
  ai sandbox rm <branch | TASK-id | short id> Remove one sandbox; use a full TASK-id for a task-bound sandbox and a branch for branch-only sandboxes
9
- ai sandbox rm --unbound [--dry-run] [--yes] Remove completed task-bound and branch-only sandboxes and verified auxiliary state; active, blocked, and archive tasks are protected
17
+ ai sandbox rm --unbound [--dry-run] [--yes] Remove discovered task-bound and branch-only sandboxes
10
18
  ai sandbox rm --purge Tear down ALL sandboxes for the project (containers, worktrees, image, VM)`;
19
+
20
+ function resolveMissingTaskSandboxBranch(taskId: string, config: ReturnType<typeof loadConfig>): string | null {
21
+ const engine = detectEngine(config);
22
+ const listed = fetchSandboxRows(
23
+ engine,
24
+ sandboxLabel(config),
25
+ sandboxBranchLabel(config),
26
+ { mode: sandboxWorkspaceModeLabel(config), taskId: sandboxTaskIdLabel(config) }
27
+ );
28
+ const branches = new Set(
29
+ [...listed.running, ...listed.nonRunning]
30
+ .filter((row) => row.workspaceMode === 'task-bound' && row.taskId === taskId && row.branch)
31
+ .map((row) => row.branch)
32
+ );
33
+ if (branches.size === 0) return null;
34
+ if (branches.size > 1) {
35
+ throw new Error(`SANDBOX_CLEANUP_TASK_ID_AMBIGUOUS: task ${taskId} matches multiple sandbox branches`);
36
+ }
37
+ return branches.values().next().value ?? null;
38
+ }
39
+
11
40
  export async function rm(args: string[]): Promise<void> {
12
41
  const { values, positionals } = parseArgs({
13
42
  args,
@@ -64,6 +93,9 @@ export async function rm(args: string[]): Promise<void> {
64
93
  return;
65
94
  }
66
95
 
67
- const cleanupTarget = resolveSandboxCleanupTarget(positionals[0] ?? '', config.repoRoot);
96
+ const cleanupTarget = resolveSandboxCleanupTarget(positionals[0] ?? '', config.repoRoot, {
97
+ allowProtected: true,
98
+ resolveMissingTask: (taskId) => resolveMissingTaskSandboxBranch(taskId, config)
99
+ });
68
100
  await rmOne(config, tools, cleanupTarget.branch, { cleanupTarget });
69
101
  }
@@ -131,6 +131,11 @@ export type SandboxControlResponse = Readonly<{
131
131
  outputState?: 'available' | 'unavailable';
132
132
  payload?: SandboxControlPayloadReference | null;
133
133
  }>;
134
+ export type SandboxControlRecoveryWarning = Readonly<{
135
+ code: string;
136
+ message: string;
137
+ action: string;
138
+ }>;
134
139
  export type SandboxControlStatus = Readonly<{
135
140
  version: 3; generation: string; broker: ProcessIdentity & { brokerId: string };
136
141
  state: 'starting' | 'healthy' | 'busy' | 'parked'; reasonCode: string | null;
@@ -12,6 +12,7 @@ import {
12
12
  SANDBOX_CONTROL_MAX_RESPONSE_BYTES,
13
13
  SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES,
14
14
  type SandboxControlManifest,
15
+ type SandboxControlRecoveryWarning,
15
16
  type SandboxControlRequest,
16
17
  type SandboxControlResponse,
17
18
  type SandboxControlTimingPolicy
@@ -62,6 +63,7 @@ import {
62
63
  SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES
63
64
  } from '../../task/control-recovery.ts';
64
65
  import { readRun } from '../../task/orchestration.ts';
66
+ import { readLifecycleRecoveryDomainEvidence } from '../../task/lifecycle-recovery.ts';
65
67
  import { captureRepositorySnapshot } from '../../task/workspace-snapshot.ts';
66
68
  import { parseTypedTaskFrontmatter } from '../../task/frontmatter.ts';
67
69
  import { readLifecycleJournalEvidence } from '../../task/lifecycle.ts';
@@ -368,11 +370,12 @@ function outputMatchesEvidence(output: string, bytes: number, sha256: string): b
368
370
  && createHash('sha256').update(output, 'utf8').digest('hex') === sha256;
369
371
  }
370
372
 
371
- function genericRecoveryResponse(
373
+ export function genericRecoveryResponse(
372
374
  request: SandboxControlRequest,
373
375
  exitCode: number,
374
376
  payload: ReturnType<typeof readSandboxControlPayload> | null,
375
- cause: 'publish' | 'recovery' = 'recovery'
377
+ cause: 'publish' | 'recovery' = 'recovery',
378
+ warning: SandboxControlRecoveryWarning | null = null
376
379
  ): SandboxControlResponse {
377
380
  const outputUnavailable = request.family === 'task-create' && !payload;
378
381
  return {
@@ -381,9 +384,11 @@ function genericRecoveryResponse(
381
384
  phase: 'completed',
382
385
  exitCode,
383
386
  stdout: outputUnavailable ? `${JSON.stringify(taskCreateOutputUnavailableResult(request.id))}\n` : '',
384
- stderr: payload ? '' : cause === 'publish'
385
- ? 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n'
386
- : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
387
+ stderr: payload ? '' : warning
388
+ ? `${warning.code}: ${warning.message}\nAction: ${warning.action}\n`
389
+ : cause === 'publish'
390
+ ? 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n'
391
+ : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
387
392
  error: null,
388
393
  outputState: payload ? 'available' : 'unavailable',
389
394
  payload: payload ? payloadReference(payload) : null
@@ -504,6 +509,21 @@ function readRecoveryDomain(
504
509
  }
505
510
 
506
511
  if (operation.family === 'task-lifecycle') {
512
+ if (operation.intent === 'recover-started') {
513
+ try {
514
+ if (!('args' in request)) return { domain: null };
515
+ const parsed = parseTaskControlOperation('task-lifecycle', request.args);
516
+ if (parsed.family !== 'task-lifecycle' || parsed.request.intent !== 'recover-started') {
517
+ return { domain: null };
518
+ }
519
+ return {
520
+ domain: readLifecycleRecoveryDomainEvidence(manifest.repoRoot, parsed.request, terminalResult),
521
+ journal: readLifecycleJournalEvidence(manifest.repoRoot, taskRef!)
522
+ };
523
+ } catch {
524
+ return { domain: null };
525
+ }
526
+ }
507
527
  if (!taskRef || !terminalResult.targetState) return { domain: null };
508
528
  const journal = readLifecycleJournalEvidence(manifest.repoRoot, taskRef);
509
529
  try {
@@ -577,7 +597,7 @@ function readCommittedCriticalPhases(manifest: SandboxControlManifest, requestId
577
597
  });
578
598
  }
579
599
 
580
- function recoveryResponse(
600
+ export function recoveryResponse(
581
601
  manifest: SandboxControlManifest,
582
602
  manifestPath: string,
583
603
  request: SandboxControlRequest,
@@ -637,7 +657,7 @@ function recoveryResponse(
637
657
  if (finalization?.status !== 'matched') return null;
638
658
  return finalization.response ?? null;
639
659
  }
640
- return genericRecoveryResponse(request, evidence.exitCode, payload);
660
+ return genericRecoveryResponse(request, evidence.exitCode, payload, 'recovery', terminalResult.warning ?? null);
641
661
  }
642
662
 
643
663
  function terminalMatchesEvidence(
@@ -681,7 +701,7 @@ function terminalMatchesEvidence(
681
701
  if (response.outputState === 'unavailable') {
682
702
  const causes = ['recovery', 'publish'] as const;
683
703
  const valid = causes.some((cause) => JSON.stringify(response)
684
- === JSON.stringify(genericRecoveryResponse(request, evidence.exitCode, null, cause)));
704
+ === JSON.stringify(genericRecoveryResponse(request, evidence.exitCode, null, cause, terminalResult?.warning ?? null)));
685
705
  return { valid, payloadReferenced: false };
686
706
  }
687
707
  return {
@@ -700,7 +720,7 @@ function publishExecutionResult(
700
720
  brokerOwns: () => boolean
701
721
  ): boolean {
702
722
  const normalized = sanitizeSandboxControlResult(manifest, result);
703
- writeSandboxControlTerminalResult(manifest, {
723
+ const terminalResult = writeSandboxControlTerminalResult(manifest, {
704
724
  id: request.id,
705
725
  family: request.family,
706
726
  operation: operationKey(request, normalized.stdout)
@@ -737,7 +757,7 @@ function publishExecutionResult(
737
757
  } catch {
738
758
  payload = null;
739
759
  }
740
- terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish');
760
+ terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish', terminalResult.warning ?? null);
741
761
  }
742
762
  }
743
763
  if (!brokerOwns()) return false;
@@ -1,5 +1,6 @@
1
1
  import { isCompletionEvidence, type CleanCompletionEvidence } from '../../task/orchestration.ts';
2
2
  import { parseControlOutput } from '../../task/control-recovery.ts';
3
+ import { isRecoveryWarning } from '../../task/recovery-warning.ts';
3
4
  import { execFileSync } from 'node:child_process';
4
5
  import fs from 'node:fs';
5
6
  import path from 'node:path';
@@ -14,6 +15,7 @@ import type {
14
15
  SandboxControlPayload,
15
16
  SandboxControlReservation,
16
17
  SandboxControlResultEvidence,
18
+ SandboxControlRecoveryWarning,
17
19
  SandboxControlStatus
18
20
  } from './protocol.ts';
19
21
  import {
@@ -113,6 +115,7 @@ export type SandboxControlTerminalResult = Readonly<{
113
115
  status: string;
114
116
  changed: boolean | null;
115
117
  completionEvidence: CleanCompletionEvidence | null;
118
+ warning?: SandboxControlRecoveryWarning | null;
116
119
  }>;
117
120
 
118
121
  export function terminalResultPath(manifest: SandboxControlManifest, requestId: string): string {
@@ -130,6 +133,7 @@ export function createSandboxControlTerminalResult(
130
133
  ? nested.run as Record<string, unknown> : null;
131
134
  const rawCompletion = nested.completionEvidence ?? run?.completionEvidence;
132
135
  const operation = request.operation ?? (typeof nested.intent === 'string' ? nested.intent : null) ?? '';
136
+ const warning = isRecoveryWarning(nested.warning) ? nested.warning : null;
133
137
  return {
134
138
  version: 1,
135
139
  requestId: request.id,
@@ -139,7 +143,8 @@ export function createSandboxControlTerminalResult(
139
143
  targetState: typeof nested.targetState === 'string' ? nested.targetState : null,
140
144
  status: typeof nested.status === 'string' ? nested.status : 'completed',
141
145
  changed: typeof nested.changed === 'boolean' ? nested.changed : null,
142
- completionEvidence: isCompletionEvidence(rawCompletion) ? rawCompletion : null
146
+ completionEvidence: isCompletionEvidence(rawCompletion) ? rawCompletion : null,
147
+ ...(warning ? { warning } : {})
143
148
  };
144
149
  }
145
150
 
@@ -173,6 +178,9 @@ export function readSandboxControlTerminalResult(filePath: string): SandboxContr
173
178
  if (value.completionEvidence !== null && !isCompletionEvidence(value.completionEvidence)) {
174
179
  throw new Error('SANDBOX_CONTROL_TERMINAL_RESULT_INVALID');
175
180
  }
181
+ if (value.warning !== undefined && value.warning !== null && !isRecoveryWarning(value.warning)) {
182
+ throw new Error('SANDBOX_CONTROL_TERMINAL_RESULT_INVALID');
183
+ }
176
184
  if (value.changed === undefined) {
177
185
  throw new Error('SANDBOX_CONTROL_TERMINAL_RESULT_INVALID');
178
186
  }
@@ -9,7 +9,7 @@ import { dispatchHostControlCommand } from '../../host-control/command.ts';
9
9
  import { assertSandboxTaskSource } from '../workspace-view.ts';
10
10
  import { appendDiagnosticAudit } from './audit.ts';
11
11
  import {
12
- readTaskArtifact, writeTaskArtifact, type TaskWorkflowRequest
12
+ readTaskArtifact, type TaskWorkflowRequest
13
13
  } from './task-workflow.ts';
14
14
  import type { SandboxControlManifest } from './protocol.ts';
15
15
  import type { SandboxControlExecutionResult } from './executor.ts';
@@ -55,7 +55,7 @@ export async function executeTaskWorkflow(
55
55
  if (command === 'task-artifact' && 'operation' in input && input.operation === 'inspect') {
56
56
  return executionResult(executeArtifactCommand(input, { repoRoot: manifest.repoRoot }));
57
57
  }
58
- if ('operation' in input && (input.operation === 'init' || input.operation === 'repair')) {
58
+ if ('operation' in input && input.operation === 'init') {
59
59
  return executionResult(executeArtifactCommand(input, { repoRoot: manifest.repoRoot, artifactDir: taskDir }));
60
60
  }
61
61
  return await withTaskExecutionLock(manifest.repoRoot, request.taskId, `sandbox-control.${request.operation}`, async () => {
@@ -65,27 +65,17 @@ export async function executeTaskWorkflow(
65
65
  if ('operation' in input) {
66
66
  const { family } = input;
67
67
  if (family !== 'analysis' && family !== 'plan' && family !== 'code') throw new Error('ARTIFACT_IDENTITY_INVALID');
68
- const local = { taskRef: request.taskId, family, artifact: input.artifact, repoRoot: manifest.repoRoot } as const;
68
+ const local = { taskRef: request.taskId, family, artifact: input.artifact, repoRoot: manifest.repoRoot, recoveryId: input.recoveryId, lockAlreadyHeld: true } as const;
69
69
  const prepared = prepareLocalArtifact(local, content, lifecycleRecoveryAttestation ?? undefined);
70
70
  if (prepared.result.status === 'failed') return executionResult(commitLocalArtifactProvenance(prepared));
71
71
  publicationStarted = true;
72
- await writeTaskArtifact(taskDir, {
73
- artifact: artifact.artifact,
74
- bytes: artifact.bytes,
75
- expectedSha256: artifact.sha256
76
- });
77
72
  result = commitLocalArtifactProvenance(prepared);
78
73
  } else {
79
74
  if (input.overrideTicket) throw new Error('TASK_WORKFLOW_OVERRIDE_UNSUPPORTED');
80
- const prepared = prepareReviewSummaryCandidate(input, content, { repoRoot: manifest.repoRoot });
75
+ const prepared = prepareReviewSummaryCandidate(input, content, { repoRoot: manifest.repoRoot, lockAlreadyHeld: true, startRecovery: true });
81
76
  if (input.dryRun || prepared.result.status === 'planned') return executionResult(prepared.result);
82
77
  if (prepared.result.status === 'failed') return executionResult(commitReviewSummaryProvenance(prepared, manifest.repoRoot));
83
78
  publicationStarted = true;
84
- await writeTaskArtifact(taskDir, {
85
- artifact: input.artifact,
86
- bytes: Buffer.from(prepared.content, 'utf8'),
87
- expectedSha256: artifact.sha256
88
- });
89
79
  result = commitReviewSummaryProvenance(prepared, manifest.repoRoot);
90
80
  }
91
81
  if (result.status !== 'failed') appendDiagnosticAudit(manifest, 'task-workflow-artifact-finalized', {
@@ -20,8 +20,7 @@ Commands:
20
20
  rm <branch | TASK-id> | --unbound | --purge
21
21
  Remove one sandbox; use a full TASK-id for task-bound
22
22
  cleanup and a branch for branch-only cleanup. --unbound
23
- removes completed task-bound and branch-only sandboxes
24
- plus verified auxiliary state; active, blocked, and archive tasks are protected.
23
+ removes discovered task-bound and branch-only sandboxes.
25
24
  start [--recreate] <branch | TASK-id | N>
26
25
  Verify or recover an existing sandbox container;
27
26
  optionally replace only the container on failure