@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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { completedReentryView } from './completed-reentry.ts';
|
|
1
2
|
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { createHash, randomUUID } from 'node:crypto';
|
|
@@ -34,7 +35,8 @@ import {
|
|
|
34
35
|
writeSandboxControlReservation,
|
|
35
36
|
writeSandboxControlResultEvidence,
|
|
36
37
|
terminateSandboxControlExecution,
|
|
37
|
-
writeSandboxControlStatus
|
|
38
|
+
writeSandboxControlStatus,
|
|
39
|
+
readSandboxControlStatus
|
|
38
40
|
} from './state.ts';
|
|
39
41
|
import { validateSandboxControlRequest } from './protocol.ts';
|
|
40
42
|
import { inspectSandboxControlContainer, type ContainerObservation } from './container-identity.ts';
|
|
@@ -48,6 +50,13 @@ import type { BrokerOwner } from './lifecycle.ts';
|
|
|
48
50
|
import { nextSandboxControlBackoff } from './timing.ts';
|
|
49
51
|
import { readTaskFinalizationReceipt } from '../../task/finalization.ts';
|
|
50
52
|
import { resolveTaskRef } from '../../task/resolve-ref.ts';
|
|
53
|
+
import {
|
|
54
|
+
mergeSandboxTaskView,
|
|
55
|
+
taskViewAfterFinalization,
|
|
56
|
+
taskViewForManifest,
|
|
57
|
+
type SandboxTaskView
|
|
58
|
+
} from './task-view.ts';
|
|
59
|
+
import { taskCreateOutputUnavailableResult } from '../../task/create-service.ts';
|
|
51
60
|
|
|
52
61
|
type ActiveExecution = {
|
|
53
62
|
request: SandboxControlRequest;
|
|
@@ -58,6 +67,67 @@ type ActiveExecution = {
|
|
|
58
67
|
settled: boolean;
|
|
59
68
|
};
|
|
60
69
|
|
|
70
|
+
function safeRealpath(value: string): string | null {
|
|
71
|
+
try {
|
|
72
|
+
return fs.realpathSync.native(value);
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function appendBrokerAudit(
|
|
79
|
+
manifest: SandboxControlManifest,
|
|
80
|
+
event: string,
|
|
81
|
+
fields: Record<string, string | number | boolean | null> = {}
|
|
82
|
+
): void {
|
|
83
|
+
try {
|
|
84
|
+
appendSandboxControlAudit(manifest, event, { source: 'broker', ...fields });
|
|
85
|
+
} catch {
|
|
86
|
+
// Diagnostics must never change the control protocol.
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function requestAuditFields(
|
|
91
|
+
manifest: SandboxControlManifest,
|
|
92
|
+
manifestPath: string,
|
|
93
|
+
request: SandboxControlRequest
|
|
94
|
+
): Record<string, string | number | boolean | null> {
|
|
95
|
+
const args = 'args' in request ? request.args : [];
|
|
96
|
+
const encodedArgs = JSON.stringify(args);
|
|
97
|
+
return {
|
|
98
|
+
requestId: request.id,
|
|
99
|
+
requestFamily: request.family,
|
|
100
|
+
sandboxTaskId: manifest.taskId,
|
|
101
|
+
requestGeneration: request.generation,
|
|
102
|
+
requestIssuedAt: request.issuedAt,
|
|
103
|
+
requestExpiresAt: request.expiresAt,
|
|
104
|
+
requestArgCount: args.length,
|
|
105
|
+
requestArgsSha256: createHash('sha256').update(encodedArgs, 'utf8').digest('hex'),
|
|
106
|
+
requestTaskRef: args[0] ?? null,
|
|
107
|
+
requestCommand: args[1] ?? null,
|
|
108
|
+
controllerProofPresent: request.controllerProof !== null,
|
|
109
|
+
hostCwd: process.cwd(),
|
|
110
|
+
manifestPath,
|
|
111
|
+
manifestPathRealpath: safeRealpath(manifestPath),
|
|
112
|
+
repoRoot: manifest.repoRoot,
|
|
113
|
+
repoRootRealpath: safeRealpath(manifest.repoRoot),
|
|
114
|
+
worktreeRoot: manifest.worktreeRoot,
|
|
115
|
+
worktreeRootRealpath: safeRealpath(manifest.worktreeRoot),
|
|
116
|
+
runtimeDir: manifest.runtimeDir,
|
|
117
|
+
runtimeDirRealpath: safeRealpath(manifest.runtimeDir)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function resultAuditFields(result: SandboxControlExecutionResult): Record<string, string | number | boolean | null> {
|
|
122
|
+
return {
|
|
123
|
+
exitCode: result.exitCode,
|
|
124
|
+
stdoutBytes: Buffer.byteLength(result.stdout, 'utf8'),
|
|
125
|
+
stderrBytes: Buffer.byteLength(result.stderr, 'utf8'),
|
|
126
|
+
stdoutSha256: createHash('sha256').update(result.stdout, 'utf8').digest('hex'),
|
|
127
|
+
stderrSha256: createHash('sha256').update(result.stderr, 'utf8').digest('hex')
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
61
131
|
function assertRealDirectory(directory: string, parent?: string): void {
|
|
62
132
|
const stat = fs.lstatSync(directory);
|
|
63
133
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
|
|
@@ -171,6 +241,36 @@ function finalizationRecoveryResponse(
|
|
|
171
241
|
};
|
|
172
242
|
}
|
|
173
243
|
|
|
244
|
+
function publishFinalizationTaskView(
|
|
245
|
+
manifest: SandboxControlManifest,
|
|
246
|
+
broker: BrokerOwner,
|
|
247
|
+
requestId: string,
|
|
248
|
+
state: 'starting' | 'healthy' | 'busy' | 'parked',
|
|
249
|
+
reasonCode: string | null,
|
|
250
|
+
activeRequestId: string | null
|
|
251
|
+
): SandboxTaskView {
|
|
252
|
+
let view: SandboxTaskView;
|
|
253
|
+
try {
|
|
254
|
+
const resolved = resolveTaskRef(manifest.taskId ?? '', { repoRoot: manifest.repoRoot });
|
|
255
|
+
const receipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId ?? '');
|
|
256
|
+
view = resolved.ok && resolved.state === 'completed'
|
|
257
|
+
? taskViewAfterFinalization({
|
|
258
|
+
taskId: manifest.taskId ?? '', generation: manifest.generation, requestId, receipt
|
|
259
|
+
})
|
|
260
|
+
: {
|
|
261
|
+
state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null,
|
|
262
|
+
reasonCode: 'SANDBOX_TASK_VIEW_SOURCE_UNCONFIRMED'
|
|
263
|
+
};
|
|
264
|
+
} catch {
|
|
265
|
+
view = {
|
|
266
|
+
state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null,
|
|
267
|
+
reasonCode: 'SANDBOX_TASK_VIEW_RECEIPT_INVALID'
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
writeSandboxControlStatus(manifest, broker, state, reasonCode, activeRequestId, Date.now(), view);
|
|
271
|
+
return view;
|
|
272
|
+
}
|
|
273
|
+
|
|
174
274
|
function payloadReference(payload: ReturnType<typeof createSandboxControlPayload>) {
|
|
175
275
|
return {
|
|
176
276
|
version: payload.version,
|
|
@@ -201,17 +301,21 @@ function outputMatchesEvidence(output: string, bytes: number, sha256: string): b
|
|
|
201
301
|
}
|
|
202
302
|
|
|
203
303
|
function genericRecoveryResponse(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
payload: ReturnType<typeof readSandboxControlPayload> | null
|
|
304
|
+
request: SandboxControlRequest,
|
|
305
|
+
exitCode: number,
|
|
306
|
+
payload: ReturnType<typeof readSandboxControlPayload> | null,
|
|
307
|
+
cause: 'publish' | 'recovery' = 'recovery'
|
|
207
308
|
): SandboxControlResponse {
|
|
309
|
+
const outputUnavailable = request.family === 'task-create' && !payload;
|
|
208
310
|
return {
|
|
209
311
|
version: 2,
|
|
210
|
-
id:
|
|
312
|
+
id: request.id,
|
|
211
313
|
phase: 'completed',
|
|
212
|
-
exitCode
|
|
213
|
-
stdout: '',
|
|
214
|
-
stderr: payload ? '' :
|
|
314
|
+
exitCode,
|
|
315
|
+
stdout: outputUnavailable ? `${JSON.stringify(taskCreateOutputUnavailableResult(request.id))}\n` : '',
|
|
316
|
+
stderr: payload ? '' : cause === 'publish'
|
|
317
|
+
? 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n'
|
|
318
|
+
: 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
|
|
215
319
|
error: null,
|
|
216
320
|
outputState: payload ? 'available' : 'unavailable',
|
|
217
321
|
payload: payload ? payloadReference(payload) : null
|
|
@@ -228,7 +332,7 @@ function recoveryResponse(
|
|
|
228
332
|
const finalization = finalizationRecoveryResponse(manifest, request.id, evidence.exitCode);
|
|
229
333
|
return finalization.status === 'matched' ? finalization.response ?? null : null;
|
|
230
334
|
}
|
|
231
|
-
return genericRecoveryResponse(request
|
|
335
|
+
return genericRecoveryResponse(request, evidence.exitCode, payload);
|
|
232
336
|
}
|
|
233
337
|
|
|
234
338
|
function terminalMatchesEvidence(
|
|
@@ -267,8 +371,10 @@ function terminalMatchesEvidence(
|
|
|
267
371
|
};
|
|
268
372
|
}
|
|
269
373
|
if (response.outputState === 'unavailable') {
|
|
270
|
-
const
|
|
271
|
-
|
|
374
|
+
const causes = ['recovery', 'publish'] as const;
|
|
375
|
+
const valid = causes.some((cause) => JSON.stringify(response)
|
|
376
|
+
=== JSON.stringify(genericRecoveryResponse(request, evidence.exitCode, null, cause)));
|
|
377
|
+
return { valid, payloadReferenced: false };
|
|
272
378
|
}
|
|
273
379
|
return {
|
|
274
380
|
valid: response.payload === undefined
|
|
@@ -282,6 +388,7 @@ function publishExecutionResult(
|
|
|
282
388
|
manifest: SandboxControlManifest,
|
|
283
389
|
request: SandboxControlRequest,
|
|
284
390
|
result: SandboxControlExecutionResult,
|
|
391
|
+
broker: BrokerOwner,
|
|
285
392
|
brokerOwns: () => boolean
|
|
286
393
|
): boolean {
|
|
287
394
|
const normalized = sanitizeSandboxControlResult(manifest, result);
|
|
@@ -299,7 +406,7 @@ function publishExecutionResult(
|
|
|
299
406
|
if (sandboxControlEncodedJsonBytes(inline) <= SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES) {
|
|
300
407
|
terminal = inline;
|
|
301
408
|
}
|
|
302
|
-
let payload = null;
|
|
409
|
+
let payload: ReturnType<typeof createSandboxControlPayload> | null = null;
|
|
303
410
|
if (!terminal) {
|
|
304
411
|
try {
|
|
305
412
|
payload = createSandboxControlPayload(manifest, request.id, normalized);
|
|
@@ -312,20 +419,14 @@ function publishExecutionResult(
|
|
|
312
419
|
} catch {
|
|
313
420
|
payload = null;
|
|
314
421
|
}
|
|
315
|
-
terminal =
|
|
316
|
-
version: 2,
|
|
317
|
-
id: request.id,
|
|
318
|
-
phase: 'completed',
|
|
319
|
-
exitCode: normalized.exitCode,
|
|
320
|
-
stdout: '',
|
|
321
|
-
stderr: payload ? '' : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n',
|
|
322
|
-
error: null,
|
|
323
|
-
outputState: payload ? 'available' : 'unavailable',
|
|
324
|
-
payload: payload ? payloadReference(payload) : null
|
|
325
|
-
};
|
|
422
|
+
terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish');
|
|
326
423
|
}
|
|
327
424
|
}
|
|
328
425
|
if (!brokerOwns()) return false;
|
|
426
|
+
if (request.family === 'task-finalization' && normalized.exitCode === 0) {
|
|
427
|
+
const view = publishFinalizationTaskView(manifest, broker, request.id, 'healthy', null, null);
|
|
428
|
+
if (view.state === 'unknown') return false;
|
|
429
|
+
}
|
|
329
430
|
return writeSandboxControlResponse(manifest, terminal);
|
|
330
431
|
}
|
|
331
432
|
|
|
@@ -391,7 +492,7 @@ function bindingReason(manifest: SandboxControlManifest): string | null {
|
|
|
391
492
|
}
|
|
392
493
|
}
|
|
393
494
|
|
|
394
|
-
function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => boolean): boolean {
|
|
495
|
+
function recoverProcessing(manifest: SandboxControlManifest, broker: BrokerOwner, brokerOwns: () => boolean): boolean {
|
|
395
496
|
for (const entry of fs.readdirSync(manifest.processingDir, { withFileTypes: true })) {
|
|
396
497
|
if (!entry.isDirectory() || !/^[a-f0-9-]{16,64}$/.test(entry.name)) continue;
|
|
397
498
|
if (!brokerOwns()) return false;
|
|
@@ -459,7 +560,7 @@ function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => b
|
|
|
459
560
|
|| response.payload.stdoutSha256 !== payload.stdoutSha256
|
|
460
561
|
|| response.payload.stderrSha256 !== payload.stderrSha256) throw new Error('payload missing or mismatched');
|
|
461
562
|
payloadReferenced = true;
|
|
462
|
-
} else if (terminal && (response.outputState !== undefined
|
|
563
|
+
} else if (terminal && ((response.outputState !== undefined && response.outputState !== 'unavailable')
|
|
463
564
|
|| response.payload !== undefined && response.payload !== null)) {
|
|
464
565
|
throw new Error('payload state invalid');
|
|
465
566
|
}
|
|
@@ -473,12 +574,20 @@ function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => b
|
|
|
473
574
|
const reconciliation = terminalMatchesEvidence(manifest, request, existingTerminalResponse!, resultEvidence, payload, payloadInvalid);
|
|
474
575
|
if (!reconciliation.valid) continue;
|
|
475
576
|
payloadReferenced = reconciliation.payloadReferenced;
|
|
577
|
+
if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
|
|
578
|
+
const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
|
|
579
|
+
if (view.state === 'unknown') continue;
|
|
580
|
+
}
|
|
476
581
|
}
|
|
477
582
|
if (!terminal) {
|
|
478
583
|
if (!brokerOwns()) return false;
|
|
479
584
|
if (!resultEvidence || !request || payloadInvalid) continue;
|
|
480
585
|
const recovered = recoveryResponse(manifest, request, resultEvidence, payload);
|
|
481
586
|
if (!recovered) continue;
|
|
587
|
+
if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
|
|
588
|
+
const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
|
|
589
|
+
if (view.state === 'unknown') continue;
|
|
590
|
+
}
|
|
482
591
|
writeSandboxControlResponse(manifest, recovered);
|
|
483
592
|
payloadReferenced = Boolean(payload) && request.family !== 'task-finalization';
|
|
484
593
|
}
|
|
@@ -499,7 +608,13 @@ function delay(ms: number): Promise<void> {
|
|
|
499
608
|
}
|
|
500
609
|
|
|
501
610
|
export function sandboxControlSafeEnv(env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
|
|
502
|
-
return Object.fromEntries(Object.entries(env).filter(([key]) =>
|
|
611
|
+
return Object.fromEntries(Object.entries(env).filter(([key]) => {
|
|
612
|
+
const normalized = key.toUpperCase();
|
|
613
|
+
return !normalized.startsWith('AGENT_INFRA_CONTROL_')
|
|
614
|
+
&& normalized !== 'AGENT_INFRA_TASK_ID'
|
|
615
|
+
&& normalized !== 'AGENT_INFRA_RUNTIME_DIR'
|
|
616
|
+
&& normalized !== 'AGENT_INFRA_EXECUTOR_MANIFEST';
|
|
617
|
+
}));
|
|
503
618
|
}
|
|
504
619
|
|
|
505
620
|
export async function serveSandboxControl(
|
|
@@ -562,6 +677,25 @@ export async function serveSandboxControl(
|
|
|
562
677
|
releaseStartup();
|
|
563
678
|
}
|
|
564
679
|
let active: ActiveExecution | null = null;
|
|
680
|
+
let startupReceipt: unknown = null;
|
|
681
|
+
if (manifest.taskId) {
|
|
682
|
+
try {
|
|
683
|
+
startupReceipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId);
|
|
684
|
+
} catch {
|
|
685
|
+
// Preserve malformed receipt evidence as unknown rather than treating it as no receipt.
|
|
686
|
+
startupReceipt = {};
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
let taskView = taskViewForManifest({
|
|
690
|
+
...manifest,
|
|
691
|
+
receipt: startupReceipt
|
|
692
|
+
});
|
|
693
|
+
try {
|
|
694
|
+
const previous = readSandboxControlStatus(manifest.publicStatusDir);
|
|
695
|
+
if (previous.generation === manifest.generation) taskView = mergeSandboxTaskView(taskView, previous.taskView);
|
|
696
|
+
} catch {
|
|
697
|
+
// A fresh control root has no previous task-view projection.
|
|
698
|
+
}
|
|
565
699
|
let lastState = '';
|
|
566
700
|
let lastStatusAt = 0;
|
|
567
701
|
let nextBindingCheckAt = 0;
|
|
@@ -572,10 +706,43 @@ export async function serveSandboxControl(
|
|
|
572
706
|
let bindingBackoffMs: number = timing.parkedBindingInitialMs;
|
|
573
707
|
try {
|
|
574
708
|
if (!brokerOwns()) return;
|
|
575
|
-
|
|
709
|
+
if (taskView.state === 'current' && taskView.observedSource === 'completed') {
|
|
710
|
+
// Recheck durable re-entry evidence before publishing a trusted startup view.
|
|
711
|
+
const unverified = { ...taskView, state: 'unknown' as const,
|
|
712
|
+
reasonCode: 'SANDBOX_COMPLETED_REENTRY_EVIDENCE_INVALID' };
|
|
713
|
+
try {
|
|
714
|
+
taskView = await completedReentryView(manifest, inspectContainer) ?? unverified;
|
|
715
|
+
} catch {
|
|
716
|
+
taskView = unverified;
|
|
717
|
+
}
|
|
718
|
+
if (!brokerOwns()) return;
|
|
719
|
+
}
|
|
720
|
+
writeSandboxControlStatus(manifest, broker, 'starting', null, null, Date.now(), taskView);
|
|
576
721
|
if (!brokerOwns()) return;
|
|
577
|
-
|
|
578
|
-
|
|
722
|
+
appendBrokerAudit(manifest, 'broker-start', {
|
|
723
|
+
pid: broker.pid,
|
|
724
|
+
brokerId: broker.brokerId,
|
|
725
|
+
hostCwd: process.cwd(),
|
|
726
|
+
manifestPath,
|
|
727
|
+
manifestPathRealpath: safeRealpath(manifestPath),
|
|
728
|
+
repoRoot: manifest.repoRoot,
|
|
729
|
+
repoRootRealpath: safeRealpath(manifest.repoRoot),
|
|
730
|
+
worktreeRoot: manifest.worktreeRoot,
|
|
731
|
+
worktreeRootRealpath: safeRealpath(manifest.worktreeRoot),
|
|
732
|
+
runtimeDir: manifest.runtimeDir,
|
|
733
|
+
runtimeDirRealpath: safeRealpath(manifest.runtimeDir),
|
|
734
|
+
channelDir: manifest.channelDir,
|
|
735
|
+
publicStatusDir: manifest.publicStatusDir,
|
|
736
|
+
processingDir: manifest.processingDir,
|
|
737
|
+
internalCliPath: options.internalCliPath ?? process.argv[1] ?? null
|
|
738
|
+
});
|
|
739
|
+
if (!recoverProcessing(manifest, broker, brokerOwns)) return;
|
|
740
|
+
try {
|
|
741
|
+
const published = readSandboxControlStatus(manifest.publicStatusDir);
|
|
742
|
+
if (published.generation === manifest.generation) taskView = published.taskView;
|
|
743
|
+
} catch {
|
|
744
|
+
// The regular heartbeat will publish an invalid or missing projection as unknown.
|
|
745
|
+
}
|
|
579
746
|
while (!signal.aborted) {
|
|
580
747
|
let settledExecution: ActiveExecution | null = null;
|
|
581
748
|
if (!brokerOwns()) break;
|
|
@@ -647,7 +814,7 @@ export async function serveSandboxControl(
|
|
|
647
814
|
if (!brokerOwns()) break;
|
|
648
815
|
if (cleanupStaleSandboxControlLease(manifest)) {
|
|
649
816
|
if (!brokerOwns()) break;
|
|
650
|
-
|
|
817
|
+
appendBrokerAudit(manifest, 'lease-stale-cleanup');
|
|
651
818
|
}
|
|
652
819
|
if (readActiveLease(manifest)) reasonCode = 'SANDBOX_CONTROL_HANDOFF_ACTIVE';
|
|
653
820
|
} catch {
|
|
@@ -667,25 +834,49 @@ export async function serveSandboxControl(
|
|
|
667
834
|
}
|
|
668
835
|
reasonCode ??= containerReasonCode;
|
|
669
836
|
reasonCode ??= bindingReasonCode;
|
|
837
|
+
if (!reasonCode && !active && taskView.state !== 'current') {
|
|
838
|
+
try {
|
|
839
|
+
const reentered = await completedReentryView(manifest, inspectContainer);
|
|
840
|
+
if (reentered && brokerOwns() && !readActiveLease(manifest) && !bindingCheck(manifest)) {
|
|
841
|
+
taskView = reentered;
|
|
842
|
+
}
|
|
843
|
+
} catch {
|
|
844
|
+
// Invalid or changed re-entry evidence never clears the existing task view.
|
|
845
|
+
}
|
|
846
|
+
}
|
|
670
847
|
const state = reasonCode ? 'parked' : active ? 'busy' : 'healthy';
|
|
671
848
|
const stateKey = `${state}:${reasonCode ?? ''}:${active?.request.id ?? ''}`;
|
|
672
849
|
if (brokerOwns() && (stateKey !== lastState || now - lastStatusAt >= timing.controlTickMs)) {
|
|
673
|
-
writeSandboxControlStatus(manifest, broker, state, reasonCode, active?.request.id ?? null, now);
|
|
850
|
+
writeSandboxControlStatus(manifest, broker, state, reasonCode, active?.request.id ?? null, now, taskView);
|
|
674
851
|
lastStatusAt = now;
|
|
675
852
|
}
|
|
676
853
|
if (brokerOwns() && stateKey !== lastState) {
|
|
677
|
-
|
|
854
|
+
appendBrokerAudit(manifest, 'broker-state', { state, reasonCode, requestId: active?.request.id ?? null });
|
|
678
855
|
lastState = stateKey;
|
|
679
856
|
}
|
|
680
857
|
if (settledExecution) {
|
|
681
858
|
if (!brokerOwns()) break;
|
|
682
859
|
let terminalCommitted = false;
|
|
683
860
|
if (settledExecution.result && settledExecution.resultEvidenceWritten) {
|
|
684
|
-
terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, brokerOwns);
|
|
861
|
+
terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, broker, brokerOwns);
|
|
862
|
+
if (terminalCommitted && settledExecution.request.family === 'task-finalization') {
|
|
863
|
+
try {
|
|
864
|
+
const published = readSandboxControlStatus(manifest.publicStatusDir);
|
|
865
|
+
if (published.generation === manifest.generation) taskView = published.taskView;
|
|
866
|
+
} catch {
|
|
867
|
+
taskView = { state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null, reasonCode: 'SANDBOX_TASK_VIEW_STATUS_INVALID' };
|
|
868
|
+
}
|
|
869
|
+
}
|
|
685
870
|
} else {
|
|
686
871
|
terminalCommitted = writeSandboxControlResponse(manifest, unknown(settledExecution.request.id));
|
|
687
872
|
}
|
|
688
873
|
if (!terminalCommitted) return;
|
|
874
|
+
appendBrokerAudit(manifest, 'executor-result-published', {
|
|
875
|
+
...requestAuditFields(manifest, manifestPath, settledExecution.request),
|
|
876
|
+
resultAvailable: settledExecution.result !== null,
|
|
877
|
+
resultEvidenceWritten: settledExecution.resultEvidenceWritten,
|
|
878
|
+
...(settledExecution.result ? resultAuditFields(settledExecution.result) : {})
|
|
879
|
+
});
|
|
689
880
|
if (!brokerOwns()) break;
|
|
690
881
|
removeAcceptedResponse(manifest, settledExecution.request.id);
|
|
691
882
|
fs.rmSync(path.join(manifest.processingDir, settledExecution.request.id), { recursive: true, force: true });
|
|
@@ -697,12 +888,19 @@ export async function serveSandboxControl(
|
|
|
697
888
|
const id = name.slice(0, -5);
|
|
698
889
|
const source = path.join(requestsDir, name);
|
|
699
890
|
let claimed: string | null = null;
|
|
891
|
+
let validatedRequest: SandboxControlRequest | null = null;
|
|
700
892
|
try {
|
|
701
893
|
if (!brokerOwns()) {
|
|
702
894
|
retiring = true;
|
|
703
895
|
break;
|
|
704
896
|
}
|
|
705
897
|
claimed = claimRequest(manifest, source, id);
|
|
898
|
+
appendBrokerAudit(manifest, 'request-claimed', {
|
|
899
|
+
requestId: id,
|
|
900
|
+
requestSourcePath: source,
|
|
901
|
+
requestClaimedPath: claimed,
|
|
902
|
+
requestProcessingDir: path.dirname(claimed)
|
|
903
|
+
});
|
|
706
904
|
if (!brokerOwns()) {
|
|
707
905
|
retiring = true;
|
|
708
906
|
break;
|
|
@@ -715,13 +913,44 @@ export async function serveSandboxControl(
|
|
|
715
913
|
if (reasonCode) throw new Error(reasonCode);
|
|
716
914
|
if (active) throw new Error('SANDBOX_CONTROL_BUSY');
|
|
717
915
|
const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(claimed, 'utf8')), manifest);
|
|
916
|
+
validatedRequest = request;
|
|
917
|
+
appendBrokerAudit(manifest, 'request-validated', {
|
|
918
|
+
...requestAuditFields(manifest, manifestPath, request),
|
|
919
|
+
requestPath: claimed
|
|
920
|
+
});
|
|
718
921
|
if (bindingCheck(manifest)) throw new Error('SANDBOX_WORKTREE_BINDING_LOST');
|
|
719
922
|
if (readActiveLease(manifest)) throw new Error('SANDBOX_CONTROL_HANDOFF_ACTIVE');
|
|
923
|
+
appendBrokerAudit(manifest, 'request-gates-passed', {
|
|
924
|
+
...requestAuditFields(manifest, manifestPath, request),
|
|
925
|
+
requestPath: claimed,
|
|
926
|
+
bindingChecked: true,
|
|
927
|
+
handoffLeaseChecked: true
|
|
928
|
+
});
|
|
720
929
|
writeSandboxControlReservation(manifest, request.id, sandboxControlGenerationUsage(manifest));
|
|
930
|
+
appendBrokerAudit(manifest, 'executor-reservation-written', {
|
|
931
|
+
...requestAuditFields(manifest, manifestPath, request),
|
|
932
|
+
requestPath: claimed,
|
|
933
|
+
executionPath: executionPath(manifest, request.id)
|
|
934
|
+
});
|
|
935
|
+
appendBrokerAudit(manifest, 'executor-prepare-start', {
|
|
936
|
+
...requestAuditFields(manifest, manifestPath, request),
|
|
937
|
+
requestPath: claimed,
|
|
938
|
+
executorCwd: manifest.repoRoot,
|
|
939
|
+
executorEntry: options.internalCliPath ?? process.argv[1] ?? null
|
|
940
|
+
});
|
|
721
941
|
const prepared = await prepareExecution({
|
|
722
942
|
manifest, manifestPath, request, requestPath: claimed,
|
|
723
943
|
internalCliPath: options.internalCliPath ?? process.argv[1]!
|
|
724
944
|
});
|
|
945
|
+
appendBrokerAudit(manifest, 'executor-prepared', {
|
|
946
|
+
...requestAuditFields(manifest, manifestPath, request),
|
|
947
|
+
requestPath: claimed,
|
|
948
|
+
executorCwd: manifest.repoRoot,
|
|
949
|
+
childPid: prepared.execution.child.pid,
|
|
950
|
+
childStartTime: prepared.execution.child.startTime,
|
|
951
|
+
childProcessGroupId: prepared.execution.child.processGroupId,
|
|
952
|
+
executionPath: executionPath(manifest, request.id)
|
|
953
|
+
});
|
|
725
954
|
const execution: ActiveExecution = {
|
|
726
955
|
request, prepared, result: null, resultEvidenceWritten: false, failure: null, settled: false
|
|
727
956
|
};
|
|
@@ -729,15 +958,39 @@ export async function serveSandboxControl(
|
|
|
729
958
|
prepared.completion.then(
|
|
730
959
|
(result) => {
|
|
731
960
|
execution.result = sanitizeSandboxControlResult(manifest, result);
|
|
961
|
+
appendBrokerAudit(manifest, 'executor-completed', {
|
|
962
|
+
...requestAuditFields(manifest, manifestPath, request),
|
|
963
|
+
...resultAuditFields(execution.result),
|
|
964
|
+
childPid: prepared.execution.child.pid,
|
|
965
|
+
childStartTime: prepared.execution.child.startTime
|
|
966
|
+
});
|
|
732
967
|
try {
|
|
733
968
|
writeSandboxControlResultEvidence(manifest, request.id, execution.result);
|
|
734
969
|
execution.resultEvidenceWritten = true;
|
|
735
970
|
execution.settled = true;
|
|
971
|
+
appendBrokerAudit(manifest, 'executor-result-evidence-written', {
|
|
972
|
+
...requestAuditFields(manifest, manifestPath, request),
|
|
973
|
+
resultPath: path.join(manifest.processingDir, request.id, 'result.json'),
|
|
974
|
+
...resultAuditFields(execution.result)
|
|
975
|
+
});
|
|
736
976
|
} catch (error) {
|
|
737
977
|
execution.failure = error;
|
|
978
|
+
appendBrokerAudit(manifest, 'executor-result-evidence-failed', {
|
|
979
|
+
...requestAuditFields(manifest, manifestPath, request),
|
|
980
|
+
errorType: error instanceof Error ? error.name : typeof error,
|
|
981
|
+
});
|
|
738
982
|
}
|
|
739
983
|
},
|
|
740
|
-
(error) => {
|
|
984
|
+
(error) => {
|
|
985
|
+
execution.failure = error;
|
|
986
|
+
execution.settled = true;
|
|
987
|
+
appendBrokerAudit(manifest, 'executor-failed', {
|
|
988
|
+
...requestAuditFields(manifest, manifestPath, request),
|
|
989
|
+
childPid: prepared.execution.child.pid,
|
|
990
|
+
childStartTime: prepared.execution.child.startTime,
|
|
991
|
+
errorType: error instanceof Error ? error.name : typeof error,
|
|
992
|
+
});
|
|
993
|
+
}
|
|
741
994
|
);
|
|
742
995
|
if (!brokerOwns()) {
|
|
743
996
|
prepared.terminate(false);
|
|
@@ -754,6 +1007,11 @@ export async function serveSandboxControl(
|
|
|
754
1007
|
writeAcceptedResponse(manifest, {
|
|
755
1008
|
version: 2, id, phase: 'accepted', exitCode: null, stdout: '', stderr: '', error: null
|
|
756
1009
|
});
|
|
1010
|
+
appendBrokerAudit(manifest, 'request-accepted', {
|
|
1011
|
+
...requestAuditFields(manifest, manifestPath, request),
|
|
1012
|
+
acceptedPath: acceptedResponsePath(manifest, id),
|
|
1013
|
+
executionPhase: 'prepared'
|
|
1014
|
+
});
|
|
757
1015
|
if (!brokerOwns()) {
|
|
758
1016
|
prepared.terminate(false);
|
|
759
1017
|
active = null;
|
|
@@ -761,8 +1019,18 @@ export async function serveSandboxControl(
|
|
|
761
1019
|
break;
|
|
762
1020
|
}
|
|
763
1021
|
try {
|
|
1022
|
+
appendBrokerAudit(manifest, 'executor-start', {
|
|
1023
|
+
...requestAuditFields(manifest, manifestPath, request),
|
|
1024
|
+
childPid: prepared.execution.child.pid,
|
|
1025
|
+
childStartTime: prepared.execution.child.startTime,
|
|
1026
|
+
executionPath: executionPath(manifest, request.id)
|
|
1027
|
+
});
|
|
764
1028
|
prepared.start(brokerOwns);
|
|
765
1029
|
} catch (error) {
|
|
1030
|
+
appendBrokerAudit(manifest, 'executor-start-failed', {
|
|
1031
|
+
...requestAuditFields(manifest, manifestPath, request),
|
|
1032
|
+
errorType: error instanceof Error ? error.name : typeof error,
|
|
1033
|
+
});
|
|
766
1034
|
const owned = brokerOwns();
|
|
767
1035
|
prepared.terminate(owned);
|
|
768
1036
|
if (!owned || !brokerOwns()) {
|
|
@@ -784,7 +1052,7 @@ export async function serveSandboxControl(
|
|
|
784
1052
|
retiring = true;
|
|
785
1053
|
break;
|
|
786
1054
|
}
|
|
787
|
-
|
|
1055
|
+
appendBrokerAudit(manifest, 'executor-gate-failed', { requestId: id });
|
|
788
1056
|
continue;
|
|
789
1057
|
}
|
|
790
1058
|
} catch (error) {
|
|
@@ -792,6 +1060,16 @@ export async function serveSandboxControl(
|
|
|
792
1060
|
retiring = true;
|
|
793
1061
|
break;
|
|
794
1062
|
}
|
|
1063
|
+
const detail = controlError(error);
|
|
1064
|
+
appendBrokerAudit(manifest, 'request-rejected', {
|
|
1065
|
+
requestId: id,
|
|
1066
|
+
requestFamily: validatedRequest?.family ?? null,
|
|
1067
|
+
sandboxTaskId: manifest.taskId,
|
|
1068
|
+
requestPath: claimed,
|
|
1069
|
+
errorCode: detail.code,
|
|
1070
|
+
errorRetryable: detail.retryable,
|
|
1071
|
+
errorType: error instanceof Error ? error.name : typeof error,
|
|
1072
|
+
});
|
|
795
1073
|
writeSandboxControlResponse(manifest, rejected(id, error));
|
|
796
1074
|
if (!brokerOwns()) {
|
|
797
1075
|
retiring = true;
|
|
@@ -818,7 +1096,7 @@ export async function serveSandboxControl(
|
|
|
818
1096
|
}
|
|
819
1097
|
}
|
|
820
1098
|
if (owned && active.result && active.resultEvidenceWritten) {
|
|
821
|
-
if (publishExecutionResult(manifest, active.request, active.result, brokerOwns)) {
|
|
1099
|
+
if (publishExecutionResult(manifest, active.request, active.result, broker, brokerOwns)) {
|
|
822
1100
|
if (brokerOwns()) {
|
|
823
1101
|
removeAcceptedResponse(manifest, active.request.id);
|
|
824
1102
|
fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
|
|
@@ -830,7 +1108,11 @@ export async function serveSandboxControl(
|
|
|
830
1108
|
const terminationConfirmed = active.prepared.terminate(owned);
|
|
831
1109
|
if (owned && brokerOwns() && active.request.family === 'task-finalization') {
|
|
832
1110
|
const recovered = finalizationRecoveryResponse(manifest, active.request.id, 0);
|
|
833
|
-
|
|
1111
|
+
const view = recovered.status === 'matched'
|
|
1112
|
+
? publishFinalizationTaskView(manifest, broker, active.request.id, 'healthy', null, null)
|
|
1113
|
+
: null;
|
|
1114
|
+
if (view?.state !== 'unknown' && recovered.status === 'matched' && recovered.response
|
|
1115
|
+
&& writeSandboxControlResponse(manifest, recovered.response)) {
|
|
834
1116
|
if (terminationConfirmed && brokerOwns()) {
|
|
835
1117
|
removeAcceptedResponse(manifest, active.request.id);
|
|
836
1118
|
fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES,
|
|
24
24
|
SANDBOX_CONTROL_RESERVATION_BYTES
|
|
25
25
|
} from './protocol.ts';
|
|
26
|
+
import { parseSandboxTaskView, taskViewForManifest, type SandboxTaskView } from './task-view.ts';
|
|
26
27
|
|
|
27
28
|
export const SANDBOX_CONTROL_AUDIT_MAX_BYTES = 1024 * 1024;
|
|
28
29
|
export const SANDBOX_CONTROL_EXECUTION_STOP_MS = 2_000;
|
|
@@ -296,10 +297,11 @@ export function writeSandboxControlStatus(
|
|
|
296
297
|
state: SandboxControlStatus['state'],
|
|
297
298
|
reasonCode: string | null,
|
|
298
299
|
activeRequestId: string | null,
|
|
299
|
-
now = Date.now()
|
|
300
|
+
now = Date.now(),
|
|
301
|
+
taskView: SandboxTaskView = taskViewForManifest(manifest)
|
|
300
302
|
): SandboxControlStatus {
|
|
301
303
|
const status: SandboxControlStatus = {
|
|
302
|
-
version:
|
|
304
|
+
version: 3, generation: manifest.generation, broker, state, reasonCode, activeRequestId, updatedAt: now, taskView
|
|
303
305
|
};
|
|
304
306
|
atomicWriteJson(statusPath(manifest), status, 0o600, false);
|
|
305
307
|
return status;
|
|
@@ -307,7 +309,7 @@ export function writeSandboxControlStatus(
|
|
|
307
309
|
|
|
308
310
|
export function parseSandboxControlStatus(value: unknown): SandboxControlStatus {
|
|
309
311
|
const status = value as Partial<SandboxControlStatus> | null;
|
|
310
|
-
if (!status || status.version !==
|
|
312
|
+
if (!status || status.version !== 3 || typeof status.generation !== 'string'
|
|
311
313
|
|| !status.broker || !Number.isSafeInteger(status.broker.pid) || typeof status.broker.startTime !== 'number'
|
|
312
314
|
|| !Number.isSafeInteger(status.broker.startTime) || typeof status.broker.brokerId !== 'string'
|
|
313
315
|
|| status.broker.brokerId.length === 0
|
|
@@ -315,6 +317,7 @@ export function parseSandboxControlStatus(value: unknown): SandboxControlStatus
|
|
|
315
317
|
|| (status.reasonCode !== null && typeof status.reasonCode !== 'string')
|
|
316
318
|
|| (status.activeRequestId !== null && typeof status.activeRequestId !== 'string')
|
|
317
319
|
|| !Number.isSafeInteger(status.updatedAt)) throw new Error('SANDBOX_CONTROL_STATUS_INVALID');
|
|
320
|
+
parseSandboxTaskView(status.taskView);
|
|
318
321
|
return status as SandboxControlStatus;
|
|
319
322
|
}
|
|
320
323
|
|