@fitlab-ai/agent-infra 0.9.14 → 0.9.15

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 (250) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/internal-cli.sh +45 -0
  4. package/dist/bin/internal-cli.js +93 -30
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
  7. package/dist/lib/defaults.json +103 -103
  8. package/dist/lib/fs/durable-write.js +37 -0
  9. package/dist/lib/fs/file-lock.js +111 -0
  10. package/dist/lib/host-control/audit.js +19 -0
  11. package/dist/lib/host-control/client.js +142 -0
  12. package/dist/lib/host-control/command.js +49 -0
  13. package/dist/lib/host-control/path.js +160 -0
  14. package/dist/lib/host-control/secure-fs.js +140 -0
  15. package/dist/lib/host-control/server.js +159 -0
  16. package/dist/lib/host-control/service.js +112 -0
  17. package/dist/lib/init.js +11 -2
  18. package/dist/lib/internal/cli-route-inventory.js +3 -1
  19. package/dist/lib/internal/codex-lifecycle.js +6 -6
  20. package/dist/lib/internal/demo-promotion.js +322 -0
  21. package/dist/lib/internal/demo-transcript.js +454 -0
  22. package/dist/lib/internal/host-control.js +28 -0
  23. package/dist/lib/internal/manual-validation.js +322 -0
  24. package/dist/lib/internal/platform-pr.js +18 -4
  25. package/dist/lib/internal/release-workflow.js +63 -84
  26. package/dist/lib/internal/sandbox-control.js +149 -2
  27. package/dist/lib/internal/task-artifact.js +15 -262
  28. package/dist/lib/internal/task-create.js +6 -5
  29. package/dist/lib/internal/task-event.js +37 -37
  30. package/dist/lib/internal/task-operation-registry.js +101 -3
  31. package/dist/lib/internal/task-review.js +8 -53
  32. package/dist/lib/internal/task-validate.js +30 -10
  33. package/dist/lib/owned-path.js +33 -0
  34. package/dist/lib/platform/check-status.js +11 -0
  35. package/dist/lib/platform/comment-safety.js +4 -1
  36. package/dist/lib/platform/completion-backfill.js +1 -1
  37. package/dist/lib/platform/github-data.js +241 -0
  38. package/dist/lib/platform/github-provider.js +15 -20
  39. package/dist/lib/platform/issue-comments.js +2 -2
  40. package/dist/lib/platform/issues.js +1 -179
  41. package/dist/lib/platform/pr-checks.js +12 -65
  42. package/dist/lib/platform/pr-summary.js +55 -3
  43. package/dist/lib/platform/pull-requests.js +2 -105
  44. package/dist/lib/platform/snapshots.js +2 -0
  45. package/dist/lib/platform/verification-sync.js +127 -142
  46. package/dist/lib/release/workflow.js +1 -1
  47. package/dist/lib/sandbox/commands/create.js +7 -2
  48. package/dist/lib/sandbox/commands/rm.js +3 -1514
  49. package/dist/lib/sandbox/control/audit.js +203 -0
  50. package/dist/lib/sandbox/control/client.js +93 -54
  51. package/dist/lib/sandbox/control/executor.js +129 -65
  52. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  53. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  54. package/dist/lib/sandbox/control/lifecycle.js +36 -2
  55. package/dist/lib/sandbox/control/native-file-lock.js +6 -98
  56. package/dist/lib/sandbox/control/protocol.js +55 -4
  57. package/dist/lib/sandbox/control/server.js +491 -120
  58. package/dist/lib/sandbox/control/state.js +58 -0
  59. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  60. package/dist/lib/sandbox/control/workflow-executor.js +99 -0
  61. package/dist/lib/sandbox/index.js +2 -2
  62. package/dist/lib/sandbox/recovery.js +28 -4
  63. package/dist/lib/sandbox/removal.js +1717 -0
  64. package/dist/lib/sandbox/task-cleanup.js +217 -0
  65. package/dist/lib/sandbox/workspace-view.js +13 -14
  66. package/dist/lib/task/activity-intent.js +2 -1
  67. package/dist/lib/task/activity-log.js +30 -24
  68. package/dist/lib/task/artifact-command.js +101 -0
  69. package/dist/lib/task/artifact-lifecycle.js +30 -28
  70. package/dist/lib/task/artifact-name.js +36 -0
  71. package/dist/lib/task/artifact-operations.js +23 -69
  72. package/dist/lib/task/artifact-receipts.js +3 -15
  73. package/dist/lib/task/artifact-repair-intent.js +87 -16
  74. package/dist/lib/task/artifact-schema.js +6 -14
  75. package/dist/lib/task/capabilities.js +12 -19
  76. package/dist/lib/task/codex-orchestration.js +71 -25
  77. package/dist/lib/task/commit-identity.js +28 -0
  78. package/dist/lib/task/commit-operation.js +2 -16
  79. package/dist/lib/task/control-authority.js +371 -5
  80. package/dist/lib/task/control-recovery.js +128 -0
  81. package/dist/lib/task/create.js +62 -9
  82. package/dist/lib/task/decision-details.js +2 -46
  83. package/dist/lib/task/events.js +120 -9
  84. package/dist/lib/task/finalization-state.js +9 -0
  85. package/dist/lib/task/human-override.js +3 -6
  86. package/dist/lib/task/implementation-inputs.js +3 -3
  87. package/dist/lib/task/intermediate-cleanup.js +383 -0
  88. package/dist/lib/task/invalidation.js +18 -65
  89. package/dist/lib/task/ledger-intents.js +7 -22
  90. package/dist/lib/task/ledger.js +1 -3
  91. package/dist/lib/task/lifecycle.js +19 -1
  92. package/dist/lib/task/local-artifact-finalization.js +132 -157
  93. package/dist/lib/task/manual-validation-completion.js +26 -0
  94. package/dist/lib/task/manual-validation-evidence.js +130 -0
  95. package/dist/lib/task/manual-validation-receipt.js +101 -0
  96. package/dist/lib/task/manual-validation-shared.js +40 -0
  97. package/dist/lib/task/manual-validation-transaction.js +243 -0
  98. package/dist/lib/task/markdown.js +47 -0
  99. package/dist/lib/task/orchestration.js +14 -17
  100. package/dist/lib/task/qualification-audit.js +20 -34
  101. package/dist/lib/task/review-artifacts.js +2 -18
  102. package/dist/lib/task/review-command.js +25 -0
  103. package/dist/lib/task/review-finalization.js +125 -193
  104. package/dist/lib/task/review-fingerprint.js +2 -2
  105. package/dist/lib/task/sections.js +34 -56
  106. package/dist/lib/task/verification-engine.js +116 -323
  107. package/dist/lib/task/verification.js +1 -1
  108. package/dist/lib/task/workflow-command.js +17 -0
  109. package/dist/lib/task/workflow-warning-intents.js +0 -16
  110. package/dist/lib/task/workflow-warnings.js +28 -2
  111. package/dist/lib/update.js +2 -0
  112. package/dist/lifecycle-build-manifest.json +3 -3
  113. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  114. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
  115. package/lib/fs/durable-write.ts +29 -0
  116. package/lib/fs/file-lock.ts +113 -0
  117. package/lib/host-control/audit.ts +22 -0
  118. package/lib/host-control/client.ts +172 -0
  119. package/lib/host-control/command.ts +60 -0
  120. package/lib/host-control/path.ts +167 -0
  121. package/lib/host-control/secure-fs.ts +135 -0
  122. package/lib/host-control/server.ts +180 -0
  123. package/lib/host-control/service.ts +133 -0
  124. package/lib/init.ts +15 -2
  125. package/lib/internal/cli-route-inventory.ts +3 -1
  126. package/lib/internal/codex-lifecycle.ts +6 -6
  127. package/lib/internal/demo-promotion.ts +354 -0
  128. package/lib/internal/demo-transcript.ts +419 -0
  129. package/lib/internal/host-control.ts +28 -0
  130. package/lib/internal/manual-validation.ts +306 -0
  131. package/lib/internal/platform-pr.ts +15 -4
  132. package/lib/internal/release-workflow.ts +74 -82
  133. package/lib/internal/sandbox-control.ts +162 -1
  134. package/lib/internal/task-artifact.ts +13 -216
  135. package/lib/internal/task-create.ts +5 -5
  136. package/lib/internal/task-event.ts +20 -11
  137. package/lib/internal/task-operation-registry.ts +111 -3
  138. package/lib/internal/task-review.ts +6 -51
  139. package/lib/internal/task-validate.ts +26 -10
  140. package/lib/owned-path.ts +26 -0
  141. package/lib/platform/check-status.ts +9 -0
  142. package/lib/platform/comment-safety.ts +5 -0
  143. package/lib/platform/completion-backfill.ts +1 -1
  144. package/lib/platform/github-data.ts +322 -0
  145. package/lib/platform/github-provider.ts +15 -20
  146. package/lib/platform/issue-comments.ts +3 -4
  147. package/lib/platform/issues.ts +4 -245
  148. package/lib/platform/pr-checks.ts +13 -77
  149. package/lib/platform/pr-summary.ts +61 -5
  150. package/lib/platform/pull-requests.ts +3 -170
  151. package/lib/platform/snapshots.ts +53 -0
  152. package/lib/platform/verification-sync.ts +145 -155
  153. package/lib/release/workflow.ts +3 -3
  154. package/lib/sandbox/commands/create.ts +7 -6
  155. package/lib/sandbox/commands/rm.ts +3 -1909
  156. package/lib/sandbox/control/audit.ts +253 -0
  157. package/lib/sandbox/control/client.ts +109 -66
  158. package/lib/sandbox/control/executor.ts +134 -74
  159. package/lib/sandbox/control/finalization-response.ts +29 -0
  160. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  161. package/lib/sandbox/control/lifecycle.ts +45 -3
  162. package/lib/sandbox/control/native-file-lock.ts +6 -107
  163. package/lib/sandbox/control/protocol.ts +75 -6
  164. package/lib/sandbox/control/server.ts +538 -123
  165. package/lib/sandbox/control/state.ts +78 -0
  166. package/lib/sandbox/control/task-workflow.ts +95 -0
  167. package/lib/sandbox/control/workflow-executor.ts +102 -0
  168. package/lib/sandbox/index.ts +2 -2
  169. package/lib/sandbox/recovery.ts +28 -5
  170. package/lib/sandbox/removal.ts +2144 -0
  171. package/lib/sandbox/task-cleanup.ts +209 -0
  172. package/lib/sandbox/workspace-view.ts +17 -15
  173. package/lib/task/activity-intent.ts +2 -2
  174. package/lib/task/activity-log.ts +27 -19
  175. package/lib/task/artifact-command.ts +104 -0
  176. package/lib/task/artifact-lifecycle.ts +31 -26
  177. package/lib/task/artifact-name.ts +55 -0
  178. package/lib/task/artifact-operations.ts +22 -68
  179. package/lib/task/artifact-receipts.ts +3 -14
  180. package/lib/task/artifact-repair-intent.ts +99 -18
  181. package/lib/task/artifact-schema.ts +5 -34
  182. package/lib/task/capabilities.ts +13 -22
  183. package/lib/task/codex-orchestration.ts +85 -24
  184. package/lib/task/commit-identity.ts +31 -0
  185. package/lib/task/commit-operation.ts +2 -16
  186. package/lib/task/control-authority.ts +502 -5
  187. package/lib/task/control-recovery.ts +174 -0
  188. package/lib/task/create.ts +63 -8
  189. package/lib/task/decision-details.ts +3 -71
  190. package/lib/task/events.ts +131 -10
  191. package/lib/task/finalization-state.ts +11 -0
  192. package/lib/task/human-override.ts +3 -8
  193. package/lib/task/implementation-inputs.ts +3 -3
  194. package/lib/task/intermediate-cleanup.ts +455 -0
  195. package/lib/task/invalidation.ts +18 -62
  196. package/lib/task/ledger-intents.ts +7 -24
  197. package/lib/task/ledger.ts +1 -3
  198. package/lib/task/lifecycle.ts +17 -1
  199. package/lib/task/local-artifact-finalization.ts +148 -182
  200. package/lib/task/manual-validation-completion.ts +40 -0
  201. package/lib/task/manual-validation-evidence.ts +209 -0
  202. package/lib/task/manual-validation-receipt.ts +152 -0
  203. package/lib/task/manual-validation-shared.ts +50 -0
  204. package/lib/task/manual-validation-transaction.ts +304 -0
  205. package/lib/task/markdown.ts +70 -0
  206. package/lib/task/orchestration.ts +19 -19
  207. package/lib/task/qualification-audit.ts +22 -33
  208. package/lib/task/review-artifacts.ts +1 -20
  209. package/lib/task/review-command.ts +23 -0
  210. package/lib/task/review-finalization.ts +131 -259
  211. package/lib/task/review-fingerprint.ts +2 -2
  212. package/lib/task/sections.ts +33 -54
  213. package/lib/task/verification-engine.ts +110 -366
  214. package/lib/task/verification-types.ts +17 -0
  215. package/lib/task/verification.ts +1 -1
  216. package/lib/task/workflow-command.ts +18 -0
  217. package/lib/task/workflow-warning-intents.ts +0 -13
  218. package/lib/task/workflow-warnings.ts +21 -2
  219. package/lib/update.ts +6 -0
  220. package/package.json +6 -6
  221. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  222. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  223. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  224. package/templates/.agents/scripts/package.json +3 -0
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
  226. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
  227. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  228. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  229. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  230. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  231. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  232. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  233. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  234. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  235. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  236. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  237. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
  238. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
  239. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  240. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  241. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
  242. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
  243. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  244. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  245. package/templates/.agents/skills/test/SKILL.en.md +17 -6
  246. package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
  247. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  248. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  249. package/dist/lib/platform/adapters.js +0 -56
  250. package/lib/platform/adapters.ts +0 -197
@@ -2,7 +2,7 @@ import path from 'node:path';
2
2
  // Verification runs in-process; configuration, review parsing and platform
3
3
  // checks are provided by typed domain modules without a stdout protocol.
4
4
  import { verifyInProcess } from './verification-engine.js';
5
- import { parseArtifactName } from './artifact-lifecycle.js';
5
+ import { parseArtifactName } from './artifact-name.js';
6
6
  import { resolveTaskRef } from './resolve-ref.js';
7
7
  import { allowsManualOverride } from './guard-override.js';
8
8
  const gate = (skill, expectedState, artifactFamily) => ({
@@ -0,0 +1,17 @@
1
+ export const TASK_WORKFLOW_COMMANDS = Object.freeze({
2
+ 'artifact-inspect': ['task-artifact', 'inspect'],
3
+ 'artifact-init': ['task-artifact', 'init'],
4
+ 'artifact-repair': ['task-artifact', 'repair'],
5
+ 'artifact-finalize-local': ['task-artifact', 'finalize-local'],
6
+ 'review-finalize-summary': ['task-review', 'finalize-summary'],
7
+ event: ['task-event', null],
8
+ 'ledger-finding-response': ['task-ledger', 'finding-respond'],
9
+ 'ledger-finding-review': ['task-ledger', 'finding-review'],
10
+ 'ledger-finding-upsert': ['task-ledger', 'finding-upsert'],
11
+ 'decision-next-id': ['task-ledger', 'decision-next-id'],
12
+ 'decision-upsert': ['task-ledger', 'decision-upsert'],
13
+ 'invalidation-reconcile': ['task-invalidation', 'reconcile'],
14
+ 'warning-add': ['task-warning', 'add']
15
+ });
16
+ export const TASK_WORKFLOW_OPERATIONS = Object.freeze(Object.keys(TASK_WORKFLOW_COMMANDS));
17
+ //# sourceMappingURL=workflow-command.js.map
@@ -19,19 +19,6 @@ function nextWarningId(rows) {
19
19
  }
20
20
  return `WW-${max + 1}`;
21
21
  }
22
- function validateRows(rows) {
23
- const ids = new Set();
24
- for (const row of rows) {
25
- if (!/^WW-[1-9]\d*$/.test(row.id))
26
- return { code: 'WARNING_ID_INVALID', message: `warning id '${row.id}' is invalid` };
27
- if (ids.has(row.id))
28
- return { code: 'WARNING_DUPLICATE_ID', message: `duplicate warning id '${row.id}'` };
29
- ids.add(row.id);
30
- if (!WORKFLOW_WARNING_SEVERITIES.has(row.severity) || !WORKFLOW_WARNING_STATUSES.has(row.status))
31
- return { code: 'WARNING_DOCUMENT_INVALID', message: `warning '${row.id}' has invalid severity or status` };
32
- }
33
- return null;
34
- }
35
22
  function sectionMutation(content) {
36
23
  if (/^##\s+(工作流告警|Workflow Warnings)\s*$/m.test(content))
37
24
  return [];
@@ -69,9 +56,6 @@ function applyWorkflowWarningIntent(intent, options = {}) {
69
56
  const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : 'WARNING_DOCUMENT_INVALID';
70
57
  return failed(intent, code, error instanceof Error ? error.message : String(error), resolved.taskId);
71
58
  }
72
- const invalid = validateRows(rows);
73
- if (invalid)
74
- return failed(intent, invalid.code, invalid.message, resolved.taskId);
75
59
  if (intent.kind === 'list') {
76
60
  if (intent.status && !WORKFLOW_WARNING_STATUSES.has(intent.status))
77
61
  return failed(intent, 'WARNING_PAYLOAD_INVALID', 'warning status is invalid', resolved.taskId);
@@ -3,14 +3,40 @@ const WORKFLOW_WARNING_HEADINGS = ['工作流告警', 'Workflow Warnings'];
3
3
  const WORKFLOW_WARNING_STATUSES = new Set(['open', 'resolved', 'ignored']);
4
4
  const WORKFLOW_WARNING_SEVERITIES = new Set(['IMPORTANT', 'ACTION_REQUIRED']);
5
5
  const WORKFLOW_WARNING_COLUMNS = ['id', 'time', 'step', 'severity', 'code', 'status', 'target', 'message', 'action', 'resolved_at', 'resolution'];
6
- /** Parses workflow warning rows and fails closed on malformed table structure. */
6
+ const WARNING_TIME_PATTERN = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:[+-]\d{2}:\d{2})?$/;
7
+ /** Parses warning structure and lifecycle fields using one contract for all readers. */
7
8
  function parseWorkflowWarnings(content) {
8
9
  const table = parseTable(content, { sectionAliases: WORKFLOW_WARNING_HEADINGS, columns: WORKFLOW_WARNING_COLUMNS });
9
- return table?.rows.map(({ values }) => ({
10
+ const warnings = table?.rows.map(({ values }) => ({
10
11
  id: values.id, time: values.time, step: values.step, severity: values.severity,
11
12
  code: values.code, status: values.status, target: values.target, message: values.message,
12
13
  action: values.action, resolvedAt: values.resolved_at, resolution: values.resolution
13
14
  })) ?? [];
15
+ const errors = [];
16
+ const invalid = (message, code = 'WARNING_DOCUMENT_INVALID') => errors.push({ code, message });
17
+ for (const warning of warnings) {
18
+ const { id, time, severity, status, action, resolvedAt, resolution } = warning;
19
+ if (!/^WW-[1-9]\d*$/.test(id))
20
+ invalid(`${id || '(empty id)'}: invalid id`, 'WARNING_ID_INVALID');
21
+ if (!WARNING_TIME_PATTERN.test(time))
22
+ invalid(`${id}: invalid time '${time}'`);
23
+ for (const field of ['step', 'code', 'target', 'message']) {
24
+ if (!warning[field].trim())
25
+ invalid(`${id}: ${field} is required`);
26
+ }
27
+ if (!WORKFLOW_WARNING_SEVERITIES.has(severity))
28
+ invalid(`${id}: illegal severity '${severity}'`);
29
+ if (!WORKFLOW_WARNING_STATUSES.has(status))
30
+ invalid(`${id}: illegal status '${status}'`);
31
+ if (status === 'open' && !action.trim())
32
+ invalid(`${id}: open warning requires action`);
33
+ if ((status === 'resolved' || status === 'ignored') && (!resolvedAt.trim() || !resolution.trim())) {
34
+ invalid(`${id}: ${status} warning requires resolved_at and resolution`);
35
+ }
36
+ }
37
+ if (errors.length)
38
+ throw Object.assign(new Error(errors.map((error) => error.message).join('; ')), { code: errors[0].code });
39
+ return warnings;
14
40
  }
15
41
  function getOpenWorkflowWarnings(content) {
16
42
  return parseWorkflowWarnings(content).filter((warning) => warning.status === 'open');
@@ -69,6 +69,8 @@ async function cmdSync() {
69
69
  ok('Updated .agents/skills/update-agent-infra/');
70
70
  renderFile(path.join(templateDir, '.agents', 'scripts', 'lib', 'agent-infra-package.js'), path.join('.agents', 'scripts', 'lib', 'agent-infra-package.js'), replacements);
71
71
  ok('Updated .agents/scripts/lib/agent-infra-package.js');
72
+ renderFile(path.join(templateDir, '.agents', 'scripts', 'package.json'), path.join('.agents', 'scripts', 'package.json'), replacements);
73
+ ok('Updated .agents/scripts/package.json');
72
74
  const added = {
73
75
  managed: workflowPlan.projectAssets.registry.managed.filter((entry) => !currentRegistry.managed.includes(entry)),
74
76
  merged: workflowPlan.projectAssets.registry.merged.filter((entry) => !currentRegistry.merged.includes(entry))
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "protocolVersion": 3,
3
- "packageVersion": "0.9.14",
4
- "sourceInputHash": "a3cf306ad6576904e6ef7d962e51245c6684540bd41358beaebf8209c00f3cfc",
5
- "internalExecutableBuildHash": "14773c26aea5022af695f2e3ca4a39bed3c87c1eab04347aeded028745d8ded1"
3
+ "packageVersion": "0.9.15",
4
+ "sourceInputHash": "d6521519c8d772a6d81c177d8c6e6795c080b04d9f8afcec19bca45c486a70df",
5
+ "internalExecutableBuildHash": "f0ab80c423acc4def5130e075a209a64349296985701506e7d9e73f8151bcb2a"
6
6
  }
@@ -12,6 +12,14 @@ import type { LifecycleBuildIdentity } from './build-identity.ts';
12
12
  import { resolveAgentRuntimeStoreRoot } from '../../../runtime/agent-runtime.ts';
13
13
 
14
14
  type CapabilityStatus = 'armed' | 'attested' | 'consumed' | 'expired';
15
+ type RecoveryState = 'unreserved' | 'reserved' | 'consumed';
16
+ type RecoveryPhase = 'orchestration.prepare' | 'artifact.finalize-local' | 'task-event.completed';
17
+ type RecoveryPhaseState = 'issued' | 'consumed';
18
+ type CodexCapabilityRecoveryPhase = Readonly<{
19
+ phase: RecoveryPhase;
20
+ requestId: string;
21
+ state: RecoveryPhaseState;
22
+ }>;
15
23
  type CodexControllerBinding = Readonly<{
16
24
  instanceDigest: string;
17
25
  controlGeneration: string;
@@ -47,11 +55,15 @@ type CodexCapabilityProvenanceDetail = Readonly<{
47
55
  }>;
48
56
  }>;
49
57
  type CodexCapabilityRecord = Readonly<{
50
- schemaVersion: 1;
58
+ schemaVersion: 2;
51
59
  revision: number;
52
- tokenDigest: string;
60
+ capabilityRefDigest: string;
53
61
  taskId: string;
54
62
  status: CapabilityStatus;
63
+ recoveryOperationId: string | null;
64
+ recoveryState: RecoveryState;
65
+ reservedAt: number | null;
66
+ recoveryPhases: readonly CodexCapabilityRecoveryPhase[];
55
67
  buildIdentity: LifecycleBuildIdentity;
56
68
  controller: CodexControllerBinding | null;
57
69
  sessionId: string | null;
@@ -67,7 +79,7 @@ type CodexCapabilityRecord = Readonly<{
67
79
  type CodexCapabilityStoreOptions = Readonly<{
68
80
  root?: string;
69
81
  now?: () => number;
70
- token?: () => string;
82
+ reference?: () => string;
71
83
  ttlMs?: number;
72
84
  tombstoneMs?: number;
73
85
  maxRecords?: number;
@@ -212,6 +224,22 @@ function exactKeys(value: Record<string, unknown>, keys: readonly string[]): boo
212
224
  return Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
213
225
  }
214
226
 
227
+ function isRecoveryPhase(value: unknown): value is RecoveryPhase {
228
+ return value === 'orchestration.prepare'
229
+ || value === 'artifact.finalize-local'
230
+ || value === 'task-event.completed';
231
+ }
232
+
233
+ function isRecoveryPhaseRecord(value: unknown): value is CodexCapabilityRecoveryPhase {
234
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
235
+ const phase = value as Record<string, unknown>;
236
+ return exactKeys(phase, ['phase', 'requestId', 'state'])
237
+ && isRecoveryPhase(phase.phase)
238
+ && typeof phase.requestId === 'string'
239
+ && /^[a-f0-9-]{16,64}$/u.test(phase.requestId)
240
+ && (phase.state === 'issued' || phase.state === 'consumed');
241
+ }
242
+
215
243
  function isCodexControllerBinding(value: unknown): value is CodexControllerBinding {
216
244
  if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
217
245
  const record = value as Record<string, unknown>;
@@ -302,20 +330,28 @@ function createCodexCapabilityStore(options: CodexCapabilityStoreOptions = {}) {
302
330
  const root = options.root
303
331
  ?? resolveAgentRuntimeStoreRoot({ store: 'capabilities' });
304
332
  const now = options.now ?? Date.now;
305
- const createToken = options.token ?? (() => crypto.randomBytes(32).toString('base64url'));
333
+ const createReference = options.reference ?? (() => crypto.randomBytes(32).toString('base64url'));
306
334
  const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
307
335
  const tombstoneMs = options.tombstoneMs ?? DEFAULT_TOMBSTONE_MS;
308
336
  const maxRecords = options.maxRecords ?? DEFAULT_MAX_RECORDS;
309
337
  fs.mkdirSync(root, { recursive: true, mode: 0o700 });
310
338
  fs.chmodSync(root, 0o700);
311
339
 
312
- function fileFor(token: string): string {
313
- return path.join(root, `${digest(token)}.json`);
340
+ function fileFor(capabilityRef: string): string {
341
+ return path.join(root, `${digest(capabilityRef)}.json`);
314
342
  }
315
343
 
316
344
  function read(file: string): CodexCapabilityRecord {
317
345
  const value = JSON.parse(fs.readFileSync(file, 'utf8')) as CodexCapabilityRecord;
318
- if (value.schemaVersion !== 1 || !Number.isSafeInteger(value.revision) || value.revision < 1) {
346
+ const keys = Object.keys(value as unknown as Record<string, unknown>).sort().join(',');
347
+ if (keys !== 'armedAt,attestedAt,buildIdentity,capabilityRefDigest,consumedAt,controller,expiresAt,hookDefinitionHash,recoveryOperationId,recoveryPhases,recoveryState,reservedAt,revision,schemaVersion,sessionId,status,taskId,toolUseId,turnId'
348
+ || value.schemaVersion !== 2 || !Number.isSafeInteger(value.revision) || value.revision < 1
349
+ || typeof value.capabilityRefDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(value.capabilityRefDigest)
350
+ || !['unreserved', 'reserved', 'consumed'].includes(value.recoveryState)
351
+ || (value.recoveryOperationId !== null && typeof value.recoveryOperationId !== 'string')
352
+ || (value.reservedAt !== null && !Number.isSafeInteger(value.reservedAt))
353
+ || !Array.isArray(value.recoveryPhases)
354
+ || !value.recoveryPhases.every((phase) => isRecoveryPhaseRecord(phase))) {
319
355
  throw capabilityError('CODEX_CAPABILITY_STATE_INVALID', 'capability record schema is invalid');
320
356
  }
321
357
  return value;
@@ -426,18 +462,22 @@ function createCodexCapabilityStore(options: CodexCapabilityStoreOptions = {}) {
426
462
  taskId: string;
427
463
  buildIdentity: LifecycleBuildIdentity;
428
464
  controller?: CodexControllerBinding;
429
- }>): CodexCapabilityRecord & Readonly<{ token: string; path: string; marker: string }> {
465
+ }>): CodexCapabilityRecord & Readonly<{ capabilityRef: string; path: string; marker: string }> {
430
466
  sweep();
431
- const token = createToken();
432
- const file = fileFor(token);
467
+ const capabilityRef = createReference();
468
+ const file = fileFor(capabilityRef);
433
469
  if (fs.existsSync(file)) throw capabilityError('CODEX_CAPABILITY_TOKEN_COLLISION', 'capability token already exists');
434
470
  const timestamp = now();
435
471
  const record: CodexCapabilityRecord = Object.freeze({
436
- schemaVersion: 1,
472
+ schemaVersion: 2,
437
473
  revision: 1,
438
- tokenDigest: digest(token),
474
+ capabilityRefDigest: digest(capabilityRef),
439
475
  taskId: input.taskId,
440
476
  status: 'armed',
477
+ recoveryOperationId: null,
478
+ recoveryState: 'unreserved',
479
+ reservedAt: null,
480
+ recoveryPhases: Object.freeze([]),
441
481
  buildIdentity: input.buildIdentity,
442
482
  controller: input.controller ?? null,
443
483
  sessionId: null,
@@ -450,28 +490,32 @@ function createCodexCapabilityStore(options: CodexCapabilityStoreOptions = {}) {
450
490
  consumedAt: null
451
491
  });
452
492
  write(file, record, 0);
453
- return Object.freeze({
493
+ const result = {
454
494
  ...record,
455
- token,
495
+ capabilityRef,
456
496
  path: file,
457
- marker: `agent-infra-codex-capability:${token}`
458
- });
497
+ marker: `agent-infra-codex-capability:${capabilityRef}`
498
+ };
499
+ return Object.freeze(result) as CodexCapabilityRecord & Readonly<{ capabilityRef: string; path: string; marker: string }>;
459
500
  }
460
501
 
461
- function loadActive(token: string): Readonly<{ file: string; record: CodexCapabilityRecord }> {
462
- const file = fileFor(token);
502
+ function loadActive(
503
+ capabilityRef: string,
504
+ options: Readonly<{ allowExpiredReserved?: boolean }> = {}
505
+ ): Readonly<{ file: string; record: CodexCapabilityRecord }> {
506
+ const file = fileFor(capabilityRef);
463
507
  if (!fs.existsSync(file)) {
464
508
  throw capabilityError('CODEX_CAPABILITY_MISSING', 'capability token was not found');
465
509
  }
466
510
  const record = expire(read(file), file, now());
467
- if (record.status === 'expired') {
511
+ if (record.status === 'expired' && !options.allowExpiredReserved) {
468
512
  throw capabilityError('CODEX_CAPABILITY_EXPIRED', 'capability token expired');
469
513
  }
470
514
  return { file, record };
471
515
  }
472
516
 
473
- function attest(input: Readonly<{
474
- token: string;
517
+ function attestByReference(input: Readonly<{
518
+ capabilityRef: string;
475
519
  sessionId: string;
476
520
  turnId: string;
477
521
  toolUseId: string;
@@ -479,7 +523,7 @@ function createCodexCapabilityStore(options: CodexCapabilityStoreOptions = {}) {
479
523
  buildIdentity: LifecycleBuildIdentity;
480
524
  controller?: CodexControllerBinding;
481
525
  }>): CodexCapabilityRecord {
482
- const { file, record } = loadActive(input.token);
526
+ const { file, record } = loadActive(input.capabilityRef);
483
527
  if (record.status !== 'armed') {
484
528
  throw capabilityError(
485
529
  record.status === 'consumed' ? 'CODEX_CAPABILITY_REPLAY' : 'CODEX_CAPABILITY_STATE_INVALID',
@@ -514,11 +558,14 @@ function createCodexCapabilityStore(options: CodexCapabilityStoreOptions = {}) {
514
558
  hookDefinitionHash: string;
515
559
  buildIdentity: LifecycleBuildIdentity;
516
560
  controller?: CodexControllerBinding;
517
- }>): CodexCapabilityRecord {
561
+ }>, options: Readonly<{ allowExpiredReserved?: boolean }> = {}): CodexCapabilityRecord {
518
562
  if (record.status === 'consumed') {
519
563
  throw capabilityError('CODEX_CAPABILITY_REPLAY', 'capability token was already consumed');
520
564
  }
521
- if (record.status !== 'attested') {
565
+ const expiredReserved = options.allowExpiredReserved
566
+ && record.status === 'expired'
567
+ && record.recoveryState === 'reserved';
568
+ if (record.status !== 'attested' && !expiredReserved) {
522
569
  throw capabilityError('CODEX_CAPABILITY_NOT_ATTESTED', 'capability token has no current-session hook attestation');
523
570
  }
524
571
  const identity = record.buildIdentity && typeof record.buildIdentity === 'object'
@@ -537,49 +584,192 @@ function createCodexCapabilityStore(options: CodexCapabilityStoreOptions = {}) {
537
584
  return record;
538
585
  }
539
586
 
540
- function validate(token: string, expected: Readonly<{
587
+ function validateReference(capabilityRef: string, expected: Readonly<{
541
588
  taskId: string;
542
589
  hookDefinitionHash: string;
543
590
  buildIdentity: LifecycleBuildIdentity;
544
591
  controller?: CodexControllerBinding;
545
592
  }>): CodexCapabilityRecord {
546
593
  sweep();
547
- const { record } = loadActive(token);
594
+ const { record } = loadActive(capabilityRef);
548
595
  return validateRecord(record, expected);
549
596
  }
550
597
 
551
- function consume(token: string, expected: Readonly<{
598
+ function consumeReference(capabilityRef: string, operationId: string, expected: Readonly<{
552
599
  taskId: string;
553
600
  hookDefinitionHash: string;
554
601
  buildIdentity: LifecycleBuildIdentity;
555
602
  controller?: CodexControllerBinding;
556
- }>): CodexCapabilityRecord {
603
+ }>, options: Readonly<{ allowExpiredReserved?: boolean }> = {}): CodexCapabilityRecord {
557
604
  sweep();
558
- const { file, record } = loadActive(token);
559
- validateRecord(record, expected);
605
+ if (!operationId || /[\r\n]/u.test(operationId)) {
606
+ throw capabilityError('CODEX_CAPABILITY_RECOVERY_INVALID', 'recovery operation id is invalid');
607
+ }
608
+ const { file, record } = loadActive(capabilityRef, options);
609
+ if (record.status === 'consumed' && record.recoveryState === 'consumed' && record.recoveryOperationId === operationId) {
610
+ return record;
611
+ }
612
+ validateRecord(record, expected, options);
613
+ if (record.recoveryState === 'consumed') {
614
+ if (record.recoveryOperationId === operationId) return record;
615
+ throw capabilityError('CODEX_CAPABILITY_REPLAY', 'capability recovery operation was already consumed');
616
+ }
617
+ if (record.recoveryState !== 'reserved' || record.recoveryOperationId !== operationId) {
618
+ throw capabilityError('CODEX_CAPABILITY_RECOVERY_INVALID', 'capability must be reserved by this operation before consumption');
619
+ }
560
620
  const next: CodexCapabilityRecord = Object.freeze({
561
621
  ...record,
562
622
  revision: record.revision + 1,
563
623
  status: 'consumed',
624
+ recoveryState: 'consumed',
564
625
  consumedAt: now()
565
626
  });
566
627
  write(file, next, record.revision);
567
628
  return next;
568
629
  }
569
630
 
570
- function inspect(token: string): CodexCapabilityRecord {
571
- return loadActive(token).record;
631
+ function reserveRecoveryPhase(
632
+ capabilityRef: string,
633
+ operationId: string,
634
+ phase: RecoveryPhase,
635
+ requestId: string,
636
+ expected: Readonly<{
637
+ taskId: string;
638
+ hookDefinitionHash: string;
639
+ buildIdentity: LifecycleBuildIdentity;
640
+ controller?: CodexControllerBinding;
641
+ }>
642
+ ): CodexCapabilityRecord {
643
+ if (!operationId || /[\r\n]/u.test(operationId) || !isRecoveryPhase(phase)
644
+ || !/^[a-f0-9-]{16,64}$/u.test(requestId)) {
645
+ throw capabilityError('CODEX_CAPABILITY_RECOVERY_INVALID', 'recovery phase identity is invalid');
646
+ }
647
+ sweep();
648
+ const { file, record } = loadActive(capabilityRef);
649
+ validateRecord(record, expected);
650
+ if (record.recoveryState !== 'reserved' || record.recoveryOperationId !== operationId) {
651
+ throw capabilityError('CODEX_CAPABILITY_RECOVERY_INVALID', 'capability must be reserved by this operation before phase reservation');
652
+ }
653
+ if (record.recoveryPhases.some((entry) => entry.phase === phase)) {
654
+ throw capabilityError('CODEX_CAPABILITY_PHASE_REPLAY', 'recovery phase was already reserved');
655
+ }
656
+ const next: CodexCapabilityRecord = Object.freeze({
657
+ ...record,
658
+ revision: record.revision + 1,
659
+ recoveryPhases: Object.freeze([
660
+ ...record.recoveryPhases,
661
+ Object.freeze({ phase, requestId, state: 'issued' as const })
662
+ ])
663
+ });
664
+ write(file, next, record.revision);
665
+ return next;
666
+ }
667
+
668
+ function consumeRecoveryPhase(
669
+ capabilityRef: string,
670
+ operationId: string,
671
+ phase: RecoveryPhase,
672
+ requestId: string,
673
+ expected: Readonly<{
674
+ taskId: string;
675
+ hookDefinitionHash: string;
676
+ buildIdentity: LifecycleBuildIdentity;
677
+ controller?: CodexControllerBinding;
678
+ }>,
679
+ options: Readonly<{ allowExpiredReserved?: boolean }> = {}
680
+ ): CodexCapabilityRecord {
681
+ if (!operationId || /[\r\n]/u.test(operationId) || !isRecoveryPhase(phase)
682
+ || !/^[a-f0-9-]{16,64}$/u.test(requestId)) {
683
+ throw capabilityError('CODEX_CAPABILITY_RECOVERY_INVALID', 'recovery phase identity is invalid');
684
+ }
685
+ sweep();
686
+ const { file, record } = loadActive(capabilityRef, options);
687
+ validateRecord(record, expected, options);
688
+ if (record.recoveryState !== 'reserved' && record.recoveryState !== 'consumed') {
689
+ throw capabilityError('CODEX_CAPABILITY_RECOVERY_INVALID', 'capability has no reserved recovery operation');
690
+ }
691
+ if (record.recoveryOperationId !== operationId) {
692
+ throw capabilityError('CODEX_CAPABILITY_REPLAY', 'capability recovery operation does not match');
693
+ }
694
+ const current = record.recoveryPhases.find((entry) => entry.phase === phase);
695
+ if (!current || current.requestId !== requestId) {
696
+ throw capabilityError('CODEX_CAPABILITY_PHASE_REPLAY', 'recovery phase identity does not match');
697
+ }
698
+ if (current.state === 'consumed') return record;
699
+ const next: CodexCapabilityRecord = Object.freeze({
700
+ ...record,
701
+ revision: record.revision + 1,
702
+ recoveryPhases: Object.freeze(record.recoveryPhases.map((entry) =>
703
+ entry.phase === phase ? Object.freeze({ ...entry, state: 'consumed' as const }) : entry
704
+ ))
705
+ });
706
+ write(file, next, record.revision);
707
+ return next;
708
+ }
709
+
710
+ function reserveReference(
711
+ capabilityRef: string,
712
+ operationId: string,
713
+ expected: Readonly<{
714
+ taskId: string;
715
+ hookDefinitionHash: string;
716
+ buildIdentity: LifecycleBuildIdentity;
717
+ controller?: CodexControllerBinding;
718
+ }>
719
+ ): CodexCapabilityRecord {
720
+ if (!operationId || /[\r\n]/u.test(operationId)) {
721
+ throw capabilityError('CODEX_CAPABILITY_RECOVERY_INVALID', 'recovery operation id is invalid');
722
+ }
723
+ sweep();
724
+ const { file, record } = loadActive(capabilityRef);
725
+ validateRecord(record, expected);
726
+ if (record.recoveryState === 'reserved') {
727
+ if (record.recoveryOperationId === operationId) return record;
728
+ throw capabilityError('CODEX_CAPABILITY_BUSY', 'capability is reserved by another recovery operation');
729
+ }
730
+ if (record.recoveryState === 'consumed') {
731
+ if (record.recoveryOperationId === operationId) return record;
732
+ throw capabilityError('CODEX_CAPABILITY_REPLAY', 'capability recovery operation was already consumed');
733
+ }
734
+ const next: CodexCapabilityRecord = Object.freeze({
735
+ ...record,
736
+ revision: record.revision + 1,
737
+ recoveryOperationId: operationId,
738
+ recoveryState: 'reserved',
739
+ reservedAt: now()
740
+ });
741
+ write(file, next, record.revision);
742
+ return next;
743
+ }
744
+
745
+ function inspectReference(capabilityRef: string): CodexCapabilityRecord {
746
+ return loadActive(capabilityRef).record;
747
+ }
748
+
749
+ function findByRecoveryOperation(operationId: string): readonly CodexCapabilityRecord[] {
750
+ if (!operationId || /[\r\n]/u.test(operationId)) {
751
+ throw capabilityError('CODEX_CAPABILITY_RECOVERY_INVALID', 'recovery operation id is invalid');
752
+ }
753
+ return Object.freeze(files()
754
+ .map((file) => read(file))
755
+ .filter((record) => record.recoveryOperationId === operationId));
572
756
  }
573
757
 
574
- return Object.freeze({ arm, attest, consume, inspect, validate, sweep });
758
+ return Object.freeze({
759
+ arm, attestByReference, reserveReference, reserveRecoveryPhase, consumeRecoveryPhase,
760
+ consumeReference, inspectReference,
761
+ findByRecoveryOperation, validateReference, sweep
762
+ });
575
763
  }
576
764
 
577
765
  export { createCodexCapabilityStore };
578
766
  export type {
579
767
  CapabilityStatus,
768
+ RecoveryState,
580
769
  CodexCapabilityProjection,
581
770
  CodexCapabilityProvenanceDetail,
582
771
  CodexCapabilityRecord,
772
+ CodexCapabilityRecoveryPhase,
583
773
  CodexCapabilityStoreOptions,
584
774
  CodexControllerBinding
585
775
  };
@@ -351,9 +351,10 @@ function prepareCodexSandboxController(
351
351
  const shimDir = path.join(home, 'bin');
352
352
  fs.mkdirSync(shimDir, { mode: 0o700 });
353
353
  const internalCli = path.resolve(process.argv[1] ?? path.join(repoRoot, 'bin', 'internal-cli.ts'));
354
+ const launcherPrefix = ['#!/bin/sh', 'set -eu', 'unset NODE_OPTIONS NODE_PATH'].join('\n') + '\n';
354
355
  const source = internalCli.endsWith('.ts')
355
- ? `#!/bin/sh\nexec ${shellQuote(process.execPath)} --experimental-strip-types ${shellQuote(internalCli)} "$@"\n`
356
- : `#!/bin/sh\nexec ${shellQuote(process.execPath)} ${shellQuote(internalCli)} "$@"\n`;
356
+ ? `${launcherPrefix}exec ${shellQuote(process.execPath)} --experimental-strip-types ${shellQuote(internalCli)} "$@"\n`
357
+ : `${launcherPrefix}exec ${shellQuote(process.execPath)} ${shellQuote(internalCli)} "$@"\n`;
357
358
  fs.writeFileSync(path.join(shimDir, 'agent-infra-internal'), source, { mode: 0o700 });
358
359
 
359
360
  const policy = [
@@ -0,0 +1,29 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+
5
+ export function fsyncDirectory(directory: string): void {
6
+ const descriptor = fs.openSync(directory, 'r');
7
+ try { fs.fsyncSync(descriptor); } finally { fs.closeSync(descriptor); }
8
+ }
9
+
10
+ /** The caller owns directory validation and the meaning of an existing target. */
11
+ export function writeDurableFile(
12
+ target: string,
13
+ content: string,
14
+ options: Readonly<{ mode: number; replace: boolean }>
15
+ ): void {
16
+ const directory = path.dirname(target);
17
+ const temporary = path.join(directory, `.${path.basename(target)}.${process.pid}.${randomUUID()}.tmp`);
18
+ try {
19
+ const descriptor = fs.openSync(temporary, 'wx', options.mode);
20
+ try {
21
+ fs.writeFileSync(descriptor, content, { encoding: 'utf8' });
22
+ fs.fchmodSync(descriptor, options.mode);
23
+ fs.fsyncSync(descriptor);
24
+ } finally { fs.closeSync(descriptor); }
25
+ if (options.replace) fs.renameSync(temporary, target);
26
+ else fs.linkSync(temporary, target);
27
+ fsyncDirectory(directory);
28
+ } finally { fs.rmSync(temporary, { force: true }); }
29
+ }