@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
|
@@ -16,6 +16,7 @@ import { fetchSandboxRows, selectSandboxContainer } from './list-running.ts';
|
|
|
16
16
|
import { resolveTools, toolConfigDirCandidates } from '../tools.ts';
|
|
17
17
|
import { detectEngine } from '../engine.ts';
|
|
18
18
|
import { resolveSandboxTarget } from '../workspace-identity.ts';
|
|
19
|
+
import { readSandboxControlStatusForRow } from './control-status.ts';
|
|
19
20
|
|
|
20
21
|
const USAGE = `Usage: ai sandbox show <branch | TASK-id | N>
|
|
21
22
|
|
|
@@ -93,6 +94,21 @@ export function show(args: string[] = []): void {
|
|
|
93
94
|
process.stdout.write(` Mode: ${container?.workspaceMode ?? 'legacy-invalid'}\n`);
|
|
94
95
|
process.stdout.write(` Task: ${container?.taskId ?? '-'}\n`);
|
|
95
96
|
|
|
97
|
+
p.log.step('Control status');
|
|
98
|
+
const controlStatus = container
|
|
99
|
+
? readSandboxControlStatusForRow(config, container)
|
|
100
|
+
: null;
|
|
101
|
+
if (!controlStatus) {
|
|
102
|
+
p.log.warn(' Control status unavailable');
|
|
103
|
+
} else {
|
|
104
|
+
process.stdout.write(` Health: ${controlStatus.state}\n`);
|
|
105
|
+
process.stdout.write(` Task view: ${controlStatus.taskView.state}\n`);
|
|
106
|
+
process.stdout.write(` Task source: ${controlStatus.taskView.observedSource ?? '-'}\n`);
|
|
107
|
+
if (controlStatus.taskView.reasonCode) {
|
|
108
|
+
process.stdout.write(` Task view reason: ${controlStatus.taskView.reasonCode}\n`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
96
112
|
for (const tool of detail.toolStates) {
|
|
97
113
|
p.log.step(`${tool.name} state`);
|
|
98
114
|
if (tool.entries.length === 0) {
|
|
@@ -22,9 +22,66 @@ import type { ProcessIdentity } from '../../server/process-state.ts';
|
|
|
22
22
|
import { normalizeAgentToken } from '../../agent-clients/tokens.ts';
|
|
23
23
|
import { readSandboxControlPayload, readSandboxControlStatus } from './state.ts';
|
|
24
24
|
import type { TaskCreateCandidateV1 } from '../../task/create.ts';
|
|
25
|
+
import { accessSandboxTaskView, taskViewFromStatus, type TaskViewAccessEffect } from './task-view.ts';
|
|
25
26
|
|
|
26
27
|
const SANDBOX_CONTROL_RESPONSE_SETTLE_MS = 250;
|
|
27
28
|
|
|
29
|
+
type SandboxControlEnvironmentClassification = Readonly<{
|
|
30
|
+
kind: 'direct' | 'controlled' | 'invalid';
|
|
31
|
+
mode?: 'task-bound' | 'branch-only';
|
|
32
|
+
code?: 'TASK_CONTROL_TRANSPORT_INVALID';
|
|
33
|
+
message?: string;
|
|
34
|
+
}>;
|
|
35
|
+
|
|
36
|
+
export function classifySandboxControlEnvironment(
|
|
37
|
+
env: NodeJS.ProcessEnv = process.env
|
|
38
|
+
): SandboxControlEnvironmentClassification {
|
|
39
|
+
const controlKeys = [
|
|
40
|
+
'AGENT_INFRA_CONTROL_TOKEN',
|
|
41
|
+
'AGENT_INFRA_CONTROL_GENERATION',
|
|
42
|
+
'AGENT_INFRA_CONTROL_DIR',
|
|
43
|
+
'AGENT_INFRA_CONTROL_STATUS_DIR'
|
|
44
|
+
] as const;
|
|
45
|
+
const hasControlMarker = controlKeys.some((key) => Boolean(env[key]));
|
|
46
|
+
const hasTaskIdentity = Boolean(env.AGENT_INFRA_TASK_ID);
|
|
47
|
+
const hasRuntime = Boolean(env.AGENT_INFRA_RUNTIME_DIR);
|
|
48
|
+
const hasExecutorMarker = Boolean(env.AGENT_INFRA_EXECUTOR_MANIFEST);
|
|
49
|
+
const hasControllerBinding = Boolean(env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING);
|
|
50
|
+
const hasAnyMarker = hasControlMarker || hasTaskIdentity || hasRuntime || hasExecutorMarker || hasControllerBinding;
|
|
51
|
+
const hasCompleteControl = controlKeys.every((key) => Boolean(env[key]));
|
|
52
|
+
|
|
53
|
+
if (!hasAnyMarker) return { kind: 'direct' };
|
|
54
|
+
if (hasExecutorMarker) {
|
|
55
|
+
return {
|
|
56
|
+
kind: 'invalid',
|
|
57
|
+
code: 'TASK_CONTROL_TRANSPORT_INVALID',
|
|
58
|
+
message: 'executor context is only valid for sandbox-control execute'
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (hasControllerBinding) {
|
|
62
|
+
return {
|
|
63
|
+
kind: 'invalid',
|
|
64
|
+
code: 'TASK_CONTROL_TRANSPORT_INVALID',
|
|
65
|
+
message: 'sandbox client control configuration is incomplete or conflicting'
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (!hasCompleteControl) {
|
|
69
|
+
return {
|
|
70
|
+
kind: 'invalid',
|
|
71
|
+
code: 'TASK_CONTROL_TRANSPORT_INVALID',
|
|
72
|
+
message: 'sandbox client control configuration is incomplete or conflicting'
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (hasTaskIdentity !== hasRuntime) {
|
|
76
|
+
return {
|
|
77
|
+
kind: 'invalid',
|
|
78
|
+
code: 'TASK_CONTROL_TRANSPORT_INVALID',
|
|
79
|
+
message: 'sandbox task identity and runtime binding must be provided together'
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return { kind: 'controlled', mode: hasTaskIdentity ? 'task-bound' : 'branch-only' };
|
|
83
|
+
}
|
|
84
|
+
|
|
28
85
|
export class SandboxControlClientError extends Error {
|
|
29
86
|
readonly detail: SandboxControlError;
|
|
30
87
|
readonly accepted: boolean;
|
|
@@ -52,7 +109,19 @@ function clientError(
|
|
|
52
109
|
throw new SandboxControlClientError({ code, message: `${code}: ${message}`, retryable }, accepted, requestId);
|
|
53
110
|
}
|
|
54
111
|
|
|
55
|
-
function
|
|
112
|
+
function hasTaskBoundMarker(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
113
|
+
return Boolean(env.AGENT_INFRA_TASK_ID)
|
|
114
|
+
&& Boolean(env.AGENT_INFRA_CONTROL_TOKEN || env.AGENT_INFRA_CONTROL_GENERATION
|
|
115
|
+
|| env.AGENT_INFRA_CONTROL_STATUS_DIR || env.AGENT_INFRA_RUNTIME_DIR || env.AGENT_INFRA_CONTROL_DIR);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function preflight(
|
|
119
|
+
statusDir: string,
|
|
120
|
+
generation: string,
|
|
121
|
+
taskViewEffect: TaskViewAccessEffect | null = null,
|
|
122
|
+
now = Date.now(),
|
|
123
|
+
env: NodeJS.ProcessEnv = process.env
|
|
124
|
+
): void {
|
|
56
125
|
let status;
|
|
57
126
|
try {
|
|
58
127
|
status = readSandboxControlStatus(statusDir);
|
|
@@ -71,9 +140,29 @@ function preflight(statusDir: string, generation: string, now = Date.now()): voi
|
|
|
71
140
|
clientError(code, 'broker is parked until the host restores a valid binding', true);
|
|
72
141
|
}
|
|
73
142
|
if (status.state !== 'healthy') clientError('SANDBOX_CONTROL_BROKER_UNAVAILABLE', 'broker is not ready', true);
|
|
143
|
+
if (taskViewEffect && hasTaskBoundMarker(env)) {
|
|
144
|
+
const view = taskViewFromStatus(status);
|
|
145
|
+
if (view.taskId !== env.AGENT_INFRA_TASK_ID) {
|
|
146
|
+
clientError('SANDBOX_TASK_VIEW_TASK_ID_MISMATCH', 'task view task id does not match the sandbox task marker', false);
|
|
147
|
+
}
|
|
148
|
+
const access = accessSandboxTaskView(view, taskViewEffect);
|
|
149
|
+
if (!access.allowed) {
|
|
150
|
+
clientError(
|
|
151
|
+
access.reasonCode ?? 'SANDBOX_TASK_VIEW_DENIED',
|
|
152
|
+
access.message ?? 'task view denied',
|
|
153
|
+
false
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
74
157
|
}
|
|
75
158
|
|
|
76
|
-
function
|
|
159
|
+
function taskViewEffectForRequest(request: SandboxControlRequest): TaskViewAccessEffect | null {
|
|
160
|
+
if (request.family === 'task-lifecycle' || request.family === 'task-finalization') return 'progress';
|
|
161
|
+
if (request.family === 'task-orchestration') return request.args[1] === 'status' ? 'diagnostic' : 'progress';
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function parseResponse(raw: string, id: string, accepted = false): SandboxControlResponse {
|
|
77
166
|
const response = JSON.parse(raw) as SandboxControlResponse;
|
|
78
167
|
if (response.version !== 2 || response.id !== id
|
|
79
168
|
|| !['completed', 'rejected'].includes(response.phase)
|
|
@@ -81,13 +170,13 @@ function parseResponse(raw: string, id: string): SandboxControlResponse {
|
|
|
81
170
|
|| (response.outputState !== undefined && response.outputState !== 'available' && response.outputState !== 'unavailable')
|
|
82
171
|
|| (response.outputState === 'available' && !response.payload)
|
|
83
172
|
|| (response.outputState === 'unavailable' && response.payload !== null && response.payload !== undefined)) {
|
|
84
|
-
clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker response is invalid', false,
|
|
173
|
+
clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker response is invalid', false, accepted, id);
|
|
85
174
|
}
|
|
86
175
|
return response;
|
|
87
176
|
}
|
|
88
177
|
|
|
89
|
-
function readPublishedResponse(raw: string, id: string, channelDir: string): SandboxControlResponse {
|
|
90
|
-
const response = parseResponse(raw, id);
|
|
178
|
+
function readPublishedResponse(raw: string, id: string, channelDir: string, accepted = false): SandboxControlResponse {
|
|
179
|
+
const response = parseResponse(raw, id, accepted);
|
|
91
180
|
if (response.outputState !== 'available') return response;
|
|
92
181
|
const filePath = path.join(channelDir, 'responses', `${id}.payload.json`);
|
|
93
182
|
let payload;
|
|
@@ -122,7 +211,8 @@ function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly
|
|
|
122
211
|
}>): SandboxControlResponse {
|
|
123
212
|
const channelDir = params.channelDir ?? process.env.AGENT_INFRA_CONTROL_DIR ?? '/run/agent-infra/control';
|
|
124
213
|
const statusDir = params.statusDir ?? process.env.AGENT_INFRA_CONTROL_STATUS_DIR ?? '/run/agent-infra/control-status';
|
|
125
|
-
|
|
214
|
+
const taskViewEffect = taskViewEffectForRequest(request);
|
|
215
|
+
preflight(statusDir, request.generation, taskViewEffect);
|
|
126
216
|
const encoded = `${JSON.stringify(request)}\n`;
|
|
127
217
|
if (Buffer.byteLength(encoded, 'utf8') > SANDBOX_CONTROL_MAX_BYTES) {
|
|
128
218
|
clientError('SANDBOX_CONTROL_REQUEST_TOO_LARGE', 'request exceeds the control limit', false, false, request.id);
|
|
@@ -154,7 +244,9 @@ function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly
|
|
|
154
244
|
}
|
|
155
245
|
let response: SandboxControlResponse;
|
|
156
246
|
try {
|
|
157
|
-
|
|
247
|
+
// The request-specific terminal proves the broker claimed the request; preserve recovery evidence if marker cleanup won the race.
|
|
248
|
+
accepted = true;
|
|
249
|
+
response = readPublishedResponse(raw, request.id, channelDir, accepted);
|
|
158
250
|
malformedResponseRaw = null;
|
|
159
251
|
} catch (error) {
|
|
160
252
|
if (!(error instanceof SyntaxError)) throw error;
|
|
@@ -176,7 +268,7 @@ function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly
|
|
|
176
268
|
}
|
|
177
269
|
if (!accepted) {
|
|
178
270
|
try {
|
|
179
|
-
preflight(statusDir, request.generation);
|
|
271
|
+
preflight(statusDir, request.generation, taskViewEffect);
|
|
180
272
|
} catch (error) {
|
|
181
273
|
if (cancelPendingRequest(requestPath)) throw error;
|
|
182
274
|
clientError(
|
|
@@ -236,7 +328,9 @@ export function recoverSandboxControl(requestId: string, params: Readonly<{
|
|
|
236
328
|
}
|
|
237
329
|
let response: SandboxControlResponse;
|
|
238
330
|
try {
|
|
239
|
-
|
|
331
|
+
// The request-specific terminal proves the broker claimed the request; preserve recovery evidence if marker cleanup won the race.
|
|
332
|
+
accepted = true;
|
|
333
|
+
response = readPublishedResponse(raw, requestId, channelDir, accepted);
|
|
240
334
|
malformedResponseRaw = null;
|
|
241
335
|
} catch (error) {
|
|
242
336
|
if (!(error instanceof SyntaxError)) throw error;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { resolveTaskWorkspace } from '../task-resolver.ts';
|
|
4
|
+
import { assertGitWorktreeBinding } from '../../git/worktree-identity.ts';
|
|
5
|
+
import { parseTypedTaskFrontmatter } from '../../task/frontmatter.ts';
|
|
6
|
+
import { readTaskFinalizationReceipt } from '../../task/finalization.ts';
|
|
7
|
+
import { inspectSandboxControlContainer } from './container-identity.ts';
|
|
8
|
+
import type { SandboxControlManifest } from './protocol.ts';
|
|
9
|
+
import { atomicWriteJson, readSandboxControlStatus } from './state.ts';
|
|
10
|
+
import { taskViewForManifest, type SandboxTaskView } from './task-view.ts';
|
|
11
|
+
|
|
12
|
+
type ReentryEvidence = Readonly<{
|
|
13
|
+
taskId: string;
|
|
14
|
+
generation: string;
|
|
15
|
+
containerId: string;
|
|
16
|
+
branch: string;
|
|
17
|
+
worktreeRoot: string;
|
|
18
|
+
source: string;
|
|
19
|
+
sourceDevice: string;
|
|
20
|
+
sourceInode: string;
|
|
21
|
+
receipt: NonNullable<SandboxTaskView['receipt']>;
|
|
22
|
+
}>;
|
|
23
|
+
|
|
24
|
+
function evidencePath(manifest: SandboxControlManifest): string {
|
|
25
|
+
return path.join(path.dirname(manifest.processingDir), 'completed-reentry.json');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function invalid(): Error {
|
|
29
|
+
return new Error('SANDBOX_COMPLETED_REENTRY_EVIDENCE_INVALID');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function prepareCompletedReentry(
|
|
33
|
+
manifest: SandboxControlManifest,
|
|
34
|
+
inspect: typeof inspectSandboxControlContainer = inspectSandboxControlContainer
|
|
35
|
+
): Promise<ReentryEvidence> {
|
|
36
|
+
if (manifest.mode !== 'task-bound' || !manifest.taskId) throw invalid();
|
|
37
|
+
assertGitWorktreeBinding(manifest.repoRoot, manifest.worktreeRoot, manifest.branch);
|
|
38
|
+
const container = await inspect(manifest);
|
|
39
|
+
if (container.state !== 'found' || container.id !== manifest.containerIdentity.id) throw invalid();
|
|
40
|
+
const task = resolveTaskWorkspace(manifest.taskId, manifest.repoRoot);
|
|
41
|
+
const metadata = parseTypedTaskFrontmatter(fs.readFileSync(task.taskMd, 'utf8'));
|
|
42
|
+
if (task.state !== 'completed' || task.branch !== manifest.branch
|
|
43
|
+
|| metadata.id !== manifest.taskId || metadata.status !== 'completed') throw invalid();
|
|
44
|
+
const receipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId);
|
|
45
|
+
if (!receipt || receipt.taskId !== manifest.taskId) throw invalid();
|
|
46
|
+
const view = taskViewForManifest({ ...manifest, receipt });
|
|
47
|
+
if (view.state !== 'current' || view.observedSource !== 'completed' || !view.receipt) throw invalid();
|
|
48
|
+
const status = readSandboxControlStatus(manifest.publicStatusDir);
|
|
49
|
+
const previous = status.taskView;
|
|
50
|
+
if (status.generation !== manifest.generation || status.activeRequestId !== null
|
|
51
|
+
|| previous.taskId !== manifest.taskId || !previous.receipt
|
|
52
|
+
|| previous.receipt.receiptId !== view.receipt.receiptId
|
|
53
|
+
|| previous.receipt.generation !== view.receipt.generation
|
|
54
|
+
|| previous.receipt.requestId !== view.receipt.requestId
|
|
55
|
+
|| previous.receipt.revision > view.receipt.revision) throw invalid();
|
|
56
|
+
const completedRoot = fs.realpathSync.native(path.join(manifest.repoRoot, '.agents', 'workspace', 'completed'));
|
|
57
|
+
const source = fs.realpathSync.native(path.join(completedRoot, manifest.taskId));
|
|
58
|
+
if (source !== path.join(completedRoot, manifest.taskId)) throw invalid();
|
|
59
|
+
const stat = fs.statSync(source, { bigint: true });
|
|
60
|
+
return {
|
|
61
|
+
taskId: manifest.taskId, generation: manifest.generation,
|
|
62
|
+
containerId: container.id, branch: manifest.branch,
|
|
63
|
+
worktreeRoot: fs.realpathSync.native(manifest.worktreeRoot),
|
|
64
|
+
source, sourceDevice: String(stat.dev), sourceInode: String(stat.ino), receipt: view.receipt
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function publishCompletedReentry(manifest: SandboxControlManifest, evidence: ReentryEvidence): void {
|
|
69
|
+
atomicWriteJson(evidencePath(manifest), evidence);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function completedReentryView(
|
|
73
|
+
manifest: SandboxControlManifest,
|
|
74
|
+
inspect: typeof inspectSandboxControlContainer = inspectSandboxControlContainer
|
|
75
|
+
): Promise<SandboxTaskView | null> {
|
|
76
|
+
const file = evidencePath(manifest);
|
|
77
|
+
if (!fs.existsSync(file)) return null;
|
|
78
|
+
const evidence: unknown = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
79
|
+
const current = await prepareCompletedReentry(manifest, inspect);
|
|
80
|
+
if (JSON.stringify(evidence) !== JSON.stringify(current)) throw invalid();
|
|
81
|
+
return {
|
|
82
|
+
state: 'current', taskId: current.taskId, observedSource: 'completed',
|
|
83
|
+
receipt: current.receipt, reasonCode: null
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function waitForCompletedReentry(manifest: SandboxControlManifest, evidence: ReentryEvidence): Promise<void> {
|
|
88
|
+
const deadline = Date.now() + 10_000;
|
|
89
|
+
while (Date.now() < deadline) {
|
|
90
|
+
const status = readSandboxControlStatus(manifest.publicStatusDir);
|
|
91
|
+
if (status.generation !== evidence.generation) throw invalid();
|
|
92
|
+
if (status.taskView.state === 'current' && status.taskView.observedSource === 'completed'
|
|
93
|
+
&& status.taskView.taskId === evidence.taskId
|
|
94
|
+
&& JSON.stringify(status.taskView.receipt) === JSON.stringify(evidence.receipt)) return;
|
|
95
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
96
|
+
}
|
|
97
|
+
throw new Error('SANDBOX_COMPLETED_REENTRY_STATUS_TIMEOUT');
|
|
98
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { SandboxControlManifest } from './protocol.ts';
|
|
2
|
-
import {
|
|
2
|
+
import { runProbe } from '../shell.ts';
|
|
3
|
+
import { captureSandboxAuthority, isSandboxAuthorityEvidence, verifySandboxAuthority } from '../engines/authority.ts';
|
|
4
|
+
import { discoverExactContainer } from '../engines/docker-exact-discovery.ts';
|
|
3
5
|
|
|
4
6
|
export type ContainerObservation =
|
|
5
7
|
| Readonly<{ state: 'found'; id: string; running: boolean; labels: Readonly<Record<string, string>> }>
|
|
@@ -12,20 +14,13 @@ export type ContainerInspection = Readonly<{
|
|
|
12
14
|
labels?: unknown;
|
|
13
15
|
}>;
|
|
14
16
|
|
|
15
|
-
function isAuthoritativeNotFound(error: unknown): boolean {
|
|
16
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
17
|
-
return /(?:no such (?:container|object)|container .* not found|cannot find container)/i.test(message);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
17
|
export function classifySandboxContainerInspection(
|
|
21
18
|
expected: SandboxControlManifest['containerIdentity'],
|
|
22
19
|
inspection: ContainerInspection | { notFound: true } | { error: unknown }
|
|
23
20
|
): ContainerObservation {
|
|
24
21
|
if ('notFound' in inspection) return { state: 'absent', id: expected.id };
|
|
25
22
|
if ('error' in inspection) {
|
|
26
|
-
return
|
|
27
|
-
? { state: 'absent', id: expected.id }
|
|
28
|
-
: { state: 'unknown', reason: `container inspection failed: ${inspection.error instanceof Error ? inspection.error.message : String(inspection.error)}` };
|
|
23
|
+
return { state: 'unknown', reason: `container inspection failed: ${inspection.error instanceof Error ? inspection.error.message : String(inspection.error)}` };
|
|
29
24
|
}
|
|
30
25
|
const response = inspection as ContainerInspection;
|
|
31
26
|
if (typeof response.id !== 'string' || response.id !== expected.id
|
|
@@ -49,44 +44,33 @@ export function classifySandboxContainerInspection(
|
|
|
49
44
|
|
|
50
45
|
export async function inspectSandboxControlContainer(
|
|
51
46
|
manifest: SandboxControlManifest,
|
|
52
|
-
options: {
|
|
47
|
+
options: { probe?: typeof runProbe; timeoutMs?: number } = {}
|
|
53
48
|
): Promise<ContainerObservation> {
|
|
49
|
+
if (!isSandboxAuthorityEvidence(manifest.authorityEvidence)) {
|
|
50
|
+
return { state: 'unknown', reason: 'SANDBOX_AUTHORITY_EVIDENCE_MISSING' };
|
|
51
|
+
}
|
|
54
52
|
try {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
options.timeoutMs === undefined ? {} : { timeout: options.timeoutMs }
|
|
65
|
-
);
|
|
66
|
-
if (result.status !== 0) {
|
|
67
|
-
const stderr = typeof result.stderr === 'string' ? result.stderr : result.stderr?.toString('utf8') ?? '';
|
|
68
|
-
return classifySandboxContainerInspection(manifest.containerIdentity, {
|
|
69
|
-
error: new Error(stderr || `container inspection exited with ${result.status ?? 'unknown'}`)
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
raw = typeof result.stdout === 'string' ? result.stdout : result.stdout?.toString('utf8') ?? '';
|
|
53
|
+
const authority = captureSandboxAuthority(manifest.engine, {
|
|
54
|
+
probe: options.probe,
|
|
55
|
+
timeoutMs: options.timeoutMs,
|
|
56
|
+
lockDomain: manifest.authorityEvidence.lockDomain,
|
|
57
|
+
route: manifest.authorityEvidence
|
|
58
|
+
});
|
|
59
|
+
const authorityState = verifySandboxAuthority(manifest.authorityEvidence, authority);
|
|
60
|
+
if (authorityState.state !== 'verified') {
|
|
61
|
+
return { state: 'unknown', reason: authorityState.reason ?? 'SANDBOX_AUTHORITY_UNAVAILABLE' };
|
|
73
62
|
}
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
} | Array<{
|
|
79
|
-
Id?: unknown;
|
|
80
|
-
State?: { Running?: unknown };
|
|
81
|
-
Config?: { Labels?: unknown };
|
|
82
|
-
}>;
|
|
83
|
-
const value = Array.isArray(parsed) && parsed.length === 1 ? parsed[0] : parsed;
|
|
84
|
-
if (!value || Array.isArray(value)) return { state: 'unknown', reason: 'container inspection response is incomplete' };
|
|
85
|
-
return classifySandboxContainerInspection(manifest.containerIdentity, {
|
|
86
|
-
id: value.Id,
|
|
87
|
-
running: value.State?.Running,
|
|
88
|
-
labels: value.Config?.Labels
|
|
63
|
+
const discovered = discoverExactContainer(manifest.engine, manifest.containerIdentity.id, {
|
|
64
|
+
probe: options.probe,
|
|
65
|
+
timeoutMs: options.timeoutMs,
|
|
66
|
+
authority: manifest.authorityEvidence
|
|
89
67
|
});
|
|
68
|
+
if (discovered.state === 'absent') return { state: 'absent', id: discovered.id };
|
|
69
|
+
if (discovered.state === 'conflict') {
|
|
70
|
+
return { state: 'unknown', reason: `SANDBOX_CONTROL_CONTAINER_CONFLICT: ${discovered.reason}` };
|
|
71
|
+
}
|
|
72
|
+
if (discovered.state === 'unknown') return discovered;
|
|
73
|
+
return classifySandboxContainerInspection(manifest.containerIdentity, discovered);
|
|
90
74
|
} catch (error) {
|
|
91
75
|
return classifySandboxContainerInspection(manifest.containerIdentity, { error });
|
|
92
76
|
}
|