@fitlab-ai/agent-infra 0.10.0 → 0.11.0
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 +2 -6
- package/README.zh-CN.md +2 -6
- package/bin/internal-cli.sh +0 -13
- package/dist/bin/internal-cli.js +11 -49
- package/dist/lib/internal/cli-route-inventory.js +3 -4
- package/dist/lib/internal/platform-comment.js +60 -2
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +22 -12
- package/dist/lib/internal/task-orchestration.js +2 -2
- package/dist/lib/internal/task-review.js +10 -5
- package/dist/lib/platform/activity-recovery.js +159 -0
- package/dist/lib/platform/issue-comments.js +84 -32
- package/dist/lib/platform/task-comment-projection.js +70 -0
- package/dist/lib/platform/task-recovery.js +55 -0
- package/dist/lib/platform/verification-sync.js +43 -9
- package/dist/lib/sandbox/control/executor.js +4 -2
- package/dist/lib/sandbox/control/task-workflow.js +1 -1
- package/dist/lib/sandbox/control/workflow-executor.js +83 -21
- package/dist/lib/server/process-state.js +17 -5
- package/dist/lib/task/artifact-command.js +6 -3
- package/dist/lib/task/artifact-operations.js +1 -1
- package/dist/lib/task/artifact-recovery.js +114 -68
- package/dist/lib/task/artifact-repair-intent.js +25 -8
- package/dist/lib/task/control-authority.js +1 -1
- package/dist/lib/task/events.js +6 -6
- package/dist/lib/task/intermediate-cleanup.js +28 -3
- package/dist/lib/task/local-artifact-finalization.js +78 -14
- package/dist/lib/task/review-command.js +2 -2
- package/dist/lib/task/review-finalization.js +203 -10
- package/dist/lib/{host-control → task}/secure-fs.js +8 -19
- package/dist/lib/task/workflow-command.js +2 -0
- package/dist/lib/task/workflow-dispatch.js +161 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/internal/cli-route-inventory.ts +3 -4
- package/lib/internal/platform-comment.ts +53 -2
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +1 -1
- package/lib/internal/task-lifecycle.ts +1 -1
- package/lib/internal/task-operation-registry.ts +22 -12
- package/lib/internal/task-orchestration.ts +1 -1
- package/lib/internal/task-review.ts +9 -5
- package/lib/platform/activity-recovery.ts +179 -0
- package/lib/platform/issue-comments.ts +98 -31
- package/lib/platform/task-comment-projection.ts +79 -0
- package/lib/platform/task-recovery.ts +54 -0
- package/lib/platform/verification-sync.ts +46 -13
- package/lib/sandbox/control/executor.ts +4 -2
- package/lib/sandbox/control/protocol.ts +1 -1
- package/lib/sandbox/control/task-workflow.ts +1 -1
- package/lib/sandbox/control/workflow-executor.ts +102 -21
- package/lib/server/process-state.ts +26 -5
- package/lib/task/artifact-command.ts +6 -4
- package/lib/task/artifact-operations.ts +1 -1
- package/lib/task/artifact-recovery.ts +120 -67
- package/lib/task/artifact-repair-intent.ts +38 -11
- package/lib/task/control-authority.ts +1 -1
- package/lib/task/events.ts +6 -6
- package/lib/task/intermediate-cleanup.ts +18 -2
- package/lib/task/local-artifact-finalization.ts +85 -11
- package/lib/task/review-command.ts +1 -1
- package/lib/task/review-finalization.ts +203 -8
- package/lib/{host-control → task}/secure-fs.ts +10 -32
- package/lib/task/workflow-command.ts +2 -0
- package/lib/task/workflow-dispatch.ts +171 -0
- package/package.json +1 -1
- package/templates/.agents/rules/issue-sync.en.md +9 -1
- package/templates/.agents/rules/issue-sync.zh-CN.md +10 -3
- package/templates/.agents/rules/local-artifact-repair.en.md +7 -6
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +10 -8
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.en.md +9 -4
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +9 -4
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +3 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -2
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/dist/lib/host-control/audit.js +0 -19
- package/dist/lib/host-control/client.js +0 -142
- package/dist/lib/host-control/command.js +0 -49
- package/dist/lib/host-control/path.js +0 -160
- package/dist/lib/host-control/server.js +0 -159
- package/dist/lib/host-control/service.js +0 -112
- package/dist/lib/internal/host-control.js +0 -28
- package/lib/host-control/audit.ts +0 -22
- package/lib/host-control/client.ts +0 -172
- package/lib/host-control/command.ts +0 -60
- package/lib/host-control/path.ts +0 -167
- package/lib/host-control/server.ts +0 -180
- package/lib/host-control/service.ts +0 -133
- package/lib/internal/host-control.ts +0 -28
package/README.md
CHANGED
|
@@ -95,13 +95,9 @@ Every command above works the same way in Claude Code, Codex, Antigravity CLI, a
|
|
|
95
95
|
|
|
96
96
|
### Sandbox control boundary
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
Direct-host lifecycle and workflow commands execute in the current CLI process. Task-bound sandboxes route requests to their broker; the broker verifies the request, manifest, generation, owner, lease, and controller before starting a one-request executor. There is no host socket, worker token, or user-level task-control service.
|
|
99
99
|
|
|
100
|
-
The
|
|
101
|
-
|
|
102
|
-
On macOS, the installer retries service registration during reload. If registration or startup still fails, it exits with an error and a recovery command.
|
|
103
|
-
|
|
104
|
-
The task directory exposed inside a task-bound sandbox is a writable projection. Artifact commands accept only typed workflow operations and canonical artifact basenames. The host opens candidates with `O_NOFOLLOW`, reads and hashes them through one file descriptor, checks observable metadata changes, and atomically lands validated bytes into the authoritative task directory. Changes to the projection's protected topology fail closed and never turn the projection into task state.
|
|
100
|
+
The task directory exposed inside a task-bound sandbox is writable only for its bound task. Artifact commands accept typed workflow operations and canonical artifact basenames. The executor opens candidates with `O_NOFOLLOW`, reads and hashes them through one file descriptor, checks observable metadata changes, and atomically lands validated bytes into the authoritative task directory.
|
|
105
101
|
|
|
106
102
|
## Quick Start
|
|
107
103
|
|
package/README.zh-CN.md
CHANGED
|
@@ -94,13 +94,9 @@ agent-infra 的目标就是把这层共享基础设施标准化。它为所有
|
|
|
94
94
|
|
|
95
95
|
### 沙箱控制边界
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
direct-host 生命周期和工作流命令在当前 CLI 进程中执行。任务沙箱把请求发送给所属 broker;broker 会先校验请求、manifest、generation、owner、lease 和 controller,再启动一次性 executor。系统不再使用宿主 socket、worker token 或用户级 task-control 服务。
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
macOS 重载期间,安装脚本会重试服务注册;若最终仍无法注册或启动,则以错误退出并提供恢复命令。
|
|
102
|
-
|
|
103
|
-
任务沙箱内的 task 目录是可写投影。产物命令只接受类型化 workflow 操作和规范的产物 basename。宿主使用 `O_NOFOLLOW` 打开候选文件,通过同一个文件描述符读取并计算摘要,检查可观察的元数据变化,再把已校验字节原子落入权威 task 目录。投影的受保护拓扑发生变化时会失败关闭,投影修改不会变成任务状态。
|
|
99
|
+
任务沙箱内的 task 目录只允许其绑定任务读写。产物命令只接受类型化 workflow 操作和规范的产物 basename。executor 使用 `O_NOFOLLOW` 打开候选文件,通过同一个文件描述符读取并计算摘要,检查可观察的元数据变化,再把已校验字节原子落入权威 task 目录。
|
|
104
100
|
|
|
105
101
|
## 快速开始
|
|
106
102
|
|
package/bin/internal-cli.sh
CHANGED
|
@@ -26,19 +26,6 @@ else
|
|
|
26
26
|
internal_cli="$script_dir/../dist/bin/internal-cli.js"
|
|
27
27
|
fi
|
|
28
28
|
|
|
29
|
-
command=${1-}
|
|
30
|
-
task_control_command=false
|
|
31
|
-
case "$command" in
|
|
32
|
-
task-lifecycle|task-orchestration|task-finalization) task_control_command=true ;;
|
|
33
|
-
esac
|
|
34
|
-
|
|
35
|
-
status_mount_present=false
|
|
36
|
-
if [ -d /run/agent-infra/control-status ]; then status_mount_present=true; fi
|
|
37
|
-
launch_config=${AGENT_INFRA_TASK_ID-}${AGENT_INFRA_CONTROL_TOKEN-}${AGENT_INFRA_CONTROL_GENERATION-}${AGENT_INFRA_CONTROL_DIR-}${AGENT_INFRA_CONTROL_STATUS_DIR-}${AGENT_INFRA_RUNTIME_DIR-}${AGENT_INFRA_EXECUTOR_MANIFEST-}
|
|
38
|
-
if [ "$task_control_command" = true ] && [ "$status_mount_present" = true ] && [ -z "$launch_config" ] && [ -z "${AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT-}" ]; then
|
|
39
|
-
printf '%s\n' '{"status":"failed","changed":false,"error":{"code":"SANDBOX_CONTROL_IDENTITY_MISSING","message":"sandbox control identity is present but its launch configuration is missing"}}'
|
|
40
|
-
exit 1
|
|
41
|
-
fi
|
|
42
29
|
# The outer launcher owns the Node startup boundary. A task-bound process must
|
|
43
30
|
# not pass preload, import, or loader injection into the control router.
|
|
44
31
|
unset NODE_OPTIONS NODE_PATH
|
package/dist/bin/internal-cli.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { formatTaskViewDiagnostic, guardTaskOperation, resolveSandboxControlTransport, TaskViewOperationError } from '../lib/internal/task-operation-registry.js';
|
|
3
3
|
import { INTERNAL_HANDLER_ROUTE_SELECTORS } from '../lib/internal/cli-route-inventory.js';
|
|
4
|
-
import { hostControlRequestForCommand, requestHostControl, HostControlClientError, HOST_CONTROL_TEST_ENVIRONMENT_KEYS } from '../lib/host-control/client.js';
|
|
5
|
-
import { resolveHostControlEndpoint, readHostControlWorkerToken } from '../lib/host-control/path.js';
|
|
6
4
|
const [major = 0, minor = 0] = process.versions.node.split('.').map((part) => parseInt(part, 10));
|
|
7
5
|
if (major < 22 || (major === 22 && minor < 9)) {
|
|
8
6
|
process.stderr.write(`agent-infra-internal requires Node.js >= 22.9.0 (current: ${process.version})\n`);
|
|
@@ -18,9 +16,13 @@ const taskWorkflowCommand = command === 'task-artifact'
|
|
|
18
16
|
|| command === 'task-invalidation'
|
|
19
17
|
|| command === 'task-warning';
|
|
20
18
|
const manualValidationWorkflowCommand = command === 'manual-validation';
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
const taskControlMarkers = [
|
|
20
|
+
'AGENT_INFRA_TASK_ID', 'AGENT_INFRA_CONTROL_TOKEN', 'AGENT_INFRA_CONTROL_GENERATION',
|
|
21
|
+
'AGENT_INFRA_CONTROL_DIR', 'AGENT_INFRA_CONTROL_STATUS_DIR', 'AGENT_INFRA_RUNTIME_DIR',
|
|
22
|
+
'AGENT_INFRA_CONTROL_CONTROLLER_BINDING', 'AGENT_INFRA_EXECUTOR_MANIFEST'
|
|
23
|
+
];
|
|
24
|
+
const markerlessHelp = ['--help', '-h'].includes(process.argv[3] ?? '')
|
|
25
|
+
&& taskControlMarkers.every((key) => !process.env[key]);
|
|
24
26
|
let taskViewGuardFailed = false;
|
|
25
27
|
try {
|
|
26
28
|
const guard = guardTaskOperation('internal', command, process.argv.slice(3));
|
|
@@ -45,44 +47,8 @@ function taskControlTransportFailure(message, code = 'TASK_CONTROL_TRANSPORT_INV
|
|
|
45
47
|
})}\n`);
|
|
46
48
|
process.exit(1);
|
|
47
49
|
}
|
|
48
|
-
async function runHostControlCommand(commandName, args) {
|
|
49
|
-
let response;
|
|
50
|
-
try {
|
|
51
|
-
const testEndpoint = process.env.AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT;
|
|
52
|
-
const forwardedEnvironment = testEndpoint
|
|
53
|
-
? Object.fromEntries(HOST_CONTROL_TEST_ENVIRONMENT_KEYS.flatMap((key) => process.env[key] === undefined ? [] : [[key, process.env[key]]]))
|
|
54
|
-
: {};
|
|
55
|
-
response = await requestHostControl({
|
|
56
|
-
request: hostControlRequestForCommand(commandName, args, process.cwd(), forwardedEnvironment),
|
|
57
|
-
endpoint: process.env.AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT ?? resolveHostControlEndpoint()
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
if (error instanceof HostControlClientError) {
|
|
62
|
-
taskControlTransportFailure(error.message, error.changed === false
|
|
63
|
-
? 'SANDBOX_CONTROL_HOST_AUTHORITY_UNAVAILABLE' : error.code, error.changed);
|
|
64
|
-
}
|
|
65
|
-
throw error;
|
|
66
|
-
}
|
|
67
|
-
process.stdout.write(response.stdout);
|
|
68
|
-
process.stderr.write(response.stderr);
|
|
69
|
-
process.exitCode = response.exitCode;
|
|
70
|
-
}
|
|
71
|
-
const hostWorkerRequested = process.env.AGENT_INFRA_HOST_CONTROL_WORKER === '1';
|
|
72
|
-
const hostWorker = hostWorkerRequested && (() => {
|
|
73
|
-
try {
|
|
74
|
-
const endpoint = process.env.AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT ?? resolveHostControlEndpoint();
|
|
75
|
-
return process.env.AGENT_INFRA_HOST_CONTROL_WORKER_TOKEN === readHostControlWorkerToken(endpoint);
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
})();
|
|
81
|
-
if (hostWorkerRequested && !hostWorker && (taskControlCommand || taskWorkflowCommand || manualValidationWorkflowCommand)) {
|
|
82
|
-
taskControlTransportFailure('host-control worker authorization is invalid', 'SANDBOX_CONTROL_HOST_AUTHORITY_UNAVAILABLE');
|
|
83
|
-
}
|
|
84
50
|
let controlRouted = false;
|
|
85
|
-
if (!taskViewGuardFailed && (taskControlCommand || taskWorkflowCommand || manualValidationWorkflowCommand)
|
|
51
|
+
if (!taskViewGuardFailed && !markerlessHelp && (taskControlCommand || taskWorkflowCommand || manualValidationWorkflowCommand)) {
|
|
86
52
|
const transport = resolveSandboxControlTransport(process.env);
|
|
87
53
|
switch (transport.kind) {
|
|
88
54
|
case 'fail-closed': {
|
|
@@ -91,7 +57,8 @@ if (!taskViewGuardFailed && (taskControlCommand || taskWorkflowCommand || manual
|
|
|
91
57
|
break;
|
|
92
58
|
}
|
|
93
59
|
case 'direct-host':
|
|
94
|
-
|
|
60
|
+
// The host CLI is the direct-host authority. Domain handlers retain their
|
|
61
|
+
// task locks, atomic writes, and operation-specific recovery facts.
|
|
95
62
|
break;
|
|
96
63
|
case 'broker-client': {
|
|
97
64
|
const { sandboxControl } = await import('../lib/internal/sandbox-control.js');
|
|
@@ -99,7 +66,7 @@ if (!taskViewGuardFailed && (taskControlCommand || taskWorkflowCommand || manual
|
|
|
99
66
|
break;
|
|
100
67
|
}
|
|
101
68
|
}
|
|
102
|
-
controlRouted =
|
|
69
|
+
controlRouted = transport.kind === 'broker-client';
|
|
103
70
|
}
|
|
104
71
|
if (!controlRouted && !taskViewGuardFailed && internalRouteRegistered)
|
|
105
72
|
switch (command) {
|
|
@@ -128,11 +95,6 @@ if (!controlRouted && !taskViewGuardFailed && internalRouteRegistered)
|
|
|
128
95
|
await sandboxControl(process.argv.slice(3));
|
|
129
96
|
break;
|
|
130
97
|
}
|
|
131
|
-
case 'host-control': {
|
|
132
|
-
const { hostControl } = await import('../lib/internal/host-control.js');
|
|
133
|
-
await hostControl(process.argv.slice(3));
|
|
134
|
-
break;
|
|
135
|
-
}
|
|
136
98
|
case 'agent-client': {
|
|
137
99
|
const { agentClient } = await import('../lib/internal/agent-client.js');
|
|
138
100
|
agentClient(process.argv.slice(3));
|
|
@@ -33,7 +33,6 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
33
33
|
'task-create': ['input'],
|
|
34
34
|
'task-qualification': ['proposal', 'confirm', 'supersede', 'revoke'],
|
|
35
35
|
'sandbox-control': ['serve', 'execute', 'recover', 'client'],
|
|
36
|
-
'host-control': ['serve', 'status', 'install', 'uninstall'],
|
|
37
36
|
'agent-client': ['next-steps', 'model-selection'],
|
|
38
37
|
'codex-lifecycle': ['preflight', 'capability-arm', 'hook-event', 'resolve-start', 'resolve-stop', 'consume'],
|
|
39
38
|
'codex-sandbox-controller': ['verify-context', 'run'],
|
|
@@ -44,7 +43,7 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
44
43
|
'platform-security': ['read', 'dismiss'],
|
|
45
44
|
'platform-metadata': ['init-labels', 'init-milestones'],
|
|
46
45
|
'platform-context': ['resolve'],
|
|
47
|
-
'platform-comment': ['list', 'owner', 'backfill', 'sync'],
|
|
46
|
+
'platform-comment': ['list', 'recover', 'owner', 'backfill', 'sync'],
|
|
48
47
|
'platform-issue': ['inspect', 'create', 'bind', 'sync'],
|
|
49
48
|
'platform-pr': ['inspect', 'summary-context', 'resolve-external', 'create', 'bind', 'skip', 'sync', 'change-report', 'summary-sync', 'sync-in-labels'],
|
|
50
49
|
'platform-pr-review': ['inspect', 'list', 'publish-pr', 'publish-task'],
|
|
@@ -54,9 +53,9 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
54
53
|
'task-ledger': ['decision-next-id', 'stage-status', 'finding-upsert', 'finding-respond', 'finding-review', 'decision-upsert', 'rework-intent-upsert'],
|
|
55
54
|
'task-warning': ['list', 'add', 'set-status'],
|
|
56
55
|
'task-activity': ['pr-review-inspect', 'pr-review-start', 'pr-review-complete', 'pr-review-terminate'],
|
|
57
|
-
'task-artifact': ['inspect', 'init', 'finalize-local'],
|
|
56
|
+
'task-artifact': ['inspect', 'init', 'preflight', 'finalize-local'],
|
|
58
57
|
'task-orchestration': ['status', 'progress'],
|
|
59
|
-
'task-review': ['finalize-summary'],
|
|
58
|
+
'task-review': ['preflight', 'finalize-summary'],
|
|
60
59
|
'task-event': ['event'],
|
|
61
60
|
'task-invalidation': ['reconcile'],
|
|
62
61
|
'task-override': ['diagnose', 'issue', 'consume'],
|
|
@@ -2,9 +2,11 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.js';
|
|
4
4
|
import { checkPlatformCommentOwner, listPlatformComments, syncPlatformComment } from '../platform/issue-comments.js';
|
|
5
|
+
import { recoverTaskFromComments } from '../platform/task-recovery.js';
|
|
5
6
|
import { backfillCompletionComments } from '../platform/completion-backfill.js';
|
|
6
7
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
|
|
7
8
|
const USAGE = `Usage: agent-infra-internal platform-comment list --issue <token> [--cwd <path>]
|
|
9
|
+
agent-infra-internal platform-comment recover --issue <token> --task-id <TASK-id> --output <staging-task.md> [--cwd <path>]
|
|
8
10
|
agent-infra-internal platform-comment owner <task-ref> [--cwd <path>]
|
|
9
11
|
agent-infra-internal platform-comment backfill <task-ref> --agent <agent> [--cwd <path>]
|
|
10
12
|
agent-infra-internal platform-comment sync <task-ref> --kind <kind> --agent <agent> [--artifact <file>] [--body-file <path|->] [--status-label <label>] [--backfill] [--cwd <path>]
|
|
@@ -26,7 +28,7 @@ function parseFlags(args, start) {
|
|
|
26
28
|
const seen = new Set();
|
|
27
29
|
for (let index = start; index < args.length; index += 1) {
|
|
28
30
|
const flag = args[index];
|
|
29
|
-
if (!['--issue', '--cwd', '--kind', '--agent', '--artifact', '--body-file', '--status-label', '--backfill'].includes(flag)) {
|
|
31
|
+
if (!['--issue', '--task-id', '--output', '--cwd', '--kind', '--agent', '--artifact', '--body-file', '--status-label', '--backfill'].includes(flag)) {
|
|
30
32
|
return { values, error: `unknown option '${flag}'` };
|
|
31
33
|
}
|
|
32
34
|
if (seen.has(flag))
|
|
@@ -58,6 +60,7 @@ async function platformComment(args = []) {
|
|
|
58
60
|
const operation = args[0];
|
|
59
61
|
if (!operation || ![
|
|
60
62
|
internalHandlerRoute('platform-comment', 'list', operation),
|
|
63
|
+
internalHandlerRoute('platform-comment', 'recover', operation),
|
|
61
64
|
internalHandlerRoute('platform-comment', 'owner', operation),
|
|
62
65
|
internalHandlerRoute('platform-comment', 'backfill', operation),
|
|
63
66
|
internalHandlerRoute('platform-comment', 'sync', operation)
|
|
@@ -65,7 +68,7 @@ async function platformComment(args = []) {
|
|
|
65
68
|
fail('a valid operation is required');
|
|
66
69
|
return;
|
|
67
70
|
}
|
|
68
|
-
const hasTaskRef = operation !== 'list';
|
|
71
|
+
const hasTaskRef = operation !== 'list' && operation !== 'recover';
|
|
69
72
|
const taskRef = hasTaskRef ? args[1] : undefined;
|
|
70
73
|
if (hasTaskRef && (!taskRef || taskRef.startsWith('--'))) {
|
|
71
74
|
fail(`${operation} requires a task ref`);
|
|
@@ -91,6 +94,61 @@ async function platformComment(args = []) {
|
|
|
91
94
|
finish(await listPlatformComments(issue, cwd));
|
|
92
95
|
return;
|
|
93
96
|
}
|
|
97
|
+
if (internalHandlerRoute('platform-comment', 'recover', operation)) {
|
|
98
|
+
const issue = parsed.values.issue;
|
|
99
|
+
const taskId = parsed.values.taskId;
|
|
100
|
+
const output = parsed.values.output;
|
|
101
|
+
if (typeof issue !== 'string' || !issue || typeof taskId !== 'string' || !/^TASK-\d{8}-\d{6}$/.test(taskId)
|
|
102
|
+
|| typeof output !== 'string' || !output) {
|
|
103
|
+
fail('recover requires --issue <token> --task-id <TASK-id> --output <staging-task.md>');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const unexpected = Object.keys(parsed.values).find((key) => !['issue', 'taskId', 'output', 'cwd'].includes(key));
|
|
107
|
+
if (unexpected) {
|
|
108
|
+
fail(`recover does not accept '--${unexpected}'`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const outputPath = path.resolve(cwd, output);
|
|
112
|
+
const workspaceDir = path.resolve(cwd, '.agents', 'workspace');
|
|
113
|
+
const stagingDir = path.dirname(outputPath);
|
|
114
|
+
const stagingParent = fs.existsSync(stagingDir) ? path.dirname(fs.realpathSync(stagingDir)) : '';
|
|
115
|
+
const realWorkspace = fs.existsSync(workspaceDir) ? fs.realpathSync(workspaceDir) : '';
|
|
116
|
+
if (path.basename(outputPath) !== 'task.md' || stagingParent !== realWorkspace
|
|
117
|
+
|| !path.basename(stagingDir).startsWith('.restore-staging-') || !fs.existsSync(stagingDir)
|
|
118
|
+
|| !fs.lstatSync(stagingDir).isDirectory() || fs.existsSync(outputPath)) {
|
|
119
|
+
fail('recover output must be a new task.md inside an existing .agents/workspace/.restore-staging-* directory');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const listed = await listPlatformComments(issue, cwd);
|
|
123
|
+
if (listed.status === 'failed' || listed.status === 'blocked' || !listed.comments) {
|
|
124
|
+
finish(listed);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const taskContent = recoverTaskFromComments({ taskId, comments: listed.comments });
|
|
129
|
+
fs.writeFileSync(outputPath, taskContent, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
|
|
130
|
+
finish({
|
|
131
|
+
...listed,
|
|
132
|
+
status: 'applied',
|
|
133
|
+
changed: true,
|
|
134
|
+
operations: [{ name: `recovery:task:${taskId}`, status: 'applied', reasonCode: null }],
|
|
135
|
+
error: null
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
finish({
|
|
140
|
+
...listed,
|
|
141
|
+
status: 'failed',
|
|
142
|
+
changed: false,
|
|
143
|
+
error: {
|
|
144
|
+
code: 'RECOVERY_EVIDENCE_MISSING',
|
|
145
|
+
message: error instanceof Error ? error.message : String(error),
|
|
146
|
+
retryable: false
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
94
152
|
if (internalHandlerRoute('platform-comment', 'owner', operation)) {
|
|
95
153
|
const unexpected = Object.keys(parsed.values).find((key) => key !== 'cwd');
|
|
96
154
|
if (unexpected) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseArtifactCommand, executeArtifactCommand } from '../task/artifact-command.js';
|
|
2
2
|
import { ensureInternalHandlerRoute } from './cli-route-inventory.js';
|
|
3
|
-
const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family>\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> finalize-local --family <analysis|plan|code> --artifact <artifact> [--recovery-id <id>]\n\nInspect, initialize, or finalize a workflow artifact without changing task state.\n`;
|
|
3
|
+
const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family>\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> preflight|finalize-local --family <analysis|plan|code> --artifact <artifact> [--recovery-id <id>]\n\nInspect, initialize, preflight, or finalize a workflow artifact without changing task state.\n`;
|
|
4
4
|
function taskArtifact(args = []) {
|
|
5
5
|
if (!ensureInternalHandlerRoute('task-artifact', args))
|
|
6
6
|
return;
|
|
@@ -20,12 +20,12 @@ function parseFailure(error) {
|
|
|
20
20
|
return message.replace(/^TASK_CONTROL_OPERATION_INVALID: /u, '');
|
|
21
21
|
}
|
|
22
22
|
async function taskFinalization(args = []) {
|
|
23
|
-
if (!ensureInternalHandlerRoute('task-finalization', args))
|
|
24
|
-
return;
|
|
25
23
|
if (args[0] === '--help' || args[0] === '-h') {
|
|
26
24
|
process.stdout.write(USAGE);
|
|
27
25
|
return;
|
|
28
26
|
}
|
|
27
|
+
if (!ensureInternalHandlerRoute('task-finalization', args))
|
|
28
|
+
return;
|
|
29
29
|
let operation;
|
|
30
30
|
try {
|
|
31
31
|
operation = parseTaskControlOperation('task-finalization', args);
|
|
@@ -13,12 +13,12 @@ function parseFailure(error) {
|
|
|
13
13
|
return message.replace(/^TASK_CONTROL_OPERATION_INVALID: /u, '');
|
|
14
14
|
}
|
|
15
15
|
async function taskLifecycle(args = []) {
|
|
16
|
-
if (!ensureInternalHandlerRoute('task-lifecycle', args))
|
|
17
|
-
return;
|
|
18
16
|
if (args[0] === '--help' || args[0] === '-h') {
|
|
19
17
|
process.stdout.write(USAGE);
|
|
20
18
|
return;
|
|
21
19
|
}
|
|
20
|
+
if (!ensureInternalHandlerRoute('task-lifecycle', args))
|
|
21
|
+
return;
|
|
22
22
|
let operation;
|
|
23
23
|
try {
|
|
24
24
|
operation = parseTaskControlOperation('task-lifecycle', args);
|
|
@@ -16,10 +16,6 @@ function internalTaskRoutes() {
|
|
|
16
16
|
descriptor('internal', 'sandbox-control', 'execute', 'non-task', 'progress', 'none'),
|
|
17
17
|
descriptor('internal', 'sandbox-control', 'recover', 'conditional', 'recovery', 'delegated'),
|
|
18
18
|
descriptor('internal', 'sandbox-control', 'client', 'conditional', 'progress', 'delegated'),
|
|
19
|
-
descriptor('internal', 'host-control', 'serve', 'non-task', 'progress', 'none'),
|
|
20
|
-
descriptor('internal', 'host-control', 'status', 'non-task', 'diagnostic', 'none'),
|
|
21
|
-
descriptor('internal', 'host-control', 'install', 'non-task', 'progress', 'none'),
|
|
22
|
-
descriptor('internal', 'host-control', 'uninstall', 'non-task', 'progress', 'none'),
|
|
23
19
|
descriptor('internal', 'agent-client', 'next-steps', 'non-task', 'diagnostic', 'none'),
|
|
24
20
|
descriptor('internal', 'agent-client', 'model-selection', 'non-task', 'diagnostic', 'none'),
|
|
25
21
|
descriptor('internal', 'codex-lifecycle', 'preflight', 'non-task', 'diagnostic', 'none'),
|
|
@@ -51,6 +47,7 @@ function internalTaskRoutes() {
|
|
|
51
47
|
descriptor('internal', 'platform-metadata', 'init-milestones', 'non-task', 'remote-write', 'none'),
|
|
52
48
|
descriptor('internal', 'platform-context', 'resolve', 'non-task', 'diagnostic', 'none'),
|
|
53
49
|
descriptor('internal', 'platform-comment', 'list', 'non-task', 'diagnostic', 'none'),
|
|
50
|
+
descriptor('internal', 'platform-comment', 'recover', 'non-task', 'recovery', 'none'),
|
|
54
51
|
descriptor('internal', 'platform-comment', 'owner', 'task-bound', 'diagnostic'),
|
|
55
52
|
descriptor('internal', 'platform-comment', 'backfill', 'task-bound', 'remote-write'),
|
|
56
53
|
descriptor('internal', 'platform-comment', 'sync', 'task-bound', 'remote-write'),
|
|
@@ -96,9 +93,11 @@ function internalTaskRoutes() {
|
|
|
96
93
|
descriptor('internal', 'task-activity', 'pr-review-terminate', 'task-bound', 'progress'),
|
|
97
94
|
descriptor('internal', 'task-artifact', 'inspect', 'task-bound', 'diagnostic'),
|
|
98
95
|
descriptor('internal', 'task-artifact', 'init', 'task-bound', 'artifact-write'),
|
|
96
|
+
descriptor('internal', 'task-artifact', 'preflight', 'task-bound', 'artifact-write'),
|
|
99
97
|
descriptor('internal', 'task-artifact', 'finalize-local', 'task-bound', 'artifact-write'),
|
|
100
98
|
descriptor('internal', 'task-orchestration', 'status', 'task-bound', 'diagnostic'),
|
|
101
99
|
descriptor('internal', 'task-orchestration', 'progress', 'task-bound', 'progress'),
|
|
100
|
+
descriptor('internal', 'task-review', 'preflight', 'task-bound', 'artifact-write'),
|
|
102
101
|
descriptor('internal', 'task-review', 'finalize-summary', 'task-bound', 'progress'),
|
|
103
102
|
descriptor('internal', 'task-event', 'event', 'task-bound', 'progress'),
|
|
104
103
|
descriptor('internal', 'task-invalidation', 'reconcile', 'task-bound', 'progress'),
|
|
@@ -175,7 +174,7 @@ export const TASK_OPERATION_DESCRIPTORS = Object.freeze([
|
|
|
175
174
|
...PUBLIC_OPERATION_DESCRIPTORS
|
|
176
175
|
]);
|
|
177
176
|
export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
|
|
178
|
-
'task-create', 'task-qualification', 'sandbox-control', '
|
|
177
|
+
'task-create', 'task-qualification', 'sandbox-control', 'agent-client', 'codex-lifecycle', 'codex-sandbox-controller',
|
|
179
178
|
'git-workflow', 'task-delivery', 'release-workflow', 'platform-release-notes', 'platform-security', 'platform-metadata', 'platform-context',
|
|
180
179
|
'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
|
|
181
180
|
'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
|
|
@@ -287,28 +286,39 @@ function taskMarkerState(env) {
|
|
|
287
286
|
return 'incomplete';
|
|
288
287
|
}
|
|
289
288
|
export function resolveSandboxControlTransport(env = process.env, options = {}) {
|
|
290
|
-
const configuredStatusDir = env.AGENT_INFRA_CONTROL_STATUS_DIR;
|
|
291
|
-
const fixedStatusDir = options.statusMountPath
|
|
292
|
-
?? SANDBOX_CONTROL_STATUS_MOUNT;
|
|
293
289
|
const hasAnyMarker = TASK_MARKER_KEYS.some((key) => Boolean(env[key]))
|
|
294
290
|
|| Boolean(env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING)
|
|
295
291
|
|| Boolean(env.AGENT_INFRA_EXECUTOR_MANIFEST);
|
|
296
|
-
const
|
|
292
|
+
const fixedStatusDir = options.statusMountPath
|
|
293
|
+
?? SANDBOX_CONTROL_STATUS_MOUNT;
|
|
294
|
+
const fixedStatusProbe = path.isAbsolute(fixedStatusDir)
|
|
297
295
|
? nativeDirectoryProbe(fixedStatusDir) : 'absent';
|
|
298
296
|
if (fixedStatusProbe === 'unknown') {
|
|
299
297
|
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_UNAVAILABLE' };
|
|
300
298
|
}
|
|
301
299
|
const fixedStatusMounted = fixedStatusProbe === 'present';
|
|
300
|
+
// A fixed status mount is installed only in a task-bound sandbox. Its
|
|
301
|
+
// presence is not mutable by the sandbox process, so marker removal must
|
|
302
|
+
// never make that process look like a direct host invocation.
|
|
303
|
+
if (!hasAnyMarker) {
|
|
304
|
+
return fixedStatusMounted
|
|
305
|
+
? { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' }
|
|
306
|
+
: { kind: 'direct-host', reasonCode: null };
|
|
307
|
+
}
|
|
308
|
+
const hasCompleteConfig = TASK_CONTROL_CONFIG_KEYS.every((key) => Boolean(env[key]));
|
|
309
|
+
if (!hasCompleteConfig) {
|
|
310
|
+
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
|
|
311
|
+
}
|
|
312
|
+
const configuredStatusDir = env.AGENT_INFRA_CONTROL_STATUS_DIR;
|
|
302
313
|
const configuredStatusProbe = configuredStatusDir && path.isAbsolute(configuredStatusDir)
|
|
303
314
|
? nativeDirectoryProbe(configuredStatusDir) : 'absent';
|
|
304
315
|
if (configuredStatusProbe === 'unknown') {
|
|
305
316
|
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_UNAVAILABLE' };
|
|
306
317
|
}
|
|
307
|
-
const statusDir = configuredStatusDir && path.isAbsolute(configuredStatusDir)
|
|
308
|
-
? configuredStatusDir
|
|
318
|
+
const statusDir = configuredStatusDir && path.isAbsolute(configuredStatusDir)
|
|
319
|
+
? configuredStatusProbe === 'present' ? configuredStatusDir : null
|
|
309
320
|
: fixedStatusMounted ? fixedStatusDir : null;
|
|
310
321
|
const statusMounted = statusDir !== null;
|
|
311
|
-
const hasCompleteConfig = TASK_CONTROL_CONFIG_KEYS.every((key) => Boolean(env[key]));
|
|
312
322
|
if (env.AGENT_INFRA_EXECUTOR_MANIFEST || env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING) {
|
|
313
323
|
return { kind: 'fail-closed', reasonCode: 'TASK_CONTROL_TRANSPORT_INVALID' };
|
|
314
324
|
}
|
|
@@ -16,12 +16,12 @@ function parseFailure(error) {
|
|
|
16
16
|
return message.replace(/^TASK_CONTROL_OPERATION_INVALID: /u, '');
|
|
17
17
|
}
|
|
18
18
|
async function taskOrchestration(args = []) {
|
|
19
|
-
if (!ensureInternalHandlerRoute('task-orchestration', args))
|
|
20
|
-
return;
|
|
21
19
|
if (args[0] === '--help' || args[0] === '-h') {
|
|
22
20
|
process.stdout.write(USAGE);
|
|
23
21
|
return;
|
|
24
22
|
}
|
|
23
|
+
if (!ensureInternalHandlerRoute('task-orchestration', args))
|
|
24
|
+
return;
|
|
25
25
|
const route = internalHandlerRoute('task-orchestration', 'status', args[1] ?? '')
|
|
26
26
|
? 'status'
|
|
27
27
|
: internalHandlerRoute('task-orchestration', 'progress', args[1] ? 'progress' : '')
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { parseReviewCommand } from '../task/review-command.js';
|
|
2
|
-
import { finalizeReviewSummary } from '../task/review-finalization.js';
|
|
2
|
+
import { finalizeReviewSummary, preflightReviewSummary } from '../task/review-finalization.js';
|
|
3
3
|
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.js';
|
|
4
4
|
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
5
5
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
6
6
|
import { ensureInternalHandlerRoute } from './cli-route-inventory.js';
|
|
7
|
-
const USAGE = 'Usage: agent-infra-internal task-review <task-ref> finalize-summary --stage <analysis|plan|code> --artifact <review-*.md> [--recovery-id <id>] [--orchestrated] [--dry-run] [--override-ticket <ticket> --override-target <target> --override-scope <scope>]\n';
|
|
7
|
+
const USAGE = 'Usage: agent-infra-internal task-review <task-ref> <preflight|finalize-summary> --stage <analysis|plan|code> --artifact <review-*.md> [--recovery-id <id>] [--orchestrated] [--dry-run] [--override-ticket <ticket> --override-target <target> --override-scope <scope>]\n';
|
|
8
8
|
function failUsage(message) {
|
|
9
9
|
process.stdout.write(`${JSON.stringify({
|
|
10
10
|
status: 'failed',
|
|
11
11
|
changed: false,
|
|
12
|
-
intent: '
|
|
12
|
+
intent: 'review-operation',
|
|
13
13
|
error: { code: 'REVIEW_PAYLOAD_INVALID', message }
|
|
14
14
|
})}\n`);
|
|
15
15
|
process.stderr.write(USAGE);
|
|
@@ -31,6 +31,7 @@ async function taskReview(args = []) {
|
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
33
|
const { stage, artifact, recoveryId, dryRun, orchestrated, overrideTicket, overrideTarget, overrideScope } = request;
|
|
34
|
+
const operation = args[1];
|
|
34
35
|
const dryRunConflict = overrideDryRunConflict({ dryRun, overrideTicket, overrideTarget, overrideScope });
|
|
35
36
|
if (dryRunConflict) {
|
|
36
37
|
failUsage(dryRunConflict.message);
|
|
@@ -44,8 +45,12 @@ async function taskReview(args = []) {
|
|
|
44
45
|
let result;
|
|
45
46
|
let humanOverride = null;
|
|
46
47
|
try {
|
|
47
|
-
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId,
|
|
48
|
-
let current =
|
|
48
|
+
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-review.${operation}`, async () => {
|
|
49
|
+
let current = operation === 'preflight'
|
|
50
|
+
? preflightReviewSummary({ taskRef: args[0], stage, artifact, recoveryId, orchestrated, dryRun }, { lockAlreadyHeld: true })
|
|
51
|
+
: finalizeReviewSummary({ taskRef: args[0], stage, artifact, recoveryId, orchestrated, dryRun }, { lockAlreadyHeld: true });
|
|
52
|
+
if (operation === 'preflight')
|
|
53
|
+
return current;
|
|
49
54
|
if (current.status !== 'failed' || !overrideTicket)
|
|
50
55
|
return current;
|
|
51
56
|
if (!overrideTarget || !overrideScope) {
|