@fitlab-ai/agent-infra 0.11.2 → 0.11.4
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 +17 -8
- package/README.zh-CN.md +17 -8
- package/dist/bin/internal-cli.js +7 -2
- package/dist/lib/agent-clients/adapter.js +3 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +234 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +8 -35
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +1 -34
- package/dist/lib/agent-clients/adapters/codex.js +10 -0
- package/dist/lib/internal/cli-route-inventory.js +1 -1
- package/dist/lib/internal/manual-validation.js +22 -27
- package/dist/lib/internal/sandbox-control.js +1 -43
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +41 -1
- package/dist/lib/internal/task-ledger.js +13 -5
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +7 -2
- package/dist/lib/platform/completion-backfill.js +63 -17
- package/dist/lib/platform/issue-comments.js +2 -31
- package/dist/lib/platform/pr-summary.js +7 -2
- package/dist/lib/sandbox/control/client.js +3 -44
- package/dist/lib/sandbox/control/executor.js +14 -79
- package/dist/lib/sandbox/control/protocol.js +5 -64
- package/dist/lib/sandbox/control/server.js +49 -343
- package/dist/lib/sandbox/control/state.js +1 -7
- package/dist/lib/sandbox/control/task-view.js +1 -2
- package/dist/lib/sandbox/task-cleanup.js +13 -20
- package/dist/lib/task/artifact-command.js +11 -4
- package/dist/lib/task/artifact-lifecycle.js +217 -85
- package/dist/lib/task/artifact-operations.js +1 -26
- package/dist/lib/task/artifact-receipts.js +4 -1
- package/dist/lib/task/artifact-schema.js +1 -0
- package/dist/lib/task/artifact-selection.js +25 -0
- package/dist/lib/task/capabilities.js +158 -39
- package/dist/lib/task/commands/status.js +9 -2
- package/dist/lib/task/control-authority.js +37 -38
- package/dist/lib/task/control-recovery.js +3 -42
- package/dist/lib/task/decision-intents.js +12 -0
- package/dist/lib/task/delegation-receipts.js +2 -2
- package/dist/lib/task/delivery.js +1 -43
- package/dist/lib/task/events.js +80 -64
- package/dist/lib/task/finalization-artifacts.js +1 -3
- package/dist/lib/task/finalization-state.js +1 -1
- package/dist/lib/task/finalization.js +319 -234
- package/dist/lib/task/ledger-intents.js +157 -15
- package/dist/lib/task/lifecycle-path.js +75 -0
- package/dist/lib/task/lifecycle.js +11 -1
- package/dist/lib/task/local-artifact-finalization.js +1 -20
- package/dist/lib/task/manual-validation-receipt.js +8 -3
- package/dist/lib/task/orchestration.js +29 -13
- package/dist/lib/task/review-finalization.js +3 -16
- package/dist/lib/task/rework-intent.js +70 -4
- package/dist/lib/task/verification-engine.js +20 -30
- package/dist/lib/task/verification.js +1 -0
- package/dist/lib/task/workflow-warning-intents.js +21 -6
- package/dist/lib/task/workspace-snapshot.js +7 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +46 -0
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +297 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +13 -33
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -44
- package/lib/agent-clients/adapters/codex.ts +10 -0
- package/lib/internal/cli-route-inventory.ts +1 -1
- package/lib/internal/manual-validation.ts +22 -26
- package/lib/internal/sandbox-control.ts +0 -42
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +40 -1
- package/lib/internal/task-ledger.ts +12 -5
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +8 -4
- package/lib/platform/completion-backfill.ts +78 -16
- package/lib/platform/issue-comments.ts +1 -35
- package/lib/platform/pr-summary.ts +10 -2
- package/lib/sandbox/control/client.ts +5 -68
- package/lib/sandbox/control/executor.ts +14 -81
- package/lib/sandbox/control/protocol.ts +7 -83
- package/lib/sandbox/control/server.ts +47 -362
- package/lib/sandbox/control/state.ts +1 -9
- package/lib/sandbox/control/task-view.ts +1 -2
- package/lib/sandbox/task-cleanup.ts +12 -20
- package/lib/task/artifact-command.ts +14 -4
- package/lib/task/artifact-lifecycle.ts +236 -85
- package/lib/task/artifact-operations.ts +1 -26
- package/lib/task/artifact-receipts.ts +4 -1
- package/lib/task/artifact-schema.ts +1 -0
- package/lib/task/artifact-selection.ts +53 -0
- package/lib/task/capabilities.ts +154 -40
- package/lib/task/commands/status.ts +11 -2
- package/lib/task/control-authority.ts +41 -48
- package/lib/task/control-recovery.ts +3 -43
- package/lib/task/decision-intents.ts +9 -0
- package/lib/task/delegation-receipts.ts +2 -2
- package/lib/task/delivery.ts +1 -40
- package/lib/task/events.ts +82 -64
- package/lib/task/finalization-artifacts.ts +1 -3
- package/lib/task/finalization-state.ts +1 -1
- package/lib/task/finalization.ts +310 -216
- package/lib/task/ledger-intents.ts +155 -15
- package/lib/task/lifecycle-path.ts +98 -0
- package/lib/task/lifecycle.ts +15 -2
- package/lib/task/local-artifact-finalization.ts +2 -25
- package/lib/task/manual-validation-receipt.ts +8 -2
- package/lib/task/orchestration.ts +56 -27
- package/lib/task/review-finalization.ts +3 -16
- package/lib/task/rework-intent.ts +75 -5
- package/lib/task/verification-engine.ts +19 -30
- package/lib/task/verification.ts +2 -1
- package/lib/task/workflow-warning-intents.ts +18 -6
- package/lib/task/workspace-snapshot.ts +3 -2
- package/package.json +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.en.md +19 -5
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +19 -5
- package/templates/.agents/rules/review-handshake.en.md +4 -4
- package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
- package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -4
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -4
- package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.en.md +7 -8
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +7 -8
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
- package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -8
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -8
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
- package/templates/.agents/templates/task.en.md +2 -2
- package/templates/.agents/templates/task.zh-CN.md +2 -2
- package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/feature-development.en.yaml +11 -6
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/refactoring.en.yaml +11 -6
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
- package/dist/lib/sandbox/control/finalization-response.js +0 -35
- package/dist/lib/sandbox/control/task-workflow.js +0 -74
- package/dist/lib/sandbox/control/workflow-executor.js +0 -142
- package/dist/lib/task/lifecycle-recovery.js +0 -549
- package/dist/lib/task/recovery-warning.js +0 -19
- package/dist/lib/task/workflow-command.js +0 -21
- package/dist/lib/task/workflow-dispatch.js +0 -213
- package/lib/sandbox/control/finalization-response.ts +0 -37
- package/lib/sandbox/control/task-workflow.ts +0 -95
- package/lib/sandbox/control/workflow-executor.ts +0 -156
- package/lib/task/lifecycle-recovery.ts +0 -691
- package/lib/task/recovery-warning.ts +0 -21
- package/lib/task/workflow-command.ts +0 -22
- package/lib/task/workflow-dispatch.ts +0 -214
|
@@ -3,8 +3,11 @@ import {
|
|
|
3
3
|
dispatchTaskControlOperation,
|
|
4
4
|
parseTaskControlOperation
|
|
5
5
|
} from '../task/control-authority.ts';
|
|
6
|
-
import { applyTaskFinalization } from '../task/finalization.ts';
|
|
6
|
+
import { applyTaskFinalization, prepareTaskFinalization } from '../task/finalization.ts';
|
|
7
7
|
import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
|
|
8
|
+
import { verifyTaskEvent } from '../task/verification.ts';
|
|
9
|
+
import { resolveSandboxControlTransport } from './task-operation-registry.ts';
|
|
10
|
+
import { requestSandboxTaskFinalization, SandboxControlClientError } from '../sandbox/control/client.ts';
|
|
8
11
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
9
12
|
|
|
10
13
|
const USAGE = 'Usage: agent-infra-internal task-finalization <N | TASK-id> complete --agent <agent>\n';
|
|
@@ -73,10 +76,46 @@ async function taskFinalization(args: string[] = []): Promise<void> {
|
|
|
73
76
|
...operation,
|
|
74
77
|
request: { ...operation.request, taskRef: resolved.taskId }
|
|
75
78
|
};
|
|
79
|
+
const transport = resolveSandboxControlTransport(process.env, { localWorkflow: true });
|
|
80
|
+
if (transport.kind === 'sandbox-local') {
|
|
81
|
+
const prepared = await prepareTaskFinalization(boundOperation.request, {
|
|
82
|
+
repoRoot,
|
|
83
|
+
preflight: (request, options) => verifyTaskEvent({ ...request, event: 'complete-task.hard-preflight' }, options)
|
|
84
|
+
});
|
|
85
|
+
if (prepared.status !== 'prepared') {
|
|
86
|
+
process.stdout.write(envelope(prepared.status, prepared.changed, true, prepared, prepared.error));
|
|
87
|
+
process.exitCode = exitCode(prepared.status);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const response = requestSandboxTaskFinalization({ agent: boundOperation.request.agent });
|
|
92
|
+
process.stdout.write(response.stdout);
|
|
93
|
+
process.stderr.write(response.stderr);
|
|
94
|
+
process.exitCode = response.exitCode ?? 1;
|
|
95
|
+
return;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
const detail = error instanceof SandboxControlClientError
|
|
98
|
+
? error.detail
|
|
99
|
+
: { code: 'SANDBOX_CONTROL_CLIENT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
|
|
100
|
+
process.stdout.write(envelope(detail.retryable ? 'blocked' : 'failed', false, false, null, detail));
|
|
101
|
+
process.exitCode = detail.retryable ? 2 : 1;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
76
105
|
const result = await dispatchTaskControlOperation(
|
|
77
106
|
createDirectHostExecutionContext({ repoRoot }),
|
|
78
107
|
boundOperation
|
|
79
108
|
);
|
|
109
|
+
if (result.status === 'prepared') {
|
|
110
|
+
const detail = {
|
|
111
|
+
code: 'TASK_FINALIZATION_DIRECT_COMMIT_REQUIRED',
|
|
112
|
+
message: 'direct finalization prepared external work without committing the lifecycle transition',
|
|
113
|
+
retryable: false
|
|
114
|
+
};
|
|
115
|
+
process.stdout.write(envelope('failed', result.changed, true, result, detail));
|
|
116
|
+
process.exitCode = 1;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
80
119
|
process.stdout.write(envelope(result.status, result.changed, true, result, result.error));
|
|
81
120
|
process.exitCode = exitCode(result.status);
|
|
82
121
|
}
|
|
@@ -8,17 +8,18 @@ import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
|
8
8
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
9
9
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
10
10
|
|
|
11
|
-
const USAGE = `Usage: agent-infra-internal task-ledger <task-ref> <intent> [intent flags] [--dry-run]\n\nIntents: finding-upsert, finding-respond, finding-review, decision-next-id, decision-upsert, rework-intent-upsert, stage-status\n`;
|
|
11
|
+
const USAGE = `Usage: agent-infra-internal task-ledger <task-ref> <intent> [intent flags] [--dry-run]\n\nIntents: finding-upsert, finding-respond, finding-review, decision-next-id, decision-upsert, rework-intent-upsert, rework-intent-rebuild, stage-status\n`;
|
|
12
12
|
const FLAGS: Record<string, string> = {
|
|
13
13
|
'--stage': 'stage', '--review-artifact': 'reviewArtifact', '--ordinal': 'ordinal',
|
|
14
14
|
'--severity': 'severity', '--evidence': 'evidence', '--id': 'id', '--round': 'round',
|
|
15
15
|
'--status': 'status', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation',
|
|
16
16
|
'--intent-id': 'intentId', '--finding-id': 'findingId', '--source-artifact': 'sourceArtifact',
|
|
17
|
-
'--source-sha256': 'sourceSha256', '--
|
|
17
|
+
'--source-sha256': 'sourceSha256', '--classification': 'classification',
|
|
18
18
|
'--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
19
19
|
};
|
|
20
20
|
const NUMERIC = new Set(['ordinal', 'round']);
|
|
21
21
|
const BOOLEAN = new Set(['needsImplementation']);
|
|
22
|
+
const REWORK_CLASSIFICATIONS = new Set(['scope-or-requirement', 'design', 'implementation', 'human-decision', 'insufficient-evidence']);
|
|
22
23
|
|
|
23
24
|
function usageFailure(message: string): void {
|
|
24
25
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LEDGER_PAYLOAD_INVALID', message } })}\n`);
|
|
@@ -38,6 +39,7 @@ async function taskLedger(args: string[] = []): Promise<void> {
|
|
|
38
39
|
internalHandlerRoute('task-ledger', 'decision-next-id', kind),
|
|
39
40
|
internalHandlerRoute('task-ledger', 'decision-upsert', kind),
|
|
40
41
|
internalHandlerRoute('task-ledger', 'rework-intent-upsert', kind),
|
|
42
|
+
internalHandlerRoute('task-ledger', 'rework-intent-rebuild', kind),
|
|
41
43
|
internalHandlerRoute('task-ledger', 'stage-status', kind)
|
|
42
44
|
].some(Boolean)) {
|
|
43
45
|
usageFailure(`unknown ledger intent '${kind}'`); return;
|
|
@@ -67,14 +69,16 @@ async function taskLedger(args: string[] = []): Promise<void> {
|
|
|
67
69
|
'finding-review': ['id', 'status', 'evidence'],
|
|
68
70
|
'decision-next-id': [],
|
|
69
71
|
'decision-upsert': ['id', 'stage', 'artifact'],
|
|
70
|
-
'rework-intent-upsert': ['intentId', 'findingId', 'sourceArtifact', 'sourceSha256', '
|
|
72
|
+
'rework-intent-upsert': ['intentId', 'findingId', 'sourceArtifact', 'sourceSha256', 'classification'],
|
|
73
|
+
'rework-intent-rebuild': [],
|
|
71
74
|
'stage-status': ['stage']
|
|
72
75
|
};
|
|
73
76
|
const optional: Record<string, string[]> = {
|
|
74
77
|
'finding-review': ['needsImplementation'],
|
|
75
|
-
'decision-upsert': ['needsImplementation']
|
|
78
|
+
'decision-upsert': ['needsImplementation'],
|
|
79
|
+
'rework-intent-rebuild': ['findingId', 'sourceArtifact', 'sourceSha256', 'classification']
|
|
76
80
|
};
|
|
77
|
-
const overrideFields = kind === 'stage-status' || kind === 'decision-next-id' || kind === 'rework-intent-upsert'
|
|
81
|
+
const overrideFields = kind === 'stage-status' || kind === 'decision-next-id' || kind === 'rework-intent-upsert' || kind === 'rework-intent-rebuild'
|
|
78
82
|
? []
|
|
79
83
|
: ['overrideTicket', 'overrideTarget', 'overrideScope'];
|
|
80
84
|
const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind]!, ...(optional[kind] ?? []), ...overrideFields]);
|
|
@@ -84,6 +88,9 @@ async function taskLedger(args: string[] = []): Promise<void> {
|
|
|
84
88
|
if (missing) { usageFailure(`${kind} requires '${missing}'`); return; }
|
|
85
89
|
const dryRunConflict = overrideDryRunConflict(values);
|
|
86
90
|
if (dryRunConflict) { usageFailure(dryRunConflict.message); return; }
|
|
91
|
+
if (values.classification !== undefined && !REWORK_CLASSIFICATIONS.has(String(values.classification))) {
|
|
92
|
+
usageFailure(`invalid rework classification '${values.classification}'`); return;
|
|
93
|
+
}
|
|
87
94
|
if (kind === 'stage-status') {
|
|
88
95
|
if (!isReviewStage(String(values.stage))) { usageFailure(`stage-status requires a valid review stage`); return; }
|
|
89
96
|
const resolved = resolveTaskRef(taskRef);
|
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
import { lifecycleIntentCatalog } from '../task/lifecycle.ts';
|
|
8
8
|
import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
|
|
9
9
|
import type { TaskLifecycleResult } from '../task/lifecycle.ts';
|
|
10
|
-
import type { LifecycleRecoveryResult } from '../
|
|
10
|
+
import type { AgentClientLifecycleRecoveryResult as LifecycleRecoveryResult } from '../agent-clients/adapter.ts';
|
|
11
11
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
12
12
|
|
|
13
|
-
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nrecover-started: --
|
|
13
|
+
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nrecover-started: --auto\nOverride: --override-ticket <ticket> --override-target <target> --override-scope <scope>\n`;
|
|
14
14
|
|
|
15
15
|
function usageFailure(message: string): void {
|
|
16
16
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LIFECYCLE_PAYLOAD_INVALID', message } })}\n`);
|
|
@@ -22,7 +22,7 @@ export type TaskOperationEffect = TaskViewAccessEffect;
|
|
|
22
22
|
export type TaskRefSource = 'argv' | 'environment' | 'none' | 'delegated' | 'input';
|
|
23
23
|
|
|
24
24
|
export type SandboxControlTransportDecision = Readonly<{
|
|
25
|
-
kind: 'direct-host' | 'broker-client' | 'fail-closed';
|
|
25
|
+
kind: 'direct-host' | 'sandbox-local' | 'broker-client' | 'fail-closed';
|
|
26
26
|
reasonCode: string | null;
|
|
27
27
|
}>;
|
|
28
28
|
|
|
@@ -125,6 +125,7 @@ function internalTaskRoutes(): TaskOperationDescriptor[] {
|
|
|
125
125
|
descriptor('internal', 'task-ledger', 'finding-review', 'task-bound', 'progress'),
|
|
126
126
|
descriptor('internal', 'task-ledger', 'decision-upsert', 'task-bound', 'progress'),
|
|
127
127
|
descriptor('internal', 'task-ledger', 'rework-intent-upsert', 'task-bound', 'progress'),
|
|
128
|
+
descriptor('internal', 'task-ledger', 'rework-intent-rebuild', 'task-bound', 'progress'),
|
|
128
129
|
descriptor('internal', 'task-warning', 'list', 'task-bound', 'diagnostic'),
|
|
129
130
|
descriptor('internal', 'task-warning', 'add', 'task-bound', 'progress'),
|
|
130
131
|
descriptor('internal', 'task-warning', 'set-status', 'task-bound', 'progress'),
|
|
@@ -277,6 +278,7 @@ export function resolveTaskOperation(
|
|
|
277
278
|
export function resolveDelegatedTaskOperation(args: readonly string[]): TaskOperationDescriptor | null {
|
|
278
279
|
const [operation, ...rest] = args;
|
|
279
280
|
if (operation !== 'client' || !rest[0]) return null;
|
|
281
|
+
if (rest[0] === 'task-orchestration') return null;
|
|
280
282
|
return resolveTaskOperation('internal', rest[0], rest.slice(1));
|
|
281
283
|
}
|
|
282
284
|
|
|
@@ -340,7 +342,7 @@ function taskMarkerState(env: NodeJS.ProcessEnv): TaskMarkerState {
|
|
|
340
342
|
|
|
341
343
|
export function resolveSandboxControlTransport(
|
|
342
344
|
env: NodeJS.ProcessEnv = process.env,
|
|
343
|
-
options: Readonly<{ statusMountPath?: string }> = {}
|
|
345
|
+
options: Readonly<{ statusMountPath?: string; localWorkflow?: boolean }> = {}
|
|
344
346
|
): SandboxControlTransportDecision {
|
|
345
347
|
const hasAnyMarker = TASK_MARKER_KEYS.some((key) => Boolean(env[key]))
|
|
346
348
|
|| Boolean(env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING)
|
|
@@ -407,13 +409,15 @@ export function resolveSandboxControlTransport(
|
|
|
407
409
|
if (!hasCompleteConfig) {
|
|
408
410
|
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
|
|
409
411
|
}
|
|
410
|
-
return { kind: 'broker-client', reasonCode: null };
|
|
412
|
+
return { kind: options.localWorkflow ? 'sandbox-local' : 'broker-client', reasonCode: null };
|
|
411
413
|
}
|
|
412
414
|
if (!hasAnyMarker) return { kind: 'direct-host', reasonCode: null };
|
|
413
415
|
if (!hasCompleteConfig || (taskBound && !runtime) || (!taskBound && runtime)) {
|
|
414
416
|
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
|
|
415
417
|
}
|
|
416
|
-
return
|
|
418
|
+
return options.localWorkflow
|
|
419
|
+
? { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_MISSING' }
|
|
420
|
+
: { kind: 'broker-client', reasonCode: null };
|
|
417
421
|
}
|
|
418
422
|
|
|
419
423
|
export function hasTaskBoundMarker(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
@@ -19,6 +19,24 @@ type CompletionBackfillResult = PlatformResult & {
|
|
|
19
19
|
artifacts: Array<{ artifact: string; status: PlatformResult['status'] }>;
|
|
20
20
|
warnings: Array<{ id: string; status: string }>;
|
|
21
21
|
};
|
|
22
|
+
type CompletionBackfillEligibility = Readonly<{
|
|
23
|
+
status: 'ready' | 'failed';
|
|
24
|
+
eligible: boolean;
|
|
25
|
+
error: { code: string; message: string; retryable: boolean } | null;
|
|
26
|
+
}>;
|
|
27
|
+
type BackfillArtifactReference = Readonly<
|
|
28
|
+
| { status: 'ignored'; artifact: null; error: null }
|
|
29
|
+
| { status: 'ready'; artifact: string; error: null }
|
|
30
|
+
| { status: 'failed'; artifact: null; error: { code: string; message: string; retryable: false } }
|
|
31
|
+
>;
|
|
32
|
+
type BackfillWarningMatch = Readonly<{
|
|
33
|
+
warning: ReturnType<typeof getOpenWorkflowWarnings>[number];
|
|
34
|
+
artifact: string;
|
|
35
|
+
}>;
|
|
36
|
+
type BackfillWarningMatches = Readonly<
|
|
37
|
+
| { status: 'ready'; matches: BackfillWarningMatch[]; error: null }
|
|
38
|
+
| { status: 'failed'; matches: []; error: { code: string; message: string; retryable: false } }
|
|
39
|
+
>;
|
|
22
40
|
|
|
23
41
|
function result(
|
|
24
42
|
base: PlatformResult,
|
|
@@ -28,18 +46,65 @@ function result(
|
|
|
28
46
|
return { ...base, artifacts, warnings };
|
|
29
47
|
}
|
|
30
48
|
|
|
31
|
-
function
|
|
32
|
-
const names = [...message.matchAll(/(?:^|[^A-Za-z0-9_-])(
|
|
49
|
+
function referencedBackfillArtifact(taskDir: string, message: string): BackfillArtifactReference {
|
|
50
|
+
const names = [...message.matchAll(/(?:^|[^A-Za-z0-9_-])([a-z][a-z-]*(?:-r[1-9]\d*)?\.md)(?=$|[^A-Za-z0-9_.-])/g)]
|
|
33
51
|
.map((match) => match[1]!);
|
|
34
|
-
if (names.length !== 1) return null;
|
|
52
|
+
if (names.length !== 1) return { status: 'ignored', artifact: null, error: null };
|
|
35
53
|
const parsed = parseArtifactName(names[0]!);
|
|
36
|
-
if (!parsed || parsed.family !== 'pr-review')
|
|
54
|
+
if (!parsed || (parsed.family !== 'pr-review' && !COMPLETION_BACKFILL_FAMILIES.includes(parsed.family as typeof COMPLETION_BACKFILL_FAMILIES[number]))) {
|
|
55
|
+
return { status: 'ignored', artifact: null, error: null };
|
|
56
|
+
}
|
|
37
57
|
const artifactPath = path.join(taskDir, parsed.name);
|
|
38
58
|
try {
|
|
39
59
|
const stat = fs.lstatSync(artifactPath);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
60
|
+
if (stat.isSymbolicLink()) return {
|
|
61
|
+
status: 'failed', artifact: null,
|
|
62
|
+
error: { code: 'ARTIFACT_TOPOLOGY_CONFLICT', message: `SYMBOLIC_LINK: symbolic links are not workflow artifacts: ${parsed.name}`, retryable: false }
|
|
63
|
+
};
|
|
64
|
+
if (!stat.isFile()) return {
|
|
65
|
+
status: 'failed', artifact: null,
|
|
66
|
+
error: { code: 'ARTIFACT_TOPOLOGY_CONFLICT', message: `NON_REGULAR_FILE: artifact is not a regular file: ${parsed.name}`, retryable: false }
|
|
67
|
+
};
|
|
68
|
+
fs.accessSync(artifactPath, fs.constants.R_OK);
|
|
69
|
+
return { status: 'ready', artifact: parsed.name, error: null };
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return {
|
|
72
|
+
status: 'failed', artifact: null,
|
|
73
|
+
error: { code: 'ARTIFACT_READ_FAILED', message: `Unable to inspect referenced artifact '${parsed.name}': ${error instanceof Error ? error.message : String(error)}`, retryable: false }
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function matchingBackfillWarnings(taskDir: string, content: string): BackfillWarningMatches {
|
|
79
|
+
const matches: BackfillWarningMatch[] = [];
|
|
80
|
+
for (const warning of getOpenWorkflowWarnings(content)) {
|
|
81
|
+
if (warning.step !== 'complete-task' || warning.code !== 'COMMENT_SYNC_FAILED' || warning.target !== 'artifact') continue;
|
|
82
|
+
const reference = referencedBackfillArtifact(taskDir, warning.message);
|
|
83
|
+
if (reference.status === 'failed') return { status: 'failed', matches: [], error: reference.error };
|
|
84
|
+
if (reference.status === 'ready') matches.push({ warning, artifact: reference.artifact });
|
|
85
|
+
}
|
|
86
|
+
return { status: 'ready', matches, error: null };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function inspectCompletionBackfillEligibility(
|
|
90
|
+
taskRef: string,
|
|
91
|
+
options: Pick<CompletionBackfillOptions, 'cwd'> = {}
|
|
92
|
+
): CompletionBackfillEligibility {
|
|
93
|
+
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
94
|
+
if (!resolved.ok) return {
|
|
95
|
+
status: 'failed', eligible: false,
|
|
96
|
+
error: { code: resolved.code, message: resolved.message, retryable: false }
|
|
97
|
+
};
|
|
98
|
+
try {
|
|
99
|
+
const content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
100
|
+
const matching = matchingBackfillWarnings(resolved.taskDir, content);
|
|
101
|
+
if (matching.status === 'failed') return { status: 'failed', eligible: false, error: matching.error };
|
|
102
|
+
return { status: 'ready', eligible: matching.matches.length > 0, error: null };
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return {
|
|
105
|
+
status: 'failed', eligible: false,
|
|
106
|
+
error: { code: 'TASK_READ_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }
|
|
107
|
+
};
|
|
43
108
|
}
|
|
44
109
|
}
|
|
45
110
|
|
|
@@ -60,6 +125,8 @@ async function backfillCompletionComments(
|
|
|
60
125
|
}
|
|
61
126
|
}), [], []);
|
|
62
127
|
const initialContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
128
|
+
const matching = matchingBackfillWarnings(resolved.taskDir, initialContent);
|
|
129
|
+
if (matching.status === 'failed') return result(platformResult('failed', { error: matching.error }), [], []);
|
|
63
130
|
const issueIdentity = taskIssueIdentity(parseTaskFrontmatter(initialContent));
|
|
64
131
|
if (!issueIdentity) return result(platformResult('no-op', {
|
|
65
132
|
error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid platform issue identity', retryable: false }
|
|
@@ -92,17 +159,12 @@ async function backfillCompletionComments(
|
|
|
92
159
|
}), artifacts, []);
|
|
93
160
|
}
|
|
94
161
|
|
|
95
|
-
const matching = getOpenWorkflowWarnings(initialContent).flatMap((warning) => {
|
|
96
|
-
if (warning.step !== 'complete-task' || warning.code !== 'COMMENT_SYNC_FAILED' || warning.target !== 'artifact') return [];
|
|
97
|
-
const excluded = excludedPrReview(resolved.taskDir, warning.message);
|
|
98
|
-
return excluded ? [{ warning, excluded }] : [];
|
|
99
|
-
});
|
|
100
162
|
const warnings: CompletionBackfillResult['warnings'] = [];
|
|
101
163
|
let metadata: ReturnType<typeof captureTaskWriteMetadata> | null = null;
|
|
102
|
-
for (const { warning,
|
|
164
|
+
for (const { warning, artifact } of matching.matches) {
|
|
103
165
|
metadata ??= captureTaskWriteMetadata();
|
|
104
166
|
const resolution = [
|
|
105
|
-
`
|
|
167
|
+
`artifact=${artifact}`,
|
|
106
168
|
`completionFamilies=${COMPLETION_BACKFILL_FAMILIES.join(',')}`,
|
|
107
169
|
`synchronized=${inventory.artifacts.map((artifact) => artifact.name).join(',') || 'none'}`,
|
|
108
170
|
`completedAt=${metadata.timestamp}`
|
|
@@ -131,5 +193,5 @@ async function backfillCompletionComments(
|
|
|
131
193
|
}), artifacts, warnings);
|
|
132
194
|
}
|
|
133
195
|
|
|
134
|
-
export { backfillCompletionComments };
|
|
135
|
-
export type { CompletionBackfillOptions, CompletionBackfillResult };
|
|
196
|
+
export { backfillCompletionComments, inspectCompletionBackfillEligibility };
|
|
197
|
+
export type { CompletionBackfillEligibility, CompletionBackfillOptions, CompletionBackfillResult };
|
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
import { projectTaskComment } from './task-comment-projection.ts';
|
|
28
28
|
import {
|
|
29
29
|
canonicalizeCommentBody,
|
|
30
|
-
canonicalizeSummaryBody,
|
|
31
30
|
escapeHtmlText,
|
|
32
31
|
fenceRanges,
|
|
33
32
|
renderSafeCodeFence
|
|
@@ -52,7 +51,6 @@ type SyncOptions = {
|
|
|
52
51
|
client?: PlatformClient;
|
|
53
52
|
runtimeVersion?: string;
|
|
54
53
|
summaryAuthorization?: { sha256: string };
|
|
55
|
-
verifyOnly?: boolean;
|
|
56
54
|
};
|
|
57
55
|
|
|
58
56
|
function providerCommentId(id: string, provider: { identity?: { comment?: string } }): number | string {
|
|
@@ -76,8 +74,6 @@ const ARTIFACT_TITLES: Record<string, string> = {
|
|
|
76
74
|
'review-plan': '技术方案审查',
|
|
77
75
|
code: '实现报告',
|
|
78
76
|
'review-code': '代码审查',
|
|
79
|
-
'manual-validation': '人工验证报告',
|
|
80
|
-
'validation-run': '验证运行证据',
|
|
81
77
|
'pr-review': 'PR 审查报告'
|
|
82
78
|
};
|
|
83
79
|
|
|
@@ -154,7 +150,7 @@ function taskCommentLanguage(repoRoot: string): string {
|
|
|
154
150
|
|
|
155
151
|
function artifactIdentity(artifact: string): { stem: string; title: string } {
|
|
156
152
|
const stem = path.basename(artifact, '.md');
|
|
157
|
-
const match = stem.match(/^(analysis|review-analysis|plan|review-plan|code|review-code|
|
|
153
|
+
const match = stem.match(/^(analysis|review-analysis|plan|review-plan|code|review-code|pr-review)(?:-r(\d+))?$/);
|
|
158
154
|
if (!match) throw new Error(`unsupported artifact '${artifact}'`);
|
|
159
155
|
const base = ARTIFACT_TITLES[match[1]!]!;
|
|
160
156
|
const round = match[2] ? Number(match[2]) : 1;
|
|
@@ -483,15 +479,6 @@ function summaryPosition(comments: RemoteComment[], taskId: string): { ok: boole
|
|
|
483
479
|
return { ok: managed.every((comment) => sequence >= comment.createdSequence!), known: true, summary: summary[0]! };
|
|
484
480
|
}
|
|
485
481
|
|
|
486
|
-
function summaryDigest(comment: RemoteComment): string {
|
|
487
|
-
const body = normalizeCommentContent(comment.body)
|
|
488
|
-
.replace(/^<!-- sync-issue:[^\n]+:summary -->\n## [^\n]+\n\n> [^\n]+\n\n/u, '')
|
|
489
|
-
.replace(/^<details><summary>恢复元数据<\/summary>[\s\S]*?<\/details>\n\n/u, '')
|
|
490
|
-
.replace(/\n---\n\*[^\n]*\*$/u, '');
|
|
491
|
-
const canonical = canonicalizeSummaryBody(body);
|
|
492
|
-
return canonical.ok ? createHash('sha256').update(canonical.value).digest('hex') : '';
|
|
493
|
-
}
|
|
494
|
-
|
|
495
482
|
async function listedComments(provider: any, loaded: any, parent: ReturnType<typeof taskIssueIdentity>): Promise<any> {
|
|
496
483
|
return provider.comments?.list
|
|
497
484
|
? provider.comments.list({ context: providerOperationContext(loaded), parent }).then((response: any) => response.ok
|
|
@@ -609,27 +596,6 @@ async function syncPlatformComment(taskRef: string, options: SyncOptions): Promi
|
|
|
609
596
|
}
|
|
610
597
|
}
|
|
611
598
|
|
|
612
|
-
if (options.verifyOnly) {
|
|
613
|
-
if (options.kind !== 'summary' || !options.summaryAuthorization) {
|
|
614
|
-
return platformResult('failed', {
|
|
615
|
-
...contextFields(context), resource: { kind: 'issue', number: issue },
|
|
616
|
-
error: { code: 'SUMMARY_VERIFICATION_INVALID', message: 'summary verification requires durable authorization', retryable: false }
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
const position = summaryPosition(listed.value, resolved.taskId);
|
|
620
|
-
if (!position.ok || !position.summary || summaryDigest(position.summary) !== options.summaryAuthorization.sha256) {
|
|
621
|
-
return platformResult('failed', {
|
|
622
|
-
...contextFields(context), resource: { kind: 'issue', number: issue },
|
|
623
|
-
error: { code: 'SUMMARY_VERIFICATION_FAILED', message: 'summary marker, body digest, or managed comment order is invalid', retryable: true }
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
return platformResult('no-op', {
|
|
627
|
-
...contextFields(context), changed: false, resource: { kind: 'issue', number: issue },
|
|
628
|
-
comment: { kind: options.kind, marker: desired[0]!.marker, ids: [position.summary.id], parts: 1 }, error: null
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
|
|
633
599
|
// Backfill only supplies missing artifact comments; valid existing marker sets stay untouched.
|
|
634
600
|
if (options.kind === 'artifact' && options.backfill && existing.length > 0) {
|
|
635
601
|
const operations = existing.map((comment): PlatformOperation => ({
|
|
@@ -35,7 +35,7 @@ import type {
|
|
|
35
35
|
MechanicalChangeReport,
|
|
36
36
|
PrChangeReport
|
|
37
37
|
} from './pr-change-report.ts';
|
|
38
|
-
import { manualValidationFinalSummaryProjectionMatches } from '../task/manual-validation-receipt.ts';
|
|
38
|
+
import { MANUAL_VALIDATION_RECEIPT_PLACEHOLDER, manualValidationFinalSummaryProjectionMatches } from '../task/manual-validation-receipt.ts';
|
|
39
39
|
import { readManualValidationCompletion } from '../task/manual-validation-completion.ts';
|
|
40
40
|
|
|
41
41
|
type SummaryComment = { id: number | string; body: string };
|
|
@@ -582,6 +582,7 @@ async function syncPullRequestSummary(
|
|
|
582
582
|
const initial = await inspectBoundPullRequest(context, resolved.repoRoot, prIdentity, loaded.value);
|
|
583
583
|
if (!initial.ok) return fail(initial.status, context, initial.error);
|
|
584
584
|
const manual = options.manualValidation;
|
|
585
|
+
let manualEvidenceDigest: string | null = null;
|
|
585
586
|
const hasFinalManualValidation = /###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)/u.test(options.body);
|
|
586
587
|
if (hasFinalManualValidation && (!manual || manual.phase !== 'final')) return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires the transaction coordinator', retryable: false }, prNumber);
|
|
587
588
|
if (manual && (manual.phase === 'pending' ? hasFinalManualValidation : !hasFinalManualValidation)) return fail('failed', context, { code: 'MANUAL_VALIDATION_SUMMARY_PHASE_INVALID', message: 'manual-validation summary phase does not match the requested writer phase', retryable: false }, prNumber);
|
|
@@ -599,6 +600,7 @@ async function syncPullRequestSummary(
|
|
|
599
600
|
});
|
|
600
601
|
if (!completion.ok) return fail('failed', context, platformError(completion.error), prNumber);
|
|
601
602
|
const { receipt, transaction } = completion.value;
|
|
603
|
+
manualEvidenceDigest = receipt.evidenceDigest;
|
|
602
604
|
if (transaction.phase !== 'final-promotion-in-progress' || !transaction.eventAppended) return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: 'final summary requires a receipt-backed promotion intent', retryable: false }, prNumber);
|
|
603
605
|
if (!manualValidationFinalSummaryProjectionMatches(options.body, receipt)) return fail('failed', context, { code: 'MANUAL_VALIDATION_SUMMARY_PROJECTION_INVALID', message: 'final summary body does not match the canonical receipt projection', retryable: false }, prNumber);
|
|
604
606
|
}
|
|
@@ -635,7 +637,13 @@ async function syncPullRequestSummary(
|
|
|
635
637
|
}
|
|
636
638
|
const replaced = replaceCanonicalReportPlaceholder(`${prefix.value}${placeholder}${suffix.value}`, report.value);
|
|
637
639
|
if (!replaced.ok) return fail('failed', context, platformError(replaced.error));
|
|
638
|
-
const
|
|
640
|
+
const renderedBody = manual?.phase === 'final'
|
|
641
|
+
? replaced.value.replace(
|
|
642
|
+
MANUAL_VALIDATION_RECEIPT_PLACEHOLDER,
|
|
643
|
+
`<!-- manual-validation-receipt: transaction=${manual.transactionId}; receipt=${manual.receiptDigest}; evidence=${manualEvidenceDigest}; head=${manual.prHeadSha} -->`
|
|
644
|
+
)
|
|
645
|
+
: replaced.value;
|
|
646
|
+
const desired = buildPullRequestSummary(resolved.taskId, renderedBody, initial.value.head.sha, renderHumanOverrideAudit(taskContent));
|
|
639
647
|
if (!isSafeSummaryEnvelope(desired, resolved.taskId)) return fail('failed', context, {
|
|
640
648
|
code: 'PR_SUMMARY_RENDER_INVALID',
|
|
641
649
|
message: 'Summary contains an invalid or duplicated reserved control marker',
|
|
@@ -11,10 +11,8 @@ import {
|
|
|
11
11
|
type SandboxControlRequest,
|
|
12
12
|
type SandboxControlResponse,
|
|
13
13
|
type SandboxTaskCreateRequest,
|
|
14
|
-
type SandboxTaskCommandRequest,
|
|
15
14
|
type SandboxTaskFinalizationRequest,
|
|
16
|
-
type SandboxCodexControllerRequest
|
|
17
|
-
type SandboxTaskWorkflowRequest
|
|
15
|
+
type SandboxCodexControllerRequest
|
|
18
16
|
} from './protocol.ts';
|
|
19
17
|
import type {
|
|
20
18
|
CodexControllerLeaseProofV1,
|
|
@@ -26,8 +24,6 @@ import { readSandboxControlPayload, readSandboxControlStatus } from './state.ts'
|
|
|
26
24
|
import type { TaskCreateCandidateV1 } from '../../task/create.ts';
|
|
27
25
|
import { accessSandboxTaskView, taskViewFromStatus, type TaskViewAccessEffect } from './task-view.ts';
|
|
28
26
|
import { readSandboxControlIdentitySentinel } from './identity-sentinel.ts';
|
|
29
|
-
import type { TaskWorkflowRequest } from './task-workflow.ts';
|
|
30
|
-
import type { LifecycleAuthorityRequestV1 } from '../../task/control-authority.ts';
|
|
31
27
|
import { configuredShortIdLength, resolveShortIdReadOnly } from '../../task/short-id.ts';
|
|
32
28
|
|
|
33
29
|
const SANDBOX_CONTROL_RESPONSE_SETTLE_MS = 250;
|
|
@@ -119,8 +115,6 @@ function preflight(
|
|
|
119
115
|
|
|
120
116
|
function taskViewEffectForRequest(request: SandboxControlRequest): TaskViewAccessEffect | null {
|
|
121
117
|
if (request.family === 'task-lifecycle' || request.family === 'task-finalization') return 'progress';
|
|
122
|
-
if (request.family === 'task-orchestration') return request.args[1] === 'status' ? 'diagnostic' : 'progress';
|
|
123
|
-
if (request.family === 'task-workflow') return request.workflow.operation === 'artifact-inspect' || request.workflow.operation === 'decision-next-id' ? 'diagnostic' : 'progress';
|
|
124
118
|
return null;
|
|
125
119
|
}
|
|
126
120
|
|
|
@@ -194,9 +188,7 @@ export function resolveVisibleActiveShortId(ref: string): string | null {
|
|
|
194
188
|
// may be a documented short id. Compare canonical ids so a bound sandbox keeps
|
|
195
189
|
// refusing other tasks without rejecting its own task's short id.
|
|
196
190
|
function canonicalRequestTask(request: SandboxControlRequest): CanonicalRequestTask {
|
|
197
|
-
const ref = request.
|
|
198
|
-
? request.workflow.taskId
|
|
199
|
-
: 'args' in request ? request.args[0] ?? null : null;
|
|
191
|
+
const ref = 'args' in request ? request.args[0] ?? null : null;
|
|
200
192
|
if (!ref) return { kind: 'none' };
|
|
201
193
|
if (CONTROL_TASK_ID_RE.test(ref)) return { kind: 'task', taskId: ref };
|
|
202
194
|
if (!CONTROL_SHORT_ID_RE.test(ref)) return { kind: 'none' };
|
|
@@ -223,7 +215,7 @@ function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly
|
|
|
223
215
|
|| (requestTask.kind === 'task' && requestTask.taskId !== identity.taskId))) {
|
|
224
216
|
clientError('SANDBOX_CONTROL_IDENTITY_TOPOLOGY_MISMATCH', 'request task does not match the sandbox identity', false);
|
|
225
217
|
}
|
|
226
|
-
if (identity.mode === 'branch-only' &&
|
|
218
|
+
if (identity.mode === 'branch-only' && request.family === 'task-finalization') {
|
|
227
219
|
clientError('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot finalize tasks', false);
|
|
228
220
|
}
|
|
229
221
|
}
|
|
@@ -384,7 +376,7 @@ export function requestSandboxControl(params: Readonly<{
|
|
|
384
376
|
token?: string; generation?: string; timeoutMs?: number;
|
|
385
377
|
}>): SandboxControlResponse {
|
|
386
378
|
if (!isSandboxControlFamily(params.family)) clientError('SANDBOX_CONTROL_COMMAND_DENIED', `'${params.family}' is not allowed`, false);
|
|
387
|
-
if (params.family === 'task-create' || params.family === 'codex-controller' || params.family === 'task-finalization'
|
|
379
|
+
if (params.family === 'task-create' || params.family === 'codex-controller' || params.family === 'task-finalization') {
|
|
388
380
|
clientError('SANDBOX_CONTROL_COMMAND_DENIED', `'${params.family}' requires a typed request`, false);
|
|
389
381
|
}
|
|
390
382
|
const auth = authority(params);
|
|
@@ -392,41 +384,13 @@ export function requestSandboxControl(params: Readonly<{
|
|
|
392
384
|
const request: SandboxControlRequest = {
|
|
393
385
|
version: 3, id: randomUUID(), ...auth, issuedAt,
|
|
394
386
|
expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
|
|
395
|
-
family: params.family as 'task-lifecycle'
|
|
387
|
+
family: params.family as 'task-lifecycle', args: params.args,
|
|
396
388
|
controllerProcess: null,
|
|
397
389
|
controllerProof: null
|
|
398
390
|
};
|
|
399
391
|
return exchangeSandboxControl(request, params);
|
|
400
392
|
}
|
|
401
393
|
|
|
402
|
-
export function requestSandboxTaskControl(params: Readonly<{
|
|
403
|
-
family: 'task-lifecycle' | 'task-orchestration';
|
|
404
|
-
args: string[];
|
|
405
|
-
controllerProof: CodexControllerLeaseProofV1 | null;
|
|
406
|
-
authority?: LifecycleAuthorityRequestV1;
|
|
407
|
-
channelDir?: string;
|
|
408
|
-
statusDir?: string;
|
|
409
|
-
token?: string;
|
|
410
|
-
generation?: string;
|
|
411
|
-
timeoutMs?: number;
|
|
412
|
-
}>): SandboxControlResponse {
|
|
413
|
-
const auth = authority(params);
|
|
414
|
-
const issuedAt = Date.now();
|
|
415
|
-
const request: SandboxTaskCommandRequest = {
|
|
416
|
-
version: 3,
|
|
417
|
-
id: params.authority?.requestId ?? randomUUID(),
|
|
418
|
-
...auth,
|
|
419
|
-
issuedAt,
|
|
420
|
-
expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
|
|
421
|
-
family: params.family,
|
|
422
|
-
args: params.args,
|
|
423
|
-
controllerProcess: null,
|
|
424
|
-
controllerProof: params.authority ? null : params.controllerProof,
|
|
425
|
-
...(params.authority ? { authority: params.authority } : {})
|
|
426
|
-
};
|
|
427
|
-
return exchangeSandboxControl(request, params);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
394
|
export function requestSandboxTaskFinalization(params: Readonly<{
|
|
431
395
|
agent: string;
|
|
432
396
|
channelDir?: string;
|
|
@@ -455,33 +419,6 @@ export function requestSandboxTaskFinalization(params: Readonly<{
|
|
|
455
419
|
return exchangeSandboxControl(request, params);
|
|
456
420
|
}
|
|
457
421
|
|
|
458
|
-
export function requestSandboxTaskWorkflow(params: Readonly<{
|
|
459
|
-
workflow: TaskWorkflowRequest;
|
|
460
|
-
channelDir?: string;
|
|
461
|
-
statusDir?: string;
|
|
462
|
-
token?: string;
|
|
463
|
-
generation?: string;
|
|
464
|
-
timeoutMs?: number;
|
|
465
|
-
authority?: LifecycleAuthorityRequestV1;
|
|
466
|
-
}>): SandboxControlResponse {
|
|
467
|
-
const auth = authority(params);
|
|
468
|
-
const issuedAt = Date.now();
|
|
469
|
-
const request: SandboxTaskWorkflowRequest = {
|
|
470
|
-
version: 3,
|
|
471
|
-
id: params.workflow.id,
|
|
472
|
-
...auth,
|
|
473
|
-
issuedAt,
|
|
474
|
-
expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
|
|
475
|
-
family: 'task-workflow',
|
|
476
|
-
args: [],
|
|
477
|
-
controllerProcess: null,
|
|
478
|
-
controllerProof: null,
|
|
479
|
-
workflow: params.workflow,
|
|
480
|
-
...(params.authority ? { authority: params.authority } : {})
|
|
481
|
-
};
|
|
482
|
-
return exchangeSandboxControl(request, params);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
422
|
type CodexControllerClosed = Readonly<{
|
|
486
423
|
version: 1;
|
|
487
424
|
status: 'closed';
|