@fitlab-ai/agent-infra 0.9.12 → 0.9.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/README.zh-CN.md +11 -11
- package/dist/bin/internal-cli.js +24 -19
- package/dist/lib/agent-clients/next-steps.js +6 -1
- package/dist/lib/decide.js +3 -3
- package/dist/lib/internal/platform-checks.js +3 -3
- package/dist/lib/internal/platform-comment.js +9 -9
- package/dist/lib/internal/platform-context.js +2 -2
- package/dist/lib/internal/platform-issue.js +8 -9
- package/dist/lib/internal/platform-pr-review.js +10 -10
- package/dist/lib/internal/platform-pr.js +63 -24
- package/dist/lib/internal/platform-release-notes.js +3 -3
- package/dist/lib/internal/pr-review-grade.js +11 -9
- package/dist/lib/internal/release-workflow.js +4 -4
- package/dist/lib/internal/sandbox-control.js +30 -4
- package/dist/lib/internal/task-activity.js +3 -3
- package/dist/lib/internal/task-artifact.js +76 -24
- package/dist/lib/internal/task-create.js +2 -2
- package/dist/lib/internal/task-event.js +6 -3
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-invalidation.js +60 -0
- package/dist/lib/internal/task-ledger.js +9 -6
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-override.js +4 -4
- package/dist/lib/internal/task-review.js +3 -3
- package/dist/lib/internal/task-verify.js +6 -6
- package/dist/lib/internal/task-warning.js +3 -3
- package/dist/lib/platform/adapters.js +1 -3
- package/dist/lib/platform/change-request-git-evidence.js +27 -7
- package/dist/lib/platform/completion-backfill.js +6 -5
- package/dist/lib/platform/context.js +70 -130
- package/dist/lib/platform/github-client.js +1 -1
- package/dist/lib/platform/github-provider.js +903 -0
- package/dist/lib/platform/in-label-sync.js +143 -0
- package/dist/lib/platform/issue-comments.js +94 -41
- package/dist/lib/platform/issues.js +335 -189
- package/dist/lib/platform/metadata-labels.js +6 -2
- package/dist/lib/platform/none-provider.js +28 -0
- package/dist/lib/platform/pr-change-report.js +551 -0
- package/dist/lib/platform/pr-checks.js +119 -104
- package/dist/lib/platform/pr-review.js +88 -72
- package/dist/lib/platform/pr-summary.js +520 -50
- package/dist/lib/platform/provider-bridge.js +45 -0
- package/dist/lib/platform/provider-contract.d.ts +487 -0
- package/dist/lib/platform/provider-contract.js +95 -0
- package/dist/lib/platform/provider-loader.js +251 -0
- package/dist/lib/platform/provider-validation.js +504 -0
- package/dist/lib/platform/pull-request-metadata.js +8 -6
- package/dist/lib/platform/pull-requests.js +783 -537
- package/dist/lib/platform/release-notes.js +78 -55
- package/dist/lib/platform/releases.js +79 -50
- package/dist/lib/platform/resource-identity.d.ts +26 -0
- package/dist/lib/platform/resource-identity.js +92 -0
- package/dist/lib/platform/task-identities.js +31 -0
- package/dist/lib/platform/verification-required.js +11 -12
- package/dist/lib/platform/verification-sync.js +112 -238
- package/dist/lib/pr-review/evidence-grading.js +19 -6
- package/dist/lib/run/index.js +63 -23
- package/dist/lib/sandbox/control/client.js +96 -15
- package/dist/lib/sandbox/control/executor.js +3 -3
- package/dist/lib/sandbox/control/protocol.js +77 -0
- package/dist/lib/sandbox/control/server.js +398 -25
- package/dist/lib/sandbox/control/state.js +204 -1
- package/dist/lib/server/protocol.js +38 -1
- package/dist/lib/task/activity-log.js +8 -1
- package/dist/lib/task/artifact-lifecycle.js +33 -5
- package/dist/lib/task/capabilities.js +239 -0
- package/dist/lib/task/commands/status.js +32 -0
- package/dist/lib/task/commit-operation.js +12 -1
- package/dist/lib/task/control-authority.js +8 -5
- package/dist/lib/task/create-service.js +8 -8
- package/dist/lib/task/current-contract.js +5 -1
- package/dist/lib/task/decision-details.js +1 -1
- package/dist/lib/task/events.js +297 -17
- package/dist/lib/task/finalization.js +37 -13
- package/dist/lib/task/human-override.js +58 -36
- package/dist/lib/task/invalidation-command.js +86 -0
- package/dist/lib/task/invalidation.js +260 -0
- package/dist/lib/task/ledger-intents.js +37 -0
- package/dist/lib/task/local-artifact-finalization.js +374 -0
- package/dist/lib/task/orchestration.js +49 -93
- package/dist/lib/task/pr-delivery-fact.js +102 -101
- package/dist/lib/task/review-fingerprint.js +24 -2
- package/dist/lib/task/rework-intent.js +138 -0
- package/dist/lib/task/skill-scope.js +23 -0
- package/dist/lib/task/task-execution-lock.js +27 -17
- package/dist/lib/task/verification-engine.js +43 -16
- package/dist/lib/task/verification.js +3 -3
- package/dist/lib/task/workflow-warning-intents.js +1 -1
- package/dist/lib/task/write.js +11 -0
- package/dist/lib/version.js +11 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/next-steps.ts +6 -1
- package/lib/decide.ts +3 -3
- package/lib/init.ts +1 -1
- package/lib/internal/platform-checks.ts +3 -3
- package/lib/internal/platform-comment.ts +8 -8
- package/lib/internal/platform-context.ts +2 -2
- package/lib/internal/platform-issue.ts +7 -8
- package/lib/internal/platform-pr-review.ts +9 -9
- package/lib/internal/platform-pr.ts +47 -22
- package/lib/internal/platform-release-notes.ts +3 -3
- package/lib/internal/pr-review-grade.ts +10 -8
- package/lib/internal/release-workflow.ts +4 -4
- package/lib/internal/sandbox-control.ts +27 -3
- package/lib/internal/task-activity.ts +3 -3
- package/lib/internal/task-artifact.ts +65 -22
- package/lib/internal/task-create.ts +2 -2
- package/lib/internal/task-event.ts +6 -3
- package/lib/internal/task-finalization.ts +3 -3
- package/lib/internal/task-invalidation.ts +39 -0
- package/lib/internal/task-ledger.ts +11 -8
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-override.ts +6 -6
- package/lib/internal/task-review.ts +3 -3
- package/lib/internal/task-verify.ts +6 -6
- package/lib/internal/task-warning.ts +3 -3
- package/lib/platform/adapters.ts +3 -2
- package/lib/platform/change-request-git-evidence.ts +36 -9
- package/lib/platform/completion-backfill.ts +9 -8
- package/lib/platform/context.ts +97 -134
- package/lib/platform/github-client.ts +2 -2
- package/lib/platform/github-provider.ts +910 -0
- package/lib/platform/in-label-sync.ts +208 -0
- package/lib/platform/issue-comments.ts +97 -47
- package/lib/platform/issues.ts +344 -174
- package/lib/platform/metadata-labels.ts +5 -2
- package/lib/platform/none-provider.ts +35 -0
- package/lib/platform/pr-change-report.ts +645 -0
- package/lib/platform/pr-checks.ts +126 -106
- package/lib/platform/pr-review.ts +88 -85
- package/lib/platform/pr-summary.ts +560 -53
- package/lib/platform/provider-bridge.ts +82 -0
- package/lib/platform/provider-contract.ts +536 -0
- package/lib/platform/provider-loader.ts +307 -0
- package/lib/platform/provider-validation.ts +529 -0
- package/lib/platform/pull-request-metadata.ts +10 -7
- package/lib/platform/pull-requests.ts +826 -543
- package/lib/platform/release-notes.ts +76 -59
- package/lib/platform/releases.ts +77 -43
- package/lib/platform/resource-identity.ts +121 -0
- package/lib/platform/task-identities.ts +40 -0
- package/lib/platform/types.ts +4 -2
- package/lib/platform/verification-required.ts +11 -13
- package/lib/platform/verification-sync.ts +112 -292
- package/lib/pr-review/evidence-grading.ts +25 -8
- package/lib/run/index.ts +57 -22
- package/lib/sandbox/control/client.ts +105 -15
- package/lib/sandbox/control/executor.ts +4 -4
- package/lib/sandbox/control/protocol.ts +113 -0
- package/lib/sandbox/control/server.ts +431 -23
- package/lib/sandbox/control/state.ts +251 -1
- package/lib/server/protocol.ts +36 -1
- package/lib/task/activity-log.ts +8 -0
- package/lib/task/artifact-lifecycle.ts +32 -6
- package/lib/task/capabilities.ts +262 -0
- package/lib/task/commands/status.ts +38 -0
- package/lib/task/commit-operation.ts +12 -1
- package/lib/task/control-authority.ts +11 -8
- package/lib/task/create-service.ts +8 -8
- package/lib/task/current-contract.ts +6 -1
- package/lib/task/decision-details.ts +6 -2
- package/lib/task/events.ts +296 -16
- package/lib/task/finalization.ts +50 -14
- package/lib/task/human-override.ts +70 -46
- package/lib/task/invalidation-command.ts +86 -0
- package/lib/task/invalidation.ts +332 -0
- package/lib/task/ledger-intents.ts +35 -1
- package/lib/task/local-artifact-finalization.ts +563 -0
- package/lib/task/orchestration.ts +52 -85
- package/lib/task/pr-delivery-fact.ts +131 -148
- package/lib/task/review-fingerprint.ts +23 -2
- package/lib/task/rework-intent.ts +165 -0
- package/lib/task/skill-scope.ts +24 -0
- package/lib/task/task-execution-lock.ts +23 -14
- package/lib/task/verification-engine.ts +52 -17
- package/lib/task/verification.ts +4 -4
- package/lib/task/workflow-warning-intents.ts +1 -1
- package/lib/task/write.ts +19 -0
- package/lib/update.ts +1 -1
- package/lib/version.ts +15 -1
- package/package.json +7 -1
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +2 -0
- package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
- package/templates/.agents/rules/local-artifact-repair.en.md +9 -2
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +9 -2
- package/templates/.agents/rules/pr-sync.en.md +10 -4
- package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
- package/templates/.agents/rules/task-short-id.en.md +1 -4
- package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
- package/templates/.agents/skills/analyze-task/SKILL.en.md +13 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -3
- package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
- package/templates/.agents/skills/code-task/SKILL.en.md +27 -8
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +29 -10
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +4 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
- package/templates/.agents/skills/complete-task/SKILL.en.md +21 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/create-pr/config/verify.json +1 -0
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +10 -3
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +4 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +4 -2
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
- package/templates/.agents/templates/task.en.md +18 -1
- package/templates/.agents/templates/task.zh-CN.md +18 -1
- package/templates/.github/workflows/pr-label.yml +7 -26
package/dist/lib/run/index.js
CHANGED
|
@@ -7,57 +7,98 @@ import { loadShortIdByTaskId, normalizeShortIdInput } from '../task/short-id.js'
|
|
|
7
7
|
import { buildTuiCommand, renderPrompt, selectTui } from './tui.js';
|
|
8
8
|
import { getSkillRunSpec } from './skills.js';
|
|
9
9
|
import { runHostCommand } from './host.js';
|
|
10
|
-
const USAGE = `Usage: ai run <skill> [task-ref] [
|
|
10
|
+
const USAGE = `Usage: ai run --skill <skill> [--task <task-ref>] [--tui <name>] [--recreate] [-- <skill-args...>]
|
|
11
11
|
|
|
12
12
|
Task skills are scheduled inside the sandbox tmux session; ai run returns once
|
|
13
13
|
the tmux window is created. --recreate authorizes container-only replacement
|
|
14
14
|
when in-place sandbox recovery fails; it is not included in the TUI prompt.
|
|
15
15
|
|
|
16
16
|
Examples:
|
|
17
|
-
ai run create-task "describe the task" --tui codex
|
|
18
|
-
ai run code-task
|
|
19
|
-
function
|
|
20
|
-
const
|
|
17
|
+
ai run --skill create-task "describe the task" --tui codex
|
|
18
|
+
ai run --skill code-task --task 7 --tui codex --recreate`;
|
|
19
|
+
function optionValue(args, index, option) {
|
|
20
|
+
const value = args[index + 1];
|
|
21
|
+
if (!value || value.startsWith('--'))
|
|
22
|
+
throw new Error(`${option} requires a value`);
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
function extractHostOptions(args) {
|
|
26
|
+
const positionals = [];
|
|
27
|
+
const forwarded = [];
|
|
28
|
+
let skill = null;
|
|
29
|
+
let taskRef = null;
|
|
21
30
|
let tui = null;
|
|
31
|
+
let recreate = false;
|
|
32
|
+
let separator = false;
|
|
22
33
|
for (let i = 0; i < args.length; i += 1) {
|
|
23
34
|
const arg = args[i];
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
if (separator) {
|
|
36
|
+
forwarded.push(arg);
|
|
37
|
+
}
|
|
38
|
+
else if (arg === '--') {
|
|
39
|
+
separator = true;
|
|
40
|
+
}
|
|
41
|
+
else if (arg === '--skill') {
|
|
42
|
+
if (skill !== null)
|
|
43
|
+
throw new Error("duplicate option '--skill'");
|
|
44
|
+
skill = optionValue(args, i, '--skill');
|
|
45
|
+
i += 1;
|
|
46
|
+
}
|
|
47
|
+
else if (arg === '--task') {
|
|
48
|
+
if (taskRef !== null)
|
|
49
|
+
throw new Error("duplicate option '--task'");
|
|
50
|
+
taskRef = optionValue(args, i, '--task');
|
|
51
|
+
i += 1;
|
|
52
|
+
}
|
|
53
|
+
else if (arg === '--tui') {
|
|
54
|
+
if (tui !== null)
|
|
55
|
+
throw new Error("duplicate option '--tui'");
|
|
56
|
+
tui = optionValue(args, i, '--tui');
|
|
29
57
|
i += 1;
|
|
30
|
-
continue;
|
|
31
58
|
}
|
|
32
|
-
|
|
59
|
+
else if (arg === '--recreate') {
|
|
60
|
+
if (recreate)
|
|
61
|
+
throw new Error("duplicate option '--recreate'");
|
|
62
|
+
recreate = true;
|
|
63
|
+
}
|
|
64
|
+
else if (arg.startsWith('-')) {
|
|
65
|
+
throw new Error(`Unknown option '${arg}'`);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
positionals.push(arg);
|
|
69
|
+
}
|
|
33
70
|
}
|
|
34
|
-
return {
|
|
71
|
+
return { positionals, forwarded, skill, taskRef, tui, recreate };
|
|
35
72
|
}
|
|
36
73
|
export function parseRunArgs(args) {
|
|
37
74
|
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
38
75
|
throw new Error(USAGE);
|
|
39
76
|
}
|
|
40
|
-
const {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const [skill = '', maybeTaskRef, ...remaining] = rest;
|
|
77
|
+
const { positionals, forwarded, skill, taskRef, tui, recreate } = extractHostOptions(args);
|
|
78
|
+
if (!skill)
|
|
79
|
+
throw new Error("option '--skill' is required");
|
|
44
80
|
const spec = getSkillRunSpec(skill);
|
|
45
81
|
if (!spec)
|
|
46
82
|
throw new Error(`Unknown skill '${skill}'`);
|
|
47
83
|
if (spec.kind === 'create') {
|
|
84
|
+
if (taskRef !== null)
|
|
85
|
+
throw new Error("create-task does not accept '--task'");
|
|
48
86
|
if (recreate)
|
|
49
87
|
throw new Error('--recreate is only valid for sandbox task runs');
|
|
50
|
-
const createArgs =
|
|
88
|
+
const createArgs = [...positionals, ...forwarded];
|
|
51
89
|
if (createArgs.length === 0)
|
|
52
90
|
throw new Error('create-task requires a description');
|
|
53
91
|
return { skill, taskRef: null, args: createArgs, tui };
|
|
54
92
|
}
|
|
55
|
-
if (
|
|
93
|
+
if (positionals.length > 0) {
|
|
94
|
+
throw new Error('ai run requires --skill and --task; positional host arguments are not supported');
|
|
95
|
+
}
|
|
96
|
+
if (taskRef === null)
|
|
56
97
|
throw new Error(`${skill} requires a task-ref`);
|
|
57
98
|
return {
|
|
58
99
|
skill,
|
|
59
|
-
taskRef
|
|
60
|
-
args:
|
|
100
|
+
taskRef,
|
|
101
|
+
args: forwarded,
|
|
61
102
|
tui,
|
|
62
103
|
...(recreate ? { recreate: true } : {})
|
|
63
104
|
};
|
|
@@ -155,8 +196,7 @@ export async function runSkill(args, options = {}) {
|
|
|
155
196
|
const commandConfig = options.command ?? config?.command ?? {};
|
|
156
197
|
assertAllowedByConfig(parsed.skill, commandConfig);
|
|
157
198
|
const tui = selectTui(parsed.skill, { cliTui: parsed.tui, command: commandConfig });
|
|
158
|
-
const
|
|
159
|
-
const prompt = renderPrompt({ tui, skill: parsed.skill, args: promptArgs });
|
|
199
|
+
const prompt = renderPrompt({ tui, skill: parsed.skill, args: parsed.args });
|
|
160
200
|
const [file, argv] = buildTuiCommand(tui, prompt);
|
|
161
201
|
const command = [file, ...argv];
|
|
162
202
|
if (parsed.taskRef === null) {
|
|
@@ -3,23 +3,25 @@ import path from 'node:path';
|
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { SANDBOX_CONTROL_ADMISSION_WINDOW_MS, SANDBOX_CONTROL_MAX_BYTES, SANDBOX_CONTROL_STATUS_STALE_MS, isSandboxControlFamily } from './protocol.js';
|
|
5
5
|
import { normalizeAgentToken } from '../../agent-clients/tokens.js';
|
|
6
|
-
import { readSandboxControlStatus } from './state.js';
|
|
6
|
+
import { readSandboxControlPayload, readSandboxControlStatus } from './state.js';
|
|
7
7
|
const SANDBOX_CONTROL_RESPONSE_SETTLE_MS = 250;
|
|
8
8
|
export class SandboxControlClientError extends Error {
|
|
9
9
|
detail;
|
|
10
10
|
accepted;
|
|
11
|
-
|
|
11
|
+
requestId;
|
|
12
|
+
constructor(detail, accepted = false, requestId = null) {
|
|
12
13
|
super(detail.message);
|
|
13
14
|
this.name = 'SandboxControlClientError';
|
|
14
15
|
this.detail = detail;
|
|
15
16
|
this.accepted = accepted;
|
|
17
|
+
this.requestId = requestId;
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
function sleep(milliseconds) {
|
|
19
21
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
20
22
|
}
|
|
21
|
-
function clientError(code, message, retryable, accepted = false) {
|
|
22
|
-
throw new SandboxControlClientError({ code, message: `${code}: ${message}`, retryable }, accepted);
|
|
23
|
+
function clientError(code, message, retryable, accepted = false, requestId = null) {
|
|
24
|
+
throw new SandboxControlClientError({ code, message: `${code}: ${message}`, retryable }, accepted, requestId);
|
|
23
25
|
}
|
|
24
26
|
function preflight(statusDir, generation, now = Date.now()) {
|
|
25
27
|
let status;
|
|
@@ -47,12 +49,35 @@ function preflight(statusDir, generation, now = Date.now()) {
|
|
|
47
49
|
function parseResponse(raw, id) {
|
|
48
50
|
const response = JSON.parse(raw);
|
|
49
51
|
if (response.version !== 2 || response.id !== id
|
|
50
|
-
|| !['
|
|
51
|
-
|| (response.phase === 'completed' && !Number.isInteger(response.exitCode))
|
|
52
|
-
|
|
52
|
+
|| !['completed', 'rejected'].includes(response.phase)
|
|
53
|
+
|| (response.phase === 'completed' && !Number.isInteger(response.exitCode))
|
|
54
|
+
|| (response.outputState !== undefined && response.outputState !== 'available' && response.outputState !== 'unavailable')
|
|
55
|
+
|| (response.outputState === 'available' && !response.payload)
|
|
56
|
+
|| (response.outputState === 'unavailable' && response.payload !== null && response.payload !== undefined)) {
|
|
57
|
+
clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker response is invalid', false, false, id);
|
|
53
58
|
}
|
|
54
59
|
return response;
|
|
55
60
|
}
|
|
61
|
+
function readPublishedResponse(raw, id, channelDir) {
|
|
62
|
+
const response = parseResponse(raw, id);
|
|
63
|
+
if (response.outputState !== 'available')
|
|
64
|
+
return response;
|
|
65
|
+
const filePath = path.join(channelDir, 'responses', `${id}.payload.json`);
|
|
66
|
+
let payload;
|
|
67
|
+
try {
|
|
68
|
+
payload = readSandboxControlPayload(filePath);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker payload is missing or invalid', false, true, id);
|
|
72
|
+
}
|
|
73
|
+
if (!response.payload || response.payload.version !== payload.version
|
|
74
|
+
|| response.payload.id !== payload.id || response.payload.generation !== payload.generation
|
|
75
|
+
|| response.payload.stdoutBytes !== payload.stdoutBytes || response.payload.stderrBytes !== payload.stderrBytes
|
|
76
|
+
|| response.payload.stdoutSha256 !== payload.stdoutSha256 || response.payload.stderrSha256 !== payload.stderrSha256) {
|
|
77
|
+
clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker payload reference is invalid', false, true, id);
|
|
78
|
+
}
|
|
79
|
+
return { ...response, stdout: payload.stdout, stderr: payload.stderr };
|
|
80
|
+
}
|
|
56
81
|
function cancelPendingRequest(requestPath) {
|
|
57
82
|
try {
|
|
58
83
|
fs.unlinkSync(requestPath);
|
|
@@ -70,13 +95,16 @@ function exchangeSandboxControl(request, params) {
|
|
|
70
95
|
preflight(statusDir, request.generation);
|
|
71
96
|
const encoded = `${JSON.stringify(request)}\n`;
|
|
72
97
|
if (Buffer.byteLength(encoded, 'utf8') > SANDBOX_CONTROL_MAX_BYTES) {
|
|
73
|
-
clientError('SANDBOX_CONTROL_REQUEST_TOO_LARGE', 'request exceeds the control limit', false);
|
|
98
|
+
clientError('SANDBOX_CONTROL_REQUEST_TOO_LARGE', 'request exceeds the control limit', false, false, request.id);
|
|
74
99
|
}
|
|
75
100
|
const requestsDir = path.join(channelDir, 'requests');
|
|
76
101
|
const responsesDir = path.join(channelDir, 'responses');
|
|
77
102
|
const temporary = path.join(requestsDir, `.${request.id}.tmp`);
|
|
78
103
|
const requestPath = path.join(requestsDir, `${request.id}.json`);
|
|
79
104
|
const responsePath = path.join(responsesDir, `${request.id}.json`);
|
|
105
|
+
if (fs.existsSync(responsePath)) {
|
|
106
|
+
clientError('SANDBOX_CONTROL_REQUEST_REPLAYED', 'request id already has a terminal response', false, false, request.id);
|
|
107
|
+
}
|
|
80
108
|
fs.writeFileSync(temporary, encoded, { mode: 0o600, flag: 'wx' });
|
|
81
109
|
fs.renameSync(temporary, requestPath);
|
|
82
110
|
const deadline = Date.now() + (params.timeoutMs ?? 30_000);
|
|
@@ -84,6 +112,9 @@ function exchangeSandboxControl(request, params) {
|
|
|
84
112
|
let malformedResponseRaw = null;
|
|
85
113
|
let malformedResponseObservedAt = 0;
|
|
86
114
|
while (Date.now() < deadline) {
|
|
115
|
+
const acceptedPath = path.join(responsesDir, `${request.id}.accepted.json`);
|
|
116
|
+
if (fs.existsSync(acceptedPath))
|
|
117
|
+
accepted = true;
|
|
87
118
|
if (fs.existsSync(responsePath)) {
|
|
88
119
|
let raw;
|
|
89
120
|
try {
|
|
@@ -96,7 +127,7 @@ function exchangeSandboxControl(request, params) {
|
|
|
96
127
|
}
|
|
97
128
|
let response;
|
|
98
129
|
try {
|
|
99
|
-
response =
|
|
130
|
+
response = readPublishedResponse(raw, request.id, channelDir);
|
|
100
131
|
malformedResponseRaw = null;
|
|
101
132
|
}
|
|
102
133
|
catch (error) {
|
|
@@ -108,7 +139,7 @@ function exchangeSandboxControl(request, params) {
|
|
|
108
139
|
malformedResponseObservedAt = now;
|
|
109
140
|
}
|
|
110
141
|
else if (now - malformedResponseObservedAt >= SANDBOX_CONTROL_RESPONSE_SETTLE_MS) {
|
|
111
|
-
clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker response remained malformed', false, accepted);
|
|
142
|
+
clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker response remained malformed', false, accepted, request.id);
|
|
112
143
|
}
|
|
113
144
|
sleep(25);
|
|
114
145
|
continue;
|
|
@@ -117,7 +148,6 @@ function exchangeSandboxControl(request, params) {
|
|
|
117
148
|
accepted = true;
|
|
118
149
|
}
|
|
119
150
|
else {
|
|
120
|
-
fs.rmSync(responsePath, { force: true });
|
|
121
151
|
return response;
|
|
122
152
|
}
|
|
123
153
|
}
|
|
@@ -128,18 +158,69 @@ function exchangeSandboxControl(request, params) {
|
|
|
128
158
|
catch (error) {
|
|
129
159
|
if (cancelPendingRequest(requestPath))
|
|
130
160
|
throw error;
|
|
131
|
-
clientError('SANDBOX_CONTROL_RESULT_UNKNOWN', 'request was claimed before broker availability was lost; inspect domain state before retrying', false, true);
|
|
161
|
+
clientError('SANDBOX_CONTROL_RESULT_UNKNOWN', 'request was claimed before broker availability was lost; inspect domain state before retrying', false, true, request.id);
|
|
132
162
|
}
|
|
133
163
|
}
|
|
134
164
|
sleep(25);
|
|
135
165
|
}
|
|
136
166
|
if (accepted) {
|
|
137
|
-
clientError('SANDBOX_CONTROL_RESULT_UNKNOWN', 'accepted request did not produce a final result; inspect domain state before retrying', false, true);
|
|
167
|
+
clientError('SANDBOX_CONTROL_RESULT_UNKNOWN', 'accepted request did not produce a final result; inspect domain state before retrying', false, true, request.id);
|
|
138
168
|
}
|
|
139
169
|
if (!cancelPendingRequest(requestPath)) {
|
|
140
|
-
clientError('SANDBOX_CONTROL_RESULT_UNKNOWN', 'request was claimed but no acceptance result was observed; inspect domain state before retrying', false, true);
|
|
170
|
+
clientError('SANDBOX_CONTROL_RESULT_UNKNOWN', 'request was claimed but no acceptance result was observed; inspect domain state before retrying', false, true, request.id);
|
|
171
|
+
}
|
|
172
|
+
clientError('SANDBOX_CONTROL_BROKER_UNAVAILABLE', `broker did not accept the request within ${params.timeoutMs ?? 30_000}ms`, true, false, request.id);
|
|
173
|
+
}
|
|
174
|
+
export function recoverSandboxControl(requestId, params = {}) {
|
|
175
|
+
if (!/^[a-f0-9-]{16,64}$/u.test(requestId)) {
|
|
176
|
+
clientError('SANDBOX_CONTROL_REQUEST_INVALID', 'request id is invalid', false, true);
|
|
177
|
+
}
|
|
178
|
+
const channelDir = params.channelDir ?? process.env.AGENT_INFRA_CONTROL_DIR ?? '/run/agent-infra/control';
|
|
179
|
+
const responsePath = path.join(channelDir, 'responses', `${requestId}.json`);
|
|
180
|
+
const deadline = Date.now() + (params.timeoutMs ?? 30_000);
|
|
181
|
+
let accepted = false;
|
|
182
|
+
let malformedResponseRaw = null;
|
|
183
|
+
let malformedResponseObservedAt = 0;
|
|
184
|
+
while (Date.now() < deadline) {
|
|
185
|
+
if (fs.existsSync(path.join(channelDir, 'responses', `${requestId}.accepted.json`)))
|
|
186
|
+
accepted = true;
|
|
187
|
+
if (fs.existsSync(responsePath)) {
|
|
188
|
+
let raw;
|
|
189
|
+
try {
|
|
190
|
+
raw = fs.readFileSync(responsePath, 'utf8');
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
if (error.code === 'ENOENT')
|
|
194
|
+
continue;
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
let response;
|
|
198
|
+
try {
|
|
199
|
+
response = readPublishedResponse(raw, requestId, channelDir);
|
|
200
|
+
malformedResponseRaw = null;
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
if (!(error instanceof SyntaxError))
|
|
204
|
+
throw error;
|
|
205
|
+
const now = Date.now();
|
|
206
|
+
if (malformedResponseRaw !== raw) {
|
|
207
|
+
malformedResponseRaw = raw;
|
|
208
|
+
malformedResponseObservedAt = now;
|
|
209
|
+
}
|
|
210
|
+
else if (now - malformedResponseObservedAt >= SANDBOX_CONTROL_RESPONSE_SETTLE_MS) {
|
|
211
|
+
clientError('SANDBOX_CONTROL_RESPONSE_INVALID', 'broker response remained malformed', false, accepted, requestId);
|
|
212
|
+
}
|
|
213
|
+
sleep(25);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (response.phase === 'accepted')
|
|
217
|
+
accepted = true;
|
|
218
|
+
else
|
|
219
|
+
return response;
|
|
220
|
+
}
|
|
221
|
+
sleep(25);
|
|
141
222
|
}
|
|
142
|
-
clientError('
|
|
223
|
+
clientError('SANDBOX_CONTROL_RESULT_UNKNOWN', 'request did not produce a final result; inspect domain state before retrying', false, accepted, requestId);
|
|
143
224
|
}
|
|
144
225
|
function authority(params) {
|
|
145
226
|
const token = params.token ?? process.env.AGENT_INFRA_CONTROL_TOKEN;
|
|
@@ -191,7 +191,7 @@ function finalizationResult(result) {
|
|
|
191
191
|
}
|
|
192
192
|
export async function executeRequest(manifest, manifestPath, request, options = {}) {
|
|
193
193
|
if (request.family === 'task-create') {
|
|
194
|
-
const result = createTask(request.candidate, { repoRoot: manifest.repoRoot });
|
|
194
|
+
const result = await createTask(request.candidate, { repoRoot: manifest.repoRoot });
|
|
195
195
|
return {
|
|
196
196
|
exitCode: result.status === 'blocked' ? 2 : result.status === 'failed' ? 1 : 0,
|
|
197
197
|
stdout: `${JSON.stringify(result)}\n`,
|
|
@@ -255,7 +255,7 @@ export async function executeRequest(manifest, manifestPath, request, options =
|
|
|
255
255
|
manifestPath,
|
|
256
256
|
requestId: request.id
|
|
257
257
|
}), operation);
|
|
258
|
-
return finalizationResult(result);
|
|
258
|
+
return finalizationResult(await result);
|
|
259
259
|
}
|
|
260
260
|
const boundArgs = bindSandboxControlTask(request, manifest.taskId);
|
|
261
261
|
if (request.family === 'task-orchestration') {
|
|
@@ -325,7 +325,7 @@ export async function executeRequest(manifest, manifestPath, request, options =
|
|
|
325
325
|
}
|
|
326
326
|
if (operation.family !== 'task-lifecycle')
|
|
327
327
|
throw new Error('SANDBOX_CONTROL_LIFECYCLE_OPERATION_INVALID');
|
|
328
|
-
return format(dispatchTaskControlOperation(context, operation));
|
|
328
|
+
return format(await dispatchTaskControlOperation(context, operation));
|
|
329
329
|
}
|
|
330
330
|
export async function runSandboxControlExecutor(requestPath, nonce) {
|
|
331
331
|
const gateOwner = await waitForGate(nonce);
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { validateTaskCreateCandidate } from '../../task/create.js';
|
|
2
3
|
import { normalizeAgentToken } from '../../agent-clients/tokens.js';
|
|
3
4
|
export const SANDBOX_CONTROL_MAX_BYTES = 64 * 1024;
|
|
5
|
+
export const SANDBOX_CONTROL_MAX_LOGICAL_RECORDS = 1024;
|
|
6
|
+
export const SANDBOX_CONTROL_MAX_RESPONSE_BYTES = 64 * 1024 * 1024;
|
|
7
|
+
export const SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES = 1024 * 1024;
|
|
8
|
+
export const SANDBOX_CONTROL_RESERVATION_BYTES = SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES + 4 * 1024;
|
|
4
9
|
export const SANDBOX_CONTROL_ADMISSION_WINDOW_MS = 2_000;
|
|
5
10
|
export const SANDBOX_CONTROL_STATUS_INTERVAL_MS = 250;
|
|
6
11
|
export const SANDBOX_CONTROL_STATUS_STALE_MS = 1_500;
|
|
@@ -26,6 +31,78 @@ export class SandboxControlProtocolError extends Error {
|
|
|
26
31
|
function fail(code, message, retryable = false) {
|
|
27
32
|
throw new SandboxControlProtocolError(code, message, retryable);
|
|
28
33
|
}
|
|
34
|
+
export function parseSandboxControlResultEvidence(value) {
|
|
35
|
+
const evidence = value;
|
|
36
|
+
const keys = evidence && typeof evidence === 'object' ? Object.keys(evidence).sort().join(',') : '';
|
|
37
|
+
if (keys !== 'captureState,exitCode,generation,id,stderrBytes,stderrSha256,stdoutBytes,stdoutSha256,version'
|
|
38
|
+
|| evidence?.version !== 1
|
|
39
|
+
|| typeof evidence.id !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(evidence.id)
|
|
40
|
+
|| typeof evidence.generation !== 'string' || evidence.generation.length === 0
|
|
41
|
+
|| !Number.isSafeInteger(evidence.exitCode)
|
|
42
|
+
|| !Number.isSafeInteger(evidence.stdoutBytes) || evidence.stdoutBytes < 0
|
|
43
|
+
|| !Number.isSafeInteger(evidence.stderrBytes) || evidence.stderrBytes < 0
|
|
44
|
+
|| evidence.stdoutBytes + evidence.stderrBytes > SANDBOX_CONTROL_MAX_RESPONSE_BYTES
|
|
45
|
+
|| typeof evidence.stdoutSha256 !== 'string' || !/^[a-f0-9]{64}$/u.test(evidence.stdoutSha256)
|
|
46
|
+
|| typeof evidence.stderrSha256 !== 'string' || !/^[a-f0-9]{64}$/u.test(evidence.stderrSha256)
|
|
47
|
+
|| evidence.captureState !== 'metadata-only') {
|
|
48
|
+
throw new Error('SANDBOX_CONTROL_RESULT_EVIDENCE_INVALID');
|
|
49
|
+
}
|
|
50
|
+
return evidence;
|
|
51
|
+
}
|
|
52
|
+
function validPayloadReference(value, id, generation) {
|
|
53
|
+
const payload = value;
|
|
54
|
+
const keys = payload && typeof payload === 'object' ? Object.keys(payload).sort().join(',') : '';
|
|
55
|
+
return keys === 'generation,id,stderrBytes,stderrSha256,stdoutBytes,stdoutSha256,version'
|
|
56
|
+
&& payload?.version === 1
|
|
57
|
+
&& payload.id === id
|
|
58
|
+
&& payload.generation === generation
|
|
59
|
+
&& Number.isSafeInteger(payload.stdoutBytes) && payload.stdoutBytes >= 0
|
|
60
|
+
&& Number.isSafeInteger(payload.stderrBytes) && payload.stderrBytes >= 0
|
|
61
|
+
&& payload.stdoutBytes + payload.stderrBytes <= SANDBOX_CONTROL_MAX_RESPONSE_BYTES
|
|
62
|
+
&& typeof payload.stdoutSha256 === 'string' && /^[a-f0-9]{64}$/u.test(payload.stdoutSha256)
|
|
63
|
+
&& typeof payload.stderrSha256 === 'string' && /^[a-f0-9]{64}$/u.test(payload.stderrSha256);
|
|
64
|
+
}
|
|
65
|
+
export function parseSandboxControlPayload(value) {
|
|
66
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
67
|
+
throw new Error('SANDBOX_CONTROL_PAYLOAD_INVALID');
|
|
68
|
+
const payload = value;
|
|
69
|
+
const reference = {
|
|
70
|
+
version: payload.version,
|
|
71
|
+
id: payload.id,
|
|
72
|
+
generation: payload.generation,
|
|
73
|
+
stdoutBytes: payload.stdoutBytes,
|
|
74
|
+
stderrBytes: payload.stderrBytes,
|
|
75
|
+
stdoutSha256: payload.stdoutSha256,
|
|
76
|
+
stderrSha256: payload.stderrSha256
|
|
77
|
+
};
|
|
78
|
+
const keys = Object.keys(payload).sort().join(',');
|
|
79
|
+
if (keys !== 'generation,id,stderr,stderrBytes,stderrSha256,stdout,stdoutBytes,stdoutSha256,version'
|
|
80
|
+
|| !validPayloadReference(reference, reference.id, reference.generation)
|
|
81
|
+
|| typeof payload.stdout !== 'string' || typeof payload.stderr !== 'string'
|
|
82
|
+
|| Buffer.byteLength(payload.stdout, 'utf8') !== payload.stdoutBytes
|
|
83
|
+
|| Buffer.byteLength(payload.stderr, 'utf8') !== payload.stderrBytes
|
|
84
|
+
|| createHash('sha256').update(payload.stdout, 'utf8').digest('hex') !== payload.stdoutSha256
|
|
85
|
+
|| createHash('sha256').update(payload.stderr, 'utf8').digest('hex') !== payload.stderrSha256) {
|
|
86
|
+
throw new Error('SANDBOX_CONTROL_PAYLOAD_INVALID');
|
|
87
|
+
}
|
|
88
|
+
return payload;
|
|
89
|
+
}
|
|
90
|
+
export function parseSandboxControlReservation(value) {
|
|
91
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
92
|
+
throw new Error('SANDBOX_CONTROL_RESERVATION_INVALID');
|
|
93
|
+
const reservation = value;
|
|
94
|
+
const keys = Object.keys(reservation).sort().join(',');
|
|
95
|
+
if (keys !== 'bytes,createdAt,generation,id,logicalRecords,version'
|
|
96
|
+
|| reservation.version !== 1
|
|
97
|
+
|| typeof reservation.id !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(reservation.id)
|
|
98
|
+
|| typeof reservation.generation !== 'string' || reservation.generation.length === 0
|
|
99
|
+
|| reservation.logicalRecords !== 1
|
|
100
|
+
|| !Number.isSafeInteger(reservation.bytes) || reservation.bytes < SANDBOX_CONTROL_RESERVATION_BYTES
|
|
101
|
+
|| !Number.isSafeInteger(reservation.createdAt)) {
|
|
102
|
+
throw new Error('SANDBOX_CONTROL_RESERVATION_INVALID');
|
|
103
|
+
}
|
|
104
|
+
return reservation;
|
|
105
|
+
}
|
|
29
106
|
export function isSandboxControlFamily(value) {
|
|
30
107
|
return SANDBOX_CONTROL_FAMILIES.includes(value);
|
|
31
108
|
}
|