@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.
- package/README.md +10 -0
- package/README.zh-CN.md +10 -0
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/internal-cli.js +93 -30
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +37 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +3 -1
- package/dist/lib/internal/codex-lifecycle.js +6 -6
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +322 -0
- package/dist/lib/internal/platform-pr.js +18 -4
- package/dist/lib/internal/release-workflow.js +63 -84
- package/dist/lib/internal/sandbox-control.js +149 -2
- package/dist/lib/internal/task-artifact.js +15 -262
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-operation-registry.js +101 -3
- package/dist/lib/internal/task-review.js +8 -53
- package/dist/lib/internal/task-validate.js +30 -10
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +4 -1
- package/dist/lib/platform/completion-backfill.js +1 -1
- package/dist/lib/platform/github-data.js +241 -0
- package/dist/lib/platform/github-provider.js +15 -20
- package/dist/lib/platform/issue-comments.js +2 -2
- package/dist/lib/platform/issues.js +1 -179
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +55 -3
- package/dist/lib/platform/pull-requests.js +2 -105
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/verification-sync.js +127 -142
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +3 -1514
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +93 -54
- package/dist/lib/sandbox/control/executor.js +129 -65
- package/dist/lib/sandbox/control/finalization-response.js +27 -0
- package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
- package/dist/lib/sandbox/control/lifecycle.js +36 -2
- package/dist/lib/sandbox/control/native-file-lock.js +6 -98
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +491 -120
- package/dist/lib/sandbox/control/state.js +58 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +99 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +1717 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-view.js +13 -14
- package/dist/lib/task/activity-intent.js +2 -1
- package/dist/lib/task/activity-log.js +30 -24
- package/dist/lib/task/artifact-command.js +101 -0
- package/dist/lib/task/artifact-lifecycle.js +30 -28
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +23 -69
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-repair-intent.js +87 -16
- package/dist/lib/task/artifact-schema.js +6 -14
- package/dist/lib/task/capabilities.js +12 -19
- package/dist/lib/task/codex-orchestration.js +71 -25
- package/dist/lib/task/commit-identity.js +28 -0
- package/dist/lib/task/commit-operation.js +2 -16
- package/dist/lib/task/control-authority.js +371 -5
- package/dist/lib/task/control-recovery.js +128 -0
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/events.js +120 -9
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +3 -6
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +383 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle.js +19 -1
- package/dist/lib/task/local-artifact-finalization.js +132 -157
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +101 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +14 -17
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +25 -0
- package/dist/lib/task/review-finalization.js +125 -193
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/verification-engine.js +116 -323
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +17 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
- package/lib/fs/durable-write.ts +29 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +3 -1
- package/lib/internal/codex-lifecycle.ts +6 -6
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +306 -0
- package/lib/internal/platform-pr.ts +15 -4
- package/lib/internal/release-workflow.ts +74 -82
- package/lib/internal/sandbox-control.ts +162 -1
- package/lib/internal/task-artifact.ts +13 -216
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-operation-registry.ts +111 -3
- package/lib/internal/task-review.ts +6 -51
- package/lib/internal/task-validate.ts +26 -10
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +5 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +15 -20
- package/lib/platform/issue-comments.ts +3 -4
- package/lib/platform/issues.ts +4 -245
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +61 -5
- package/lib/platform/pull-requests.ts +3 -170
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/verification-sync.ts +145 -155
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +3 -1909
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +109 -66
- package/lib/sandbox/control/executor.ts +134 -74
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +45 -3
- package/lib/sandbox/control/native-file-lock.ts +6 -107
- package/lib/sandbox/control/protocol.ts +75 -6
- package/lib/sandbox/control/server.ts +538 -123
- package/lib/sandbox/control/state.ts +78 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +102 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +2144 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-view.ts +17 -15
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +104 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +22 -68
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-repair-intent.ts +99 -18
- package/lib/task/artifact-schema.ts +5 -34
- package/lib/task/capabilities.ts +13 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +2 -16
- package/lib/task/control-authority.ts +502 -5
- package/lib/task/control-recovery.ts +174 -0
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/events.ts +131 -10
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +3 -8
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +455 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle.ts +17 -1
- package/lib/task/local-artifact-finalization.ts +148 -182
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +152 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +19 -19
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +23 -0
- package/lib/task/review-finalization.ts +131 -259
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/verification-engine.ts +110 -366
- package/lib/task/verification-types.ts +17 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +18 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/testing-discipline.en.md +2 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
- package/templates/.agents/scripts/package.json +3 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
- package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +7 -3
- package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +17 -6
- package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/dist/lib/platform/adapters.js +0 -56
- 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-
|
|
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
|
-
|
|
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
|
-
|
|
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');
|
package/dist/lib/update.js
CHANGED
|
@@ -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.
|
|
4
|
-
"sourceInputHash": "
|
|
5
|
-
"internalExecutableBuildHash": "
|
|
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:
|
|
58
|
+
schemaVersion: 2;
|
|
51
59
|
revision: number;
|
|
52
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
313
|
-
return path.join(root, `${digest(
|
|
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
|
-
|
|
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<{
|
|
465
|
+
}>): CodexCapabilityRecord & Readonly<{ capabilityRef: string; path: string; marker: string }> {
|
|
430
466
|
sweep();
|
|
431
|
-
const
|
|
432
|
-
const file = fileFor(
|
|
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:
|
|
472
|
+
schemaVersion: 2,
|
|
437
473
|
revision: 1,
|
|
438
|
-
|
|
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
|
-
|
|
493
|
+
const result = {
|
|
454
494
|
...record,
|
|
455
|
-
|
|
495
|
+
capabilityRef,
|
|
456
496
|
path: file,
|
|
457
|
-
marker: `agent-infra-codex-capability:${
|
|
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(
|
|
462
|
-
|
|
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
|
|
474
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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(
|
|
594
|
+
const { record } = loadActive(capabilityRef);
|
|
548
595
|
return validateRecord(record, expected);
|
|
549
596
|
}
|
|
550
597
|
|
|
551
|
-
function
|
|
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
|
-
|
|
559
|
-
|
|
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
|
|
571
|
-
|
|
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({
|
|
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
|
-
?
|
|
356
|
-
:
|
|
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
|
+
}
|