@fitlab-ai/agent-infra 0.9.13 → 0.9.14
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/bin/cli.ts +26 -9
- package/dist/bin/cli.js +173 -157
- package/dist/bin/internal-cli.js +42 -21
- package/dist/lib/agent-client.js +5 -0
- package/dist/lib/internal/agent-client.js +5 -2
- package/dist/lib/internal/cli-route-inventory.js +152 -0
- package/dist/lib/internal/codex-lifecycle.js +9 -6
- package/dist/lib/internal/codex-sandbox-controller.js +8 -2
- package/dist/lib/internal/git-workflow.js +10 -6
- package/dist/lib/internal/platform-checks.js +16 -4
- package/dist/lib/internal/platform-comment.js +16 -4
- package/dist/lib/internal/platform-context.js +4 -1
- package/dist/lib/internal/platform-issue.js +12 -4
- package/dist/lib/internal/platform-metadata.js +88 -0
- package/dist/lib/internal/platform-pr-review.js +16 -3
- package/dist/lib/internal/platform-pr.js +24 -10
- package/dist/lib/internal/platform-release-notes.js +6 -3
- package/dist/lib/internal/platform-security.js +93 -0
- package/dist/lib/internal/pr-review-grade.js +14 -3
- package/dist/lib/internal/release-workflow.js +19 -5
- package/dist/lib/internal/sandbox-control.js +22 -5
- package/dist/lib/internal/task-activity.js +13 -4
- package/dist/lib/internal/task-artifact.js +186 -27
- package/dist/lib/internal/task-context.js +4 -1
- package/dist/lib/internal/task-create.js +48 -15
- package/dist/lib/internal/task-delivery.js +4 -1
- package/dist/lib/internal/task-event.js +4 -1
- package/dist/lib/internal/task-finalization.js +4 -1
- package/dist/lib/internal/task-invalidation.js +4 -1
- package/dist/lib/internal/task-ledger.js +12 -1
- package/dist/lib/internal/task-lifecycle.js +4 -1
- package/dist/lib/internal/task-operation-registry.js +421 -0
- package/dist/lib/internal/task-orchestration.js +12 -0
- package/dist/lib/internal/task-override.js +10 -3
- package/dist/lib/internal/task-qualification.js +90 -0
- package/dist/lib/internal/task-review.js +4 -1
- package/dist/lib/internal/task-short-id.js +11 -1
- package/dist/lib/internal/task-snapshot.js +4 -1
- package/dist/lib/internal/task-validate.js +7 -2
- package/dist/lib/internal/task-verify.js +5 -1
- package/dist/lib/internal/task-warning.js +8 -1
- package/dist/lib/platform/comment-safety.js +226 -0
- package/dist/lib/platform/github-provider.js +233 -6
- package/dist/lib/platform/issue-comments.js +146 -34
- package/dist/lib/platform/pr-change-report.js +6 -12
- package/dist/lib/platform/pr-summary.js +28 -7
- package/dist/lib/platform/provider-contract.d.ts +67 -1
- package/dist/lib/platform/provider-contract.js +6 -2
- package/dist/lib/platform/provider-validation.js +76 -3
- package/dist/lib/platform/pull-requests.js +115 -170
- package/dist/lib/platform/repository-metadata.js +377 -0
- package/dist/lib/platform/security-alerts.js +155 -0
- package/dist/lib/platform/verification-sync.js +33 -26
- package/dist/lib/process-data/index.js +7 -0
- package/dist/lib/run/index.js +5 -0
- package/dist/lib/sandbox/commands/control-status.js +22 -0
- package/dist/lib/sandbox/commands/create.js +32 -6
- package/dist/lib/sandbox/commands/ls.js +5 -0
- package/dist/lib/sandbox/commands/rm.js +827 -120
- package/dist/lib/sandbox/commands/show.js +16 -0
- package/dist/lib/sandbox/control/client.js +83 -9
- package/dist/lib/sandbox/control/completed-reentry.js +85 -0
- package/dist/lib/sandbox/control/container-identity.js +28 -31
- package/dist/lib/sandbox/control/executor.js +149 -14
- package/dist/lib/sandbox/control/lifecycle.js +621 -127
- package/dist/lib/sandbox/control/native-file-lock.js +177 -0
- package/dist/lib/sandbox/control/server.js +305 -35
- package/dist/lib/sandbox/control/state.js +5 -3
- package/dist/lib/sandbox/control/task-view.js +183 -0
- package/dist/lib/sandbox/engines/authority.js +289 -0
- package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
- package/dist/lib/sandbox/index.js +5 -0
- package/dist/lib/sandbox/recovery.js +21 -1
- package/dist/lib/sandbox/workspace-view.js +2 -1
- package/dist/lib/sandbox/worktree-safety.js +4 -1
- package/dist/lib/server/index.js +8 -0
- package/dist/lib/task/artifact-lifecycle.js +42 -13
- package/dist/lib/task/artifact-operations.js +520 -0
- package/dist/lib/task/artifact-repair-intent.js +59 -0
- package/dist/lib/task/artifact-schema.js +135 -0
- package/dist/lib/task/capabilities.js +72 -1
- package/dist/lib/task/control-authority.js +4 -0
- package/dist/lib/task/create-service.js +103 -4
- package/dist/lib/task/create.js +25 -0
- package/dist/lib/task/events.js +186 -67
- package/dist/lib/task/index.js +11 -3
- package/dist/lib/task/local-artifact-finalization.js +75 -150
- package/dist/lib/task/orchestration.js +192 -52
- package/dist/lib/task/qualification-audit.js +490 -0
- package/dist/lib/task/qualification-confirmation.js +109 -0
- package/dist/lib/task/qualification-intents.js +148 -0
- package/dist/lib/task/review-artifacts.js +22 -23
- package/dist/lib/task/review-finalization.js +121 -4
- package/dist/lib/task/sections.js +6 -4
- package/dist/lib/task/verification-config.js +11 -0
- package/dist/lib/task/verification-engine.js +54 -4
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-client.ts +9 -0
- package/lib/internal/agent-client.ts +4 -2
- package/lib/internal/cli-route-inventory.ts +140 -0
- package/lib/internal/codex-lifecycle.ts +8 -6
- package/lib/internal/codex-sandbox-controller.ts +7 -2
- package/lib/internal/git-workflow.ts +9 -6
- package/lib/internal/platform-checks.ts +12 -4
- package/lib/internal/platform-comment.ts +12 -4
- package/lib/internal/platform-context.ts +3 -1
- package/lib/internal/platform-issue.ts +11 -4
- package/lib/internal/platform-metadata.ts +70 -0
- package/lib/internal/platform-pr-review.ts +14 -3
- package/lib/internal/platform-pr.ts +23 -10
- package/lib/internal/platform-release-notes.ts +5 -3
- package/lib/internal/platform-security.ts +83 -0
- package/lib/internal/pr-review-grade.ts +10 -3
- package/lib/internal/release-workflow.ts +17 -5
- package/lib/internal/sandbox-control.ts +19 -5
- package/lib/internal/task-activity.ts +12 -4
- package/lib/internal/task-artifact.ts +164 -25
- package/lib/internal/task-context.ts +3 -1
- package/lib/internal/task-create.ts +76 -15
- package/lib/internal/task-delivery.ts +3 -1
- package/lib/internal/task-event.ts +3 -1
- package/lib/internal/task-finalization.ts +3 -1
- package/lib/internal/task-invalidation.ts +3 -1
- package/lib/internal/task-ledger.ts +11 -1
- package/lib/internal/task-lifecycle.ts +3 -1
- package/lib/internal/task-operation-registry.ts +482 -0
- package/lib/internal/task-orchestration.ts +12 -0
- package/lib/internal/task-override.ts +9 -3
- package/lib/internal/task-qualification.ts +61 -0
- package/lib/internal/task-review.ts +3 -1
- package/lib/internal/task-short-id.ts +10 -1
- package/lib/internal/task-snapshot.ts +3 -1
- package/lib/internal/task-validate.ts +6 -2
- package/lib/internal/task-verify.ts +4 -1
- package/lib/internal/task-warning.ts +7 -1
- package/lib/platform/comment-safety.ts +250 -0
- package/lib/platform/github-provider.ts +229 -7
- package/lib/platform/issue-comments.ts +160 -35
- package/lib/platform/pr-change-report.ts +6 -12
- package/lib/platform/pr-summary.ts +27 -6
- package/lib/platform/provider-contract.ts +78 -3
- package/lib/platform/provider-validation.ts +85 -3
- package/lib/platform/pull-requests.ts +126 -178
- package/lib/platform/repository-metadata.ts +453 -0
- package/lib/platform/security-alerts.ts +191 -0
- package/lib/platform/verification-sync.ts +32 -28
- package/lib/process-data/index.ts +7 -0
- package/lib/run/index.ts +5 -0
- package/lib/sandbox/commands/control-status.ts +26 -0
- package/lib/sandbox/commands/create.ts +46 -6
- package/lib/sandbox/commands/ls.ts +7 -0
- package/lib/sandbox/commands/rm.ts +1000 -87
- package/lib/sandbox/commands/show.ts +16 -0
- package/lib/sandbox/control/client.ts +103 -9
- package/lib/sandbox/control/completed-reentry.ts +98 -0
- package/lib/sandbox/control/container-identity.ts +27 -43
- package/lib/sandbox/control/executor.ts +177 -12
- package/lib/sandbox/control/lifecycle.ts +629 -17
- package/lib/sandbox/control/native-file-lock.ts +209 -0
- package/lib/sandbox/control/protocol.ts +5 -2
- package/lib/sandbox/control/server.ts +319 -37
- package/lib/sandbox/control/state.ts +6 -3
- package/lib/sandbox/control/task-view.ts +240 -0
- package/lib/sandbox/engines/authority.ts +365 -0
- package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
- package/lib/sandbox/index.ts +7 -0
- package/lib/sandbox/recovery.ts +21 -1
- package/lib/sandbox/workspace-view.ts +9 -3
- package/lib/sandbox/worktree-safety.ts +4 -1
- package/lib/server/index.ts +10 -0
- package/lib/task/artifact-lifecycle.ts +38 -16
- package/lib/task/artifact-operations.ts +626 -0
- package/lib/task/artifact-repair-intent.ts +71 -0
- package/lib/task/artifact-schema.ts +192 -0
- package/lib/task/capabilities.ts +70 -1
- package/lib/task/control-authority.ts +11 -1
- package/lib/task/create-service.ts +131 -6
- package/lib/task/create.ts +27 -0
- package/lib/task/events.ts +161 -65
- package/lib/task/index.ts +16 -3
- package/lib/task/local-artifact-finalization.ts +87 -218
- package/lib/task/orchestration.ts +172 -49
- package/lib/task/qualification-audit.ts +599 -0
- package/lib/task/qualification-confirmation.ts +117 -0
- package/lib/task/qualification-intents.ts +150 -0
- package/lib/task/review-artifacts.ts +21 -22
- package/lib/task/review-finalization.ts +133 -4
- package/lib/task/sections.ts +6 -4
- package/lib/task/verification-config.ts +12 -0
- package/lib/task/verification-engine.ts +55 -4
- package/package.json +6 -1
- package/templates/.agents/rules/README.en.md +2 -0
- package/templates/.agents/rules/README.zh-CN.md +2 -0
- package/templates/.agents/rules/decision-qualification.en.md +22 -0
- package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
- package/templates/.agents/rules/evidence-reporting.en.md +28 -0
- package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
- package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
- package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
- package/templates/.agents/rules/local-artifact-repair.en.md +5 -1
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +5 -1
- package/templates/.agents/rules/security-alerts.en.md +8 -8
- package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
- package/templates/.agents/skills/analyze-task/SKILL.en.md +15 -2
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +19 -2
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
- package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +14 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/code-task/config/verify.en.json +5 -14
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +5 -14
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -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-task/SKILL.en.md +9 -22
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +9 -22
- package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -0
- package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/init-labels/SKILL.en.md +8 -8
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
- package/templates/.agents/skills/plan-task/SKILL.en.md +14 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +16 -1
- package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
- package/templates/.agents/skills/review-analysis/SKILL.en.md +14 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
- package/templates/.agents/skills/review-code/SKILL.en.md +14 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
- package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
- package/templates/.agents/skills/review-plan/SKILL.en.md +14 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
- package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +5 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +3 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +3 -1
- package/templates/.agents/skills/test/SKILL.en.md +13 -2
- package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
- package/templates/.agents/templates/task.en.md +6 -0
- package/templates/.agents/templates/task.zh-CN.md +6 -0
- package/templates/.agents/scripts/security-alerts.github.sh +0 -151
- package/templates/.agents/scripts/security-alerts.sh +0 -31
- package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
- package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
- package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
- package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +0 -6
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export const SANDBOX_TASK_VIEW_STATES = ['not-applicable', 'current', 'finalized-stale', 'unknown'];
|
|
4
|
+
export const SANDBOX_TASK_VIEW_SOURCES = ['active', 'completed', 'unknown'];
|
|
5
|
+
function invalidView(message) {
|
|
6
|
+
return new Error(`SANDBOX_TASK_VIEW_INVALID: ${message}`);
|
|
7
|
+
}
|
|
8
|
+
function receiptIdentity(value, generation, requestId) {
|
|
9
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
10
|
+
return null;
|
|
11
|
+
const receipt = value;
|
|
12
|
+
const binding = receipt.controlBinding;
|
|
13
|
+
if (!binding || typeof binding !== 'object' || Array.isArray(binding))
|
|
14
|
+
return null;
|
|
15
|
+
const controlBinding = binding;
|
|
16
|
+
if (receipt.version !== 2 || typeof receipt.receiptId !== 'string' || receipt.receiptId.length === 0
|
|
17
|
+
|| !Number.isSafeInteger(receipt.revision) || receipt.revision < 0
|
|
18
|
+
|| receipt.lifecycle !== 'done'
|
|
19
|
+
|| controlBinding.generation !== generation
|
|
20
|
+
|| typeof controlBinding.requestId !== 'string'
|
|
21
|
+
|| !/^[a-f0-9-]{16,64}$/u.test(controlBinding.requestId)
|
|
22
|
+
|| (requestId !== undefined && controlBinding.requestId !== requestId))
|
|
23
|
+
return null;
|
|
24
|
+
return {
|
|
25
|
+
receiptId: receipt.receiptId,
|
|
26
|
+
revision: receipt.revision,
|
|
27
|
+
generation,
|
|
28
|
+
requestId: controlBinding.requestId
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function parseSandboxTaskView(value) {
|
|
32
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
33
|
+
throw invalidView('taskView must be an object');
|
|
34
|
+
const view = value;
|
|
35
|
+
const receipt = view.receipt === null ? null : parseSandboxTaskViewReceipt(view.receipt);
|
|
36
|
+
if (!SANDBOX_TASK_VIEW_STATES.includes(view.state)
|
|
37
|
+
|| (view.taskId !== null && typeof view.taskId !== 'string')
|
|
38
|
+
|| (view.observedSource !== null && !SANDBOX_TASK_VIEW_SOURCES.includes(view.observedSource))
|
|
39
|
+
|| (view.reasonCode !== null && typeof view.reasonCode !== 'string')
|
|
40
|
+
|| (view.receipt !== null && receipt === null)) {
|
|
41
|
+
throw invalidView('taskView schema is invalid');
|
|
42
|
+
}
|
|
43
|
+
if (view.state === 'not-applicable' && (view.taskId !== null || view.observedSource !== null || view.receipt !== null)) {
|
|
44
|
+
throw invalidView('not-applicable taskView cannot carry task evidence');
|
|
45
|
+
}
|
|
46
|
+
if (view.state === 'current' && (view.taskId === null || view.observedSource === null)) {
|
|
47
|
+
throw invalidView('current taskView requires taskId and observedSource');
|
|
48
|
+
}
|
|
49
|
+
if (view.state === 'current' && view.observedSource === 'completed' && view.receipt === null) {
|
|
50
|
+
throw invalidView('completed current taskView requires receipt');
|
|
51
|
+
}
|
|
52
|
+
if (view.state === 'finalized-stale' && (view.taskId === null || view.receipt === null)) {
|
|
53
|
+
throw invalidView('finalized-stale taskView requires taskId and receipt');
|
|
54
|
+
}
|
|
55
|
+
return view;
|
|
56
|
+
}
|
|
57
|
+
function parseSandboxTaskViewReceipt(value) {
|
|
58
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
59
|
+
return null;
|
|
60
|
+
const receipt = value;
|
|
61
|
+
if (Object.keys(receipt).sort().join(',') !== 'generation,receiptId,requestId,revision'
|
|
62
|
+
|| typeof receipt.receiptId !== 'string' || receipt.receiptId.length === 0
|
|
63
|
+
|| !Number.isSafeInteger(receipt.revision) || receipt.revision < 0
|
|
64
|
+
|| typeof receipt.generation !== 'string' || receipt.generation.length === 0
|
|
65
|
+
|| typeof receipt.requestId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(receipt.requestId))
|
|
66
|
+
return null;
|
|
67
|
+
return receipt;
|
|
68
|
+
}
|
|
69
|
+
export function taskViewForManifest(params) {
|
|
70
|
+
if (params.mode !== 'task-bound' || !params.taskId) {
|
|
71
|
+
return { state: 'not-applicable', taskId: null, observedSource: null, receipt: null, reasonCode: null };
|
|
72
|
+
}
|
|
73
|
+
const active = path.join(params.repoRoot, '.agents', 'workspace', 'active', params.taskId, 'task.md');
|
|
74
|
+
const completed = path.join(params.repoRoot, '.agents', 'workspace', 'completed', params.taskId, 'task.md');
|
|
75
|
+
const activeExists = fs.existsSync(active);
|
|
76
|
+
const completedExists = fs.existsSync(completed);
|
|
77
|
+
if (activeExists === completedExists) {
|
|
78
|
+
return {
|
|
79
|
+
state: 'unknown', taskId: params.taskId,
|
|
80
|
+
observedSource: activeExists ? 'unknown' : null,
|
|
81
|
+
receipt: null, reasonCode: activeExists ? 'SANDBOX_TASK_VIEW_SOURCE_CONFLICT' : 'SANDBOX_TASK_VIEW_SOURCE_MISSING'
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const source = completedExists ? 'completed' : 'active';
|
|
85
|
+
const receipt = receiptIdentity(params.receipt, params.generation);
|
|
86
|
+
const receiptProvided = params.receipt !== undefined && params.receipt !== null;
|
|
87
|
+
if (source === 'completed' && receipt) {
|
|
88
|
+
return { state: 'current', taskId: params.taskId, observedSource: source, receipt, reasonCode: null };
|
|
89
|
+
}
|
|
90
|
+
if (source === 'active' && receipt) {
|
|
91
|
+
return {
|
|
92
|
+
state: 'finalized-stale', taskId: params.taskId, observedSource: source, receipt,
|
|
93
|
+
reasonCode: 'SANDBOX_TASK_VIEW_SOURCE_STALE'
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (receiptProvided) {
|
|
97
|
+
return {
|
|
98
|
+
state: 'unknown', taskId: params.taskId, observedSource: source, receipt: null,
|
|
99
|
+
reasonCode: 'SANDBOX_TASK_VIEW_RECEIPT_INVALID'
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (source === 'active') {
|
|
103
|
+
return { state: 'current', taskId: params.taskId, observedSource: source, receipt: null, reasonCode: null };
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
state: 'unknown', taskId: params.taskId, observedSource: source, receipt: null,
|
|
107
|
+
reasonCode: 'SANDBOX_TASK_VIEW_EVIDENCE_UNAVAILABLE'
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export function projectSandboxTaskView(input) {
|
|
111
|
+
if (input.mode !== 'task-bound' || !input.taskId) {
|
|
112
|
+
return { state: 'not-applicable', taskId: null, observedSource: null, receipt: null, reasonCode: null };
|
|
113
|
+
}
|
|
114
|
+
const receipt = receiptIdentity(input.receipt, input.generation, input.requestId);
|
|
115
|
+
if (input.source === 'completed' && receipt && input.sourceMatches) {
|
|
116
|
+
return { state: 'current', taskId: input.taskId, observedSource: 'completed', receipt, reasonCode: null };
|
|
117
|
+
}
|
|
118
|
+
if (receipt && input.source === 'active' && !input.sourceMatches) {
|
|
119
|
+
return { state: 'finalized-stale', taskId: input.taskId, observedSource: 'active', receipt, reasonCode: 'SANDBOX_TASK_VIEW_SOURCE_STALE' };
|
|
120
|
+
}
|
|
121
|
+
if (!receipt && input.source === 'active' && input.sourceMatches) {
|
|
122
|
+
return { state: 'current', taskId: input.taskId, observedSource: 'active', receipt: null, reasonCode: null };
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
state: 'unknown',
|
|
126
|
+
taskId: input.taskId,
|
|
127
|
+
observedSource: input.source,
|
|
128
|
+
receipt,
|
|
129
|
+
reasonCode: receipt ? 'SANDBOX_TASK_VIEW_SOURCE_UNCONFIRMED' : 'SANDBOX_TASK_VIEW_EVIDENCE_UNAVAILABLE'
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
export function taskViewAfterFinalization(params) {
|
|
133
|
+
const receipt = receiptIdentity(params.receipt, params.generation, params.requestId);
|
|
134
|
+
if (!receipt) {
|
|
135
|
+
return {
|
|
136
|
+
state: 'unknown', taskId: params.taskId, observedSource: 'unknown', receipt: null,
|
|
137
|
+
reasonCode: 'SANDBOX_TASK_VIEW_RECEIPT_INVALID'
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
state: 'finalized-stale', taskId: params.taskId, observedSource: 'active', receipt,
|
|
142
|
+
reasonCode: 'SANDBOX_TASK_VIEW_FINALIZED'
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export function mergeSandboxTaskView(canonical, previous) {
|
|
146
|
+
if (previous.state !== 'finalized-stale' && previous.state !== 'unknown')
|
|
147
|
+
return canonical;
|
|
148
|
+
return previous;
|
|
149
|
+
}
|
|
150
|
+
export function accessSandboxTaskView(view, effect) {
|
|
151
|
+
if (effect === 'diagnostic' || effect === 'cleanup' || effect === 'recovery') {
|
|
152
|
+
return { allowed: true, state: view.state, reasonCode: view.reasonCode, message: null };
|
|
153
|
+
}
|
|
154
|
+
if (view.state === 'current' && view.observedSource === 'active') {
|
|
155
|
+
return { allowed: true, state: view.state, reasonCode: null, message: null };
|
|
156
|
+
}
|
|
157
|
+
const reasonCode = view.state === 'current'
|
|
158
|
+
? 'SANDBOX_TASK_VIEW_COMPLETED_READ_ONLY'
|
|
159
|
+
: view.reasonCode ?? `SANDBOX_TASK_VIEW_${view.state.toUpperCase().replaceAll('-', '_')}`;
|
|
160
|
+
return {
|
|
161
|
+
allowed: false,
|
|
162
|
+
state: view.state,
|
|
163
|
+
reasonCode,
|
|
164
|
+
message: view.state === 'unknown'
|
|
165
|
+
? 'task view cannot be confirmed; inspect status, recover the original request, or explicitly clean up the sandbox'
|
|
166
|
+
: view.state === 'finalized-stale'
|
|
167
|
+
? 'task view is finalized/stale; exit or clean up the sandbox, or use a validated re-entry'
|
|
168
|
+
: 'completed task views are read-only'
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
export function taskViewFromStatus(value) {
|
|
172
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
173
|
+
throw invalidView('status must be an object');
|
|
174
|
+
const status = value;
|
|
175
|
+
if (status.version !== 3)
|
|
176
|
+
throw invalidView('status version is unsupported');
|
|
177
|
+
const taskView = parseSandboxTaskView(status.taskView);
|
|
178
|
+
if (taskView.receipt && taskView.receipt.generation !== status.generation) {
|
|
179
|
+
throw invalidView('taskView receipt generation does not match status generation');
|
|
180
|
+
}
|
|
181
|
+
return taskView;
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=task-view.js.map
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { runProbe } from '../shell.js';
|
|
3
|
+
import { getAdapter } from './index.js';
|
|
4
|
+
function digest(value) {
|
|
5
|
+
return createHash('sha256').update(value).digest('hex');
|
|
6
|
+
}
|
|
7
|
+
function canonical(value) {
|
|
8
|
+
if (Array.isArray(value))
|
|
9
|
+
return `[${value.map(canonical).join(',')}]`;
|
|
10
|
+
if (!value || typeof value !== 'object')
|
|
11
|
+
return JSON.stringify(value) ?? 'null';
|
|
12
|
+
return `{${Object.entries(value)
|
|
13
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
14
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${canonical(entry)}`)
|
|
15
|
+
.join(',')}}`;
|
|
16
|
+
}
|
|
17
|
+
function text(value) {
|
|
18
|
+
return typeof value === 'string' ? value : value?.toString('utf8') ?? '';
|
|
19
|
+
}
|
|
20
|
+
function safeEndpoint(value) {
|
|
21
|
+
if (!value)
|
|
22
|
+
return 'docker-default';
|
|
23
|
+
try {
|
|
24
|
+
const parsed = new URL(value);
|
|
25
|
+
const hasUnreplayableParts = Boolean(parsed.username || parsed.password || parsed.search || parsed.hash);
|
|
26
|
+
parsed.username = '';
|
|
27
|
+
parsed.password = '';
|
|
28
|
+
parsed.search = '';
|
|
29
|
+
parsed.hash = '';
|
|
30
|
+
const normalized = parsed.toString().replace(/\/$/u, '');
|
|
31
|
+
return hasUnreplayableParts
|
|
32
|
+
? `${normalized}#<redacted>`
|
|
33
|
+
: normalized;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
const normalized = value
|
|
37
|
+
.replace(/\/\/[^/@]+@/u, '//<redacted>@')
|
|
38
|
+
.replace(/[?&](?:token|password|secret|key)=[^&]*/giu, '');
|
|
39
|
+
return /[@?#]/u.test(value) ? `${normalized.replace(/[?#].*$/u, '')}#<redacted>` : normalized;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function lockDomainFor(engine, endpoint) {
|
|
43
|
+
return digest(`${process.platform}\0${process.arch}\0${engine}\0${endpoint}`);
|
|
44
|
+
}
|
|
45
|
+
function contextRoute(context) {
|
|
46
|
+
return {
|
|
47
|
+
kind: 'context',
|
|
48
|
+
selector: { context },
|
|
49
|
+
endpoint: `docker-context://${context}`,
|
|
50
|
+
command: (args) => ({ cmd: 'docker', args: ['--context', context, ...args] })
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function endpointRoute(endpoint, rawEndpoint, unreplayable) {
|
|
54
|
+
return {
|
|
55
|
+
kind: 'endpoint',
|
|
56
|
+
selector: {
|
|
57
|
+
source: 'DOCKER_HOST',
|
|
58
|
+
endpoint,
|
|
59
|
+
...(unreplayable ? { unreplayable } : {})
|
|
60
|
+
},
|
|
61
|
+
endpoint,
|
|
62
|
+
command: (args) => ({ cmd: 'docker', args: ['--host', rawEndpoint, ...args] })
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function tlsEnvironmentReason(env) {
|
|
66
|
+
return env.DOCKER_TLS_VERIFY || env.DOCKER_CERT_PATH || env.DOCKER_TLS
|
|
67
|
+
? 'tls-environment'
|
|
68
|
+
: undefined;
|
|
69
|
+
}
|
|
70
|
+
function routeFor(engine, env) {
|
|
71
|
+
const context = getAdapter(engine).dockerContext;
|
|
72
|
+
if (context) {
|
|
73
|
+
return contextRoute(context);
|
|
74
|
+
}
|
|
75
|
+
if (env.DOCKER_CONTEXT) {
|
|
76
|
+
return contextRoute(env.DOCKER_CONTEXT);
|
|
77
|
+
}
|
|
78
|
+
if (env.DOCKER_HOST) {
|
|
79
|
+
return endpointRoute(safeEndpoint(env.DOCKER_HOST), env.DOCKER_HOST, tlsEnvironmentReason(env));
|
|
80
|
+
}
|
|
81
|
+
return contextRoute('default');
|
|
82
|
+
}
|
|
83
|
+
function validRouteName(value) {
|
|
84
|
+
return /^[A-Za-z0-9][A-Za-z0-9_.@/-]*$/u.test(value);
|
|
85
|
+
}
|
|
86
|
+
function resolveWslDistro(probe, timeoutMs, explicit) {
|
|
87
|
+
if (explicit) {
|
|
88
|
+
if (!validRouteName(explicit))
|
|
89
|
+
throw new Error('SANDBOX_AUTHORITY_ROUTE_CAPTURE_FAILED');
|
|
90
|
+
return explicit;
|
|
91
|
+
}
|
|
92
|
+
const result = probe('wsl.exe', ['--list', '--verbose'], timeoutMs === undefined ? {} : { timeout: timeoutMs });
|
|
93
|
+
if (result.status !== 0)
|
|
94
|
+
throw new Error('SANDBOX_AUTHORITY_ROUTE_CAPTURE_FAILED');
|
|
95
|
+
const output = text(result.stdout).replace(/\0/gu, '').replace(/\r/gu, '');
|
|
96
|
+
const defaultLine = output.split('\n').find((line) => /^\s*\*\s+/u.test(line));
|
|
97
|
+
const distro = defaultLine?.replace(/^\s*\*\s+/u, '').replace(/\s+(?:Running|Stopped)\s+\d+\s*$/iu, '').trim();
|
|
98
|
+
if (!distro || !validRouteName(distro))
|
|
99
|
+
throw new Error('SANDBOX_AUTHORITY_ROUTE_CAPTURE_FAILED');
|
|
100
|
+
return distro;
|
|
101
|
+
}
|
|
102
|
+
function wslRoute(distro, context, env) {
|
|
103
|
+
const endpoint = env.DOCKER_HOST
|
|
104
|
+
? safeEndpoint(env.DOCKER_HOST)
|
|
105
|
+
: `wsl://${distro}/${context ?? 'default'}`;
|
|
106
|
+
const unreplayable = tlsEnvironmentReason(env)
|
|
107
|
+
?? (endpoint.includes('<redacted>') ? 'ambient-endpoint-secret' : undefined);
|
|
108
|
+
return {
|
|
109
|
+
kind: 'wsl2',
|
|
110
|
+
selector: {
|
|
111
|
+
distro,
|
|
112
|
+
...(context ? { context } : {}),
|
|
113
|
+
...(unreplayable ? { unreplayable } : {}),
|
|
114
|
+
...(env.DOCKER_HOST ? { endpoint } : {})
|
|
115
|
+
},
|
|
116
|
+
endpoint,
|
|
117
|
+
command: (args) => ({
|
|
118
|
+
cmd: 'wsl.exe',
|
|
119
|
+
args: [
|
|
120
|
+
'--distribution', distro,
|
|
121
|
+
'--exec', 'docker',
|
|
122
|
+
...(context ? ['--context', context] : []),
|
|
123
|
+
...(env.DOCKER_HOST ? ['--host', env.DOCKER_HOST] : []),
|
|
124
|
+
...args
|
|
125
|
+
]
|
|
126
|
+
})
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function currentWslRoute(env, probe, timeoutMs) {
|
|
130
|
+
const distro = resolveWslDistro(probe, timeoutMs, env.WSL_DISTRO_NAME);
|
|
131
|
+
let context = env.DOCKER_CONTEXT;
|
|
132
|
+
if (!context && !env.DOCKER_HOST) {
|
|
133
|
+
const result = probe('wsl.exe', ['--distribution', distro, '--exec', 'docker', 'context', 'show'], timeoutMs === undefined ? {} : { timeout: timeoutMs });
|
|
134
|
+
context = text(result.stdout).trim();
|
|
135
|
+
if (result.status !== 0 || !validRouteName(context)) {
|
|
136
|
+
throw new Error('SANDBOX_AUTHORITY_ROUTE_CAPTURE_FAILED');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return wslRoute(distro, context, env);
|
|
140
|
+
}
|
|
141
|
+
function routeFromEvidence(evidence) {
|
|
142
|
+
return {
|
|
143
|
+
kind: evidence.routeKind,
|
|
144
|
+
selector: evidence.routeSelector,
|
|
145
|
+
endpoint: evidence.normalizedEndpoint,
|
|
146
|
+
command: (args) => commandForSandboxAuthority(evidence, 'docker', args)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function currentContextRoute(engine, env, probe, timeoutMs) {
|
|
150
|
+
const configured = routeFor(engine, env);
|
|
151
|
+
if (configured.kind !== 'context' || configured.selector.context !== 'default')
|
|
152
|
+
return configured;
|
|
153
|
+
if (env.DOCKER_CONTEXT || env.DOCKER_HOST || getAdapter(engine).dockerContext)
|
|
154
|
+
return configured;
|
|
155
|
+
const result = probe('docker', ['context', 'show'], timeoutMs === undefined ? {} : { timeout: timeoutMs });
|
|
156
|
+
const context = text(result.stdout).trim();
|
|
157
|
+
if (result.status !== 0 || !/^[A-Za-z0-9][A-Za-z0-9_.@/-]*$/u.test(context)) {
|
|
158
|
+
throw new Error('SANDBOX_AUTHORITY_ROUTE_CAPTURE_FAILED');
|
|
159
|
+
}
|
|
160
|
+
return contextRoute(context);
|
|
161
|
+
}
|
|
162
|
+
/** Build a command using only the route persisted with the sandbox authority. */
|
|
163
|
+
export function commandForSandboxAuthority(evidence, cmd, args = []) {
|
|
164
|
+
if (cmd !== 'docker') {
|
|
165
|
+
throw new Error('SANDBOX_AUTHORITY_ROUTE_UNSUPPORTED');
|
|
166
|
+
}
|
|
167
|
+
if (evidence.routeKind === 'context') {
|
|
168
|
+
const context = evidence.routeSelector.context;
|
|
169
|
+
if (!context)
|
|
170
|
+
throw new Error('SANDBOX_AUTHORITY_ROUTE_INVALID');
|
|
171
|
+
return { cmd, args: ['--context', context, ...args] };
|
|
172
|
+
}
|
|
173
|
+
if (evidence.routeKind === 'endpoint') {
|
|
174
|
+
const endpoint = evidence.routeSelector.endpoint;
|
|
175
|
+
if (!endpoint || endpoint.includes('<redacted>') || evidence.routeSelector.unreplayable) {
|
|
176
|
+
throw new Error('SANDBOX_AUTHORITY_ROUTE_UNREPLAYABLE');
|
|
177
|
+
}
|
|
178
|
+
return { cmd, args: ['--host', endpoint, ...args] };
|
|
179
|
+
}
|
|
180
|
+
if (evidence.routeKind === 'wsl2') {
|
|
181
|
+
const distro = evidence.routeSelector.distro;
|
|
182
|
+
const context = evidence.routeSelector.context;
|
|
183
|
+
const endpoint = evidence.routeSelector.endpoint;
|
|
184
|
+
if (!distro || evidence.routeSelector.unreplayable || endpoint?.includes('<redacted>')) {
|
|
185
|
+
throw new Error('SANDBOX_AUTHORITY_ROUTE_UNREPLAYABLE');
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
cmd: 'wsl.exe',
|
|
189
|
+
args: [
|
|
190
|
+
'--distribution', distro,
|
|
191
|
+
'--exec', cmd,
|
|
192
|
+
...(context ? ['--context', context] : []),
|
|
193
|
+
...(endpoint && !endpoint.startsWith('wsl://') ? ['--host', endpoint] : []),
|
|
194
|
+
...args
|
|
195
|
+
]
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return { cmd, args: ['--context', 'default', ...args] };
|
|
199
|
+
}
|
|
200
|
+
export function authorityVersionArgs() {
|
|
201
|
+
return ['version', '--format', '{{json .Server}}'];
|
|
202
|
+
}
|
|
203
|
+
export function captureSandboxAuthority(engine, options = {}) {
|
|
204
|
+
const env = options.env ?? process.env;
|
|
205
|
+
const probe = options.probe ?? runProbe;
|
|
206
|
+
const route = options.route
|
|
207
|
+
? routeFromEvidence(options.route)
|
|
208
|
+
: engine === 'wsl2'
|
|
209
|
+
? currentWslRoute(env, probe, options.timeoutMs)
|
|
210
|
+
: currentContextRoute(engine, env, probe, options.timeoutMs);
|
|
211
|
+
function probeJson(args) {
|
|
212
|
+
const command = options.route
|
|
213
|
+
? commandForSandboxAuthority(options.route, 'docker', args)
|
|
214
|
+
: route.command(args);
|
|
215
|
+
try {
|
|
216
|
+
const response = probe(command.cmd, command.args, options.timeoutMs === undefined ? {} : { timeout: options.timeoutMs });
|
|
217
|
+
if (response.status !== 0)
|
|
218
|
+
throw new Error('SANDBOX_AUTHORITY_CAPTURE_FAILED');
|
|
219
|
+
return JSON.parse(text(response.stdout).trim());
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
throw new Error('SANDBOX_AUTHORITY_CAPTURE_FAILED');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const server = probeJson(authorityVersionArgs());
|
|
226
|
+
if (!server || typeof server !== 'object' || Array.isArray(server)) {
|
|
227
|
+
throw new Error('SANDBOX_AUTHORITY_CAPTURE_FAILED');
|
|
228
|
+
}
|
|
229
|
+
const record = server;
|
|
230
|
+
const api = typeof record.ApiVersion === 'string' ? /^(\d+)\.(\d+)$/u.exec(record.ApiVersion) : null;
|
|
231
|
+
if (!api)
|
|
232
|
+
throw new Error('SANDBOX_AUTHORITY_CAPTURE_FAILED');
|
|
233
|
+
// Docker version exposes API metadata; the daemon ID belongs to Docker info.
|
|
234
|
+
const serverId = probeJson(['info', '--format', '{{json .ID}}']);
|
|
235
|
+
if (typeof serverId !== 'string' || !serverId.trim())
|
|
236
|
+
throw new Error('SANDBOX_AUTHORITY_CAPTURE_FAILED');
|
|
237
|
+
const normalizedEndpoint = route.endpoint;
|
|
238
|
+
const evidenceWithoutFingerprint = {
|
|
239
|
+
version: 1,
|
|
240
|
+
provider: engine,
|
|
241
|
+
lockDomain: options.lockDomain ?? options.route?.lockDomain ?? lockDomainFor(engine, normalizedEndpoint),
|
|
242
|
+
routeKind: route.kind,
|
|
243
|
+
routeSelector: route.selector,
|
|
244
|
+
normalizedEndpoint,
|
|
245
|
+
endpointFingerprint: digest(normalizedEndpoint),
|
|
246
|
+
daemonIdentity: { kind: 'docker-server-id', fingerprint: digest(serverId) },
|
|
247
|
+
apiVersion: { major: Number(api[1]), minor: Number(api[2]) }
|
|
248
|
+
};
|
|
249
|
+
return {
|
|
250
|
+
...evidenceWithoutFingerprint,
|
|
251
|
+
authorityFingerprint: digest(canonical(evidenceWithoutFingerprint))
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
export function verifySandboxAuthority(expected, actual) {
|
|
255
|
+
if (!expected)
|
|
256
|
+
return { state: 'unknown', reason: 'SANDBOX_AUTHORITY_EVIDENCE_MISSING' };
|
|
257
|
+
if (!actual)
|
|
258
|
+
return { state: 'unknown', reason: 'SANDBOX_AUTHORITY_UNAVAILABLE' };
|
|
259
|
+
if (expected.authorityFingerprint !== actual.authorityFingerprint
|
|
260
|
+
|| expected.lockDomain !== actual.lockDomain
|
|
261
|
+
|| expected.routeKind !== actual.routeKind
|
|
262
|
+
|| expected.endpointFingerprint !== actual.endpointFingerprint
|
|
263
|
+
|| expected.daemonIdentity.fingerprint !== actual.daemonIdentity.fingerprint) {
|
|
264
|
+
return { state: 'conflict', reason: 'SANDBOX_AUTHORITY_DRIFT' };
|
|
265
|
+
}
|
|
266
|
+
return { state: 'verified' };
|
|
267
|
+
}
|
|
268
|
+
export function isSandboxAuthorityEvidence(value) {
|
|
269
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
270
|
+
return false;
|
|
271
|
+
const evidence = value;
|
|
272
|
+
const selector = evidence.routeSelector;
|
|
273
|
+
const daemon = evidence.daemonIdentity;
|
|
274
|
+
const api = evidence.apiVersion;
|
|
275
|
+
return evidence.version === 1
|
|
276
|
+
&& typeof evidence.provider === 'string'
|
|
277
|
+
&& /^[a-f0-9]{64}$/u.test(evidence.lockDomain)
|
|
278
|
+
&& ['default', 'context', 'endpoint', 'wsl2'].includes(evidence.routeKind)
|
|
279
|
+
&& !!selector && typeof selector === 'object' && !Array.isArray(selector)
|
|
280
|
+
&& Object.values(selector).every((entry) => typeof entry === 'string')
|
|
281
|
+
&& typeof evidence.normalizedEndpoint === 'string'
|
|
282
|
+
&& /^[a-f0-9]{64}$/u.test(evidence.endpointFingerprint)
|
|
283
|
+
&& !!daemon && daemon.kind === 'docker-server-id'
|
|
284
|
+
&& /^[a-f0-9]{64}$/u.test(daemon.fingerprint)
|
|
285
|
+
&& !!api && Number.isSafeInteger(api.major) && api.major >= 0
|
|
286
|
+
&& Number.isSafeInteger(api.minor) && api.minor >= 0
|
|
287
|
+
&& /^[a-f0-9]{64}$/u.test(evidence.authorityFingerprint);
|
|
288
|
+
}
|
|
289
|
+
//# sourceMappingURL=authority.js.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { commandForEngine, runProbe } from '../shell.js';
|
|
2
|
+
import { commandForSandboxAuthority } from './authority.js';
|
|
3
|
+
const FULL_CONTAINER_ID = /^[a-f0-9]{64}$/u;
|
|
4
|
+
function outputText(value) {
|
|
5
|
+
return typeof value === 'string' ? value : value?.toString('utf8') ?? '';
|
|
6
|
+
}
|
|
7
|
+
function unknown(reason) {
|
|
8
|
+
return { state: 'unknown', reason };
|
|
9
|
+
}
|
|
10
|
+
export function buildExactContainerListArgs(id) {
|
|
11
|
+
if (!FULL_CONTAINER_ID.test(id))
|
|
12
|
+
throw new Error('SANDBOX_CONTAINER_ID_NOT_FULL');
|
|
13
|
+
return ['container', 'ls', '--all', '--no-trunc', '--filter', `id=${id}`, '--format', '{{.ID}}'];
|
|
14
|
+
}
|
|
15
|
+
function buildExactContainerInspectArgs(id) {
|
|
16
|
+
return ['container', 'inspect', '--format', '{{json .}}', id];
|
|
17
|
+
}
|
|
18
|
+
function runDockerProbe(engine, args, options) {
|
|
19
|
+
const command = options.authority
|
|
20
|
+
? commandForSandboxAuthority(options.authority, 'docker', args)
|
|
21
|
+
: commandForEngine(engine, 'docker', args);
|
|
22
|
+
return (options.probe ?? runProbe)(command.cmd, command.args, options.timeoutMs === undefined ? {} : { timeout: options.timeoutMs });
|
|
23
|
+
}
|
|
24
|
+
function parseExactList(stdout, expectedId) {
|
|
25
|
+
const rows = stdout.split(/\r?\n/u).map((row) => row.trim()).filter(Boolean);
|
|
26
|
+
if (rows.length === 0)
|
|
27
|
+
return 'empty';
|
|
28
|
+
if (rows.length !== 1)
|
|
29
|
+
return 'exact container query returned multiple rows';
|
|
30
|
+
if (!FULL_CONTAINER_ID.test(rows[0]))
|
|
31
|
+
return 'exact container query returned a malformed ID';
|
|
32
|
+
if (rows[0] !== expectedId)
|
|
33
|
+
return 'conflict';
|
|
34
|
+
return 'present';
|
|
35
|
+
}
|
|
36
|
+
function parseExactInspect(stdout, expectedId) {
|
|
37
|
+
let value;
|
|
38
|
+
try {
|
|
39
|
+
value = JSON.parse(stdout);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return unknown('exact container inspection returned malformed JSON');
|
|
43
|
+
}
|
|
44
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
45
|
+
return unknown('exact container inspection returned an incomplete object');
|
|
46
|
+
}
|
|
47
|
+
const inspected = value;
|
|
48
|
+
if (inspected.Id !== expectedId || typeof inspected.State?.Running !== 'boolean'
|
|
49
|
+
|| !inspected.Config?.Labels || typeof inspected.Config.Labels !== 'object'
|
|
50
|
+
|| Array.isArray(inspected.Config.Labels)) {
|
|
51
|
+
return unknown('exact container inspection identity is incomplete');
|
|
52
|
+
}
|
|
53
|
+
const rawLabels = inspected.Config.Labels;
|
|
54
|
+
if (Object.values(rawLabels).some((value) => typeof value !== 'string')) {
|
|
55
|
+
return unknown('exact container inspection labels are malformed');
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
state: 'found',
|
|
59
|
+
id: expectedId,
|
|
60
|
+
running: inspected.State.Running,
|
|
61
|
+
labels: { ...rawLabels },
|
|
62
|
+
evidence: 'exact-present'
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function discoverExactContainer(engine, id, options = {}) {
|
|
66
|
+
if (!FULL_CONTAINER_ID.test(id))
|
|
67
|
+
return unknown('container identity is not a full 64-character ID');
|
|
68
|
+
let listed;
|
|
69
|
+
try {
|
|
70
|
+
listed = runDockerProbe(engine, buildExactContainerListArgs(id), options);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return unknown('exact container query failed before producing a result');
|
|
74
|
+
}
|
|
75
|
+
if (listed.status !== 0) {
|
|
76
|
+
return unknown(`exact container query exited with ${listed.status ?? 'unknown status'}`);
|
|
77
|
+
}
|
|
78
|
+
const listState = parseExactList(outputText(listed.stdout), id);
|
|
79
|
+
if (listState === 'empty')
|
|
80
|
+
return { state: 'absent', id, evidence: 'exact-empty' };
|
|
81
|
+
if (listState === 'conflict')
|
|
82
|
+
return { state: 'conflict', reason: 'exact container query returned a different ID' };
|
|
83
|
+
if (listState !== 'present')
|
|
84
|
+
return unknown(listState);
|
|
85
|
+
let inspected;
|
|
86
|
+
try {
|
|
87
|
+
inspected = runDockerProbe(engine, buildExactContainerInspectArgs(id), options);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return unknown('exact container inspection failed before producing a result');
|
|
91
|
+
}
|
|
92
|
+
if (inspected.status !== 0) {
|
|
93
|
+
return unknown(`exact container inspection exited with ${inspected.status ?? 'unknown status'}`);
|
|
94
|
+
}
|
|
95
|
+
return parseExactInspect(outputText(inspected.stdout).trim(), id);
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=docker-exact-discovery.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PUBLIC_CLI_ROUTE_SELECTORS } from '../internal/cli-route-inventory.js';
|
|
1
2
|
const USAGE = `Usage: ai sandbox <command> [options]
|
|
2
3
|
|
|
3
4
|
Commands:
|
|
@@ -26,6 +27,7 @@ Commands:
|
|
|
26
27
|
vm status|start|stop Manage the sandbox VM (macOS) or check the backend (Windows)
|
|
27
28
|
|
|
28
29
|
Run 'ai sandbox <command> --help' for details.`;
|
|
30
|
+
const SANDBOX_OPERATIONS = PUBLIC_CLI_ROUTE_SELECTORS.sandbox;
|
|
29
31
|
export async function runSandbox(args) {
|
|
30
32
|
const [subcommand, ...rest] = args;
|
|
31
33
|
if (!subcommand) {
|
|
@@ -37,6 +39,9 @@ export async function runSandbox(args) {
|
|
|
37
39
|
process.stdout.write(`${USAGE}\n`);
|
|
38
40
|
return;
|
|
39
41
|
}
|
|
42
|
+
if (!SANDBOX_OPERATIONS.includes(subcommand)) {
|
|
43
|
+
throw new Error(`Unknown sandbox command: ${subcommand}`);
|
|
44
|
+
}
|
|
40
45
|
switch (subcommand) {
|
|
41
46
|
case 'create': {
|
|
42
47
|
const { create } = await import('./commands/create.js');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { prepareCompletedReentry, publishCompletedReentry, waitForCompletedReentry } from './control/completed-reentry.js';
|
|
1
2
|
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { spawn } from 'node:child_process';
|
|
@@ -441,7 +442,7 @@ function recoveryTaskSources(repoRoot, taskId) {
|
|
|
441
442
|
}
|
|
442
443
|
return { mountPaths, accessiblePaths: [completedSource] };
|
|
443
444
|
}
|
|
444
|
-
function startCompletedSandboxContainer(params) {
|
|
445
|
+
export function startCompletedSandboxContainer(params) {
|
|
445
446
|
const taskSources = recoveryTaskSources(params.config.repoRoot, params.taskId);
|
|
446
447
|
const completedSource = taskSources.accessiblePaths[0];
|
|
447
448
|
const activeRoot = path.join(params.config.repoRoot, '.agents', 'workspace', 'active');
|
|
@@ -879,7 +880,17 @@ export async function ensureSandboxReady(params) {
|
|
|
879
880
|
throw new Error('Explicit container recreation requested.');
|
|
880
881
|
}
|
|
881
882
|
if (!params.row.running) {
|
|
883
|
+
let completedReentry = null;
|
|
882
884
|
if (params.reentry === 'completed' && params.workspace?.mode === 'task-bound') {
|
|
885
|
+
const control = sandboxControlPaths({
|
|
886
|
+
base: params.config.controlBase, project: params.config.project,
|
|
887
|
+
container: params.row.name, identity: params.workspace
|
|
888
|
+
});
|
|
889
|
+
const manifest = readSandboxControlManifest(control.manifestPath);
|
|
890
|
+
if (manifest.taskId !== params.workspace.taskId || manifest.branch !== params.branch) {
|
|
891
|
+
throw new Error('SANDBOX_COMPLETED_REENTRY_EVIDENCE_INVALID');
|
|
892
|
+
}
|
|
893
|
+
completedReentry = { manifest, evidence: await prepareCompletedReentry(manifest) };
|
|
883
894
|
startCompletedSandboxContainer({
|
|
884
895
|
config: params.config,
|
|
885
896
|
engine: params.engine,
|
|
@@ -943,6 +954,15 @@ export async function ensureSandboxReady(params) {
|
|
|
943
954
|
if (final.findings.length > 0) {
|
|
944
955
|
throw new Error(describeFindings(final.findings));
|
|
945
956
|
}
|
|
957
|
+
if (completedReentry) {
|
|
958
|
+
const { manifest, evidence } = completedReentry;
|
|
959
|
+
const current = await prepareCompletedReentry(manifest);
|
|
960
|
+
if (JSON.stringify(current) !== JSON.stringify(evidence)) {
|
|
961
|
+
throw new Error('SANDBOX_COMPLETED_REENTRY_EVIDENCE_INVALID');
|
|
962
|
+
}
|
|
963
|
+
publishCompletedReentry(manifest, evidence);
|
|
964
|
+
await waitForCompletedReentry(manifest, evidence);
|
|
965
|
+
}
|
|
946
966
|
return { container: params.row.name, path: 'recovered', warnings };
|
|
947
967
|
}
|
|
948
968
|
const initial = assess({
|
|
@@ -195,7 +195,8 @@ export function finalizeSandboxControlManifest(setup, identity) {
|
|
|
195
195
|
const manifest = {
|
|
196
196
|
...setup.manifestDraft,
|
|
197
197
|
engine: identity.engine,
|
|
198
|
-
containerIdentity: { id: identity.id, labels: { ...identity.labels } }
|
|
198
|
+
containerIdentity: { id: identity.id, labels: { ...identity.labels } },
|
|
199
|
+
authorityEvidence: identity.authorityEvidence
|
|
199
200
|
};
|
|
200
201
|
const temporary = `${setup.manifestPath}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`;
|
|
201
202
|
try {
|
|
@@ -387,7 +387,10 @@ function verifyWorktreePermit(permit, options = {}) {
|
|
|
387
387
|
: inspectWorktree(permit.snapshot.worktree, options);
|
|
388
388
|
if (current.status === 'failed')
|
|
389
389
|
throw new Error(`Unable to verify worktree permit: ${current.message}`);
|
|
390
|
-
|
|
390
|
+
const sameTarget = current.snapshot.worktree === permit.snapshot.worktree
|
|
391
|
+
&& current.snapshot.branch === permit.snapshot.branch;
|
|
392
|
+
if (!sameTarget || (permit.mode === 'clean'
|
|
393
|
+
&& (current.snapshot.identity !== permit.snapshot.identity || current.status !== 'clean'))) {
|
|
391
394
|
throw new Error(`Worktree changed after authorization: ${permit.snapshot.worktree}`);
|
|
392
395
|
}
|
|
393
396
|
return current.snapshot;
|