@fitlab-ai/agent-infra 0.11.2 → 0.11.3
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/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/manual-validation.js +22 -27
- package/dist/lib/internal/sandbox-control.js +1 -43
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +6 -2
- 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/finalization-response.js +3 -5
- package/dist/lib/sandbox/control/protocol.js +5 -64
- package/dist/lib/sandbox/control/server.js +29 -206
- package/dist/lib/sandbox/control/state.js +1 -7
- package/dist/lib/sandbox/control/task-view.js +1 -2
- package/dist/lib/task/control-authority.js +34 -36
- package/dist/lib/task/control-recovery.js +3 -42
- package/dist/lib/task/delegation-receipts.js +2 -2
- package/dist/lib/task/finalization-artifacts.js +1 -3
- package/dist/lib/task/finalization-state.js +1 -1
- package/dist/lib/task/finalization.js +53 -126
- package/dist/lib/task/lifecycle.js +1 -1
- package/dist/lib/task/manual-validation-receipt.js +8 -3
- package/dist/lib/task/orchestration.js +12 -6
- 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/manual-validation.ts +22 -26
- package/lib/internal/sandbox-control.ts +0 -42
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +7 -4
- 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/finalization-response.ts +3 -5
- package/lib/sandbox/control/protocol.ts +7 -83
- package/lib/sandbox/control/server.ts +30 -220
- package/lib/sandbox/control/state.ts +1 -9
- package/lib/sandbox/control/task-view.ts +1 -2
- package/lib/task/control-authority.ts +38 -47
- package/lib/task/control-recovery.ts +3 -43
- package/lib/task/delegation-receipts.ts +2 -2
- package/lib/task/finalization-artifacts.ts +1 -3
- package/lib/task/finalization-state.ts +1 -1
- package/lib/task/finalization.ts +57 -121
- package/lib/task/lifecycle.ts +2 -2
- package/lib/task/manual-validation-receipt.ts +8 -2
- package/lib/task/orchestration.ts +30 -19
- 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/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 +6 -7
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +6 -7
- 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/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/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
package/dist/bin/internal-cli.js
CHANGED
|
@@ -49,7 +49,9 @@ function taskControlTransportFailure(message, code = 'TASK_CONTROL_TRANSPORT_INV
|
|
|
49
49
|
}
|
|
50
50
|
let controlRouted = false;
|
|
51
51
|
if (!taskViewGuardFailed && !markerlessHelp && (taskControlCommand || taskWorkflowCommand || manualValidationWorkflowCommand)) {
|
|
52
|
-
const transport = resolveSandboxControlTransport(process.env
|
|
52
|
+
const transport = resolveSandboxControlTransport(process.env, {
|
|
53
|
+
localWorkflow: command === 'task-orchestration' || taskWorkflowCommand || manualValidationWorkflowCommand
|
|
54
|
+
});
|
|
53
55
|
switch (transport.kind) {
|
|
54
56
|
case 'fail-closed': {
|
|
55
57
|
const reason = transport.reasonCode ?? 'TASK_CONTROL_TRANSPORT_INVALID';
|
|
@@ -60,9 +62,12 @@ if (!taskViewGuardFailed && !markerlessHelp && (taskControlCommand || taskWorkfl
|
|
|
60
62
|
// The host CLI is the direct-host authority. Domain handlers retain their
|
|
61
63
|
// task locks, atomic writes, and operation-specific recovery facts.
|
|
62
64
|
break;
|
|
65
|
+
case 'sandbox-local': {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
63
68
|
case 'broker-client': {
|
|
64
69
|
const { sandboxControl } = await import('../lib/internal/sandbox-control.js');
|
|
65
|
-
await sandboxControl(['client',
|
|
70
|
+
await sandboxControl(['client', command, ...process.argv.slice(3)]);
|
|
66
71
|
break;
|
|
67
72
|
}
|
|
68
73
|
}
|
|
@@ -408,6 +408,9 @@ function defineAgentClientAdapter(candidate) {
|
|
|
408
408
|
capabilities: Object.freeze(capabilities),
|
|
409
409
|
modelSelection: frozenModelSelection,
|
|
410
410
|
delegationEvidence: Object.freeze({ ...evidence }),
|
|
411
|
+
...(candidate.orchestrationAdapter === undefined
|
|
412
|
+
? {}
|
|
413
|
+
: { orchestrationAdapter: Object.freeze({ ...candidate.orchestrationAdapter }) }),
|
|
411
414
|
project: Object.freeze({
|
|
412
415
|
ownedPathPrefixes: Object.freeze(paths),
|
|
413
416
|
...projectAssets,
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { createCodexLifecycleStore } from './store.js';
|
|
3
|
+
import { managedDelegationRole } from '../../../task/delegation-receipts.js';
|
|
4
|
+
import { normalizeAgentToken } from '../../tokens.js';
|
|
5
|
+
import { locateActivityLog, pairEntries, appendActivityEntry } from '../../../task/activity-log.js';
|
|
6
|
+
import { captureTaskWriteMetadata, writeTask } from '../../../task/write.js';
|
|
7
|
+
import { parseTypedTaskFrontmatter } from '../../../task/frontmatter.js';
|
|
8
|
+
import { resolveTaskRef } from '../../../task/resolve-ref.js';
|
|
9
|
+
import { resolveAgentRuntimeStoreRoot } from '../../../runtime/agent-runtime.js';
|
|
10
|
+
import { recoverActivatedOrchestrationDelegationUnderLock, readRun } from '../../../task/orchestration.js';
|
|
11
|
+
const ACTIONS = {
|
|
12
|
+
analysis: 'Analyze Task',
|
|
13
|
+
'review-analysis': 'Review Analysis',
|
|
14
|
+
plan: 'Plan Task',
|
|
15
|
+
'review-plan': 'Review Plan',
|
|
16
|
+
code: 'Code Task',
|
|
17
|
+
'review-code': 'Review Code'
|
|
18
|
+
};
|
|
19
|
+
function result(request, status, extra = {}) {
|
|
20
|
+
return {
|
|
21
|
+
status,
|
|
22
|
+
changed: status === 'applied',
|
|
23
|
+
targetState: 'active',
|
|
24
|
+
requestRef: request.taskRef,
|
|
25
|
+
intent: 'recover-started',
|
|
26
|
+
taskId: null,
|
|
27
|
+
receiptId: null,
|
|
28
|
+
childId: null,
|
|
29
|
+
error: null,
|
|
30
|
+
...extra
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function failure(request, status, code, message, extra = {}) {
|
|
34
|
+
return result(request, status, { error: { code, message }, ...extra });
|
|
35
|
+
}
|
|
36
|
+
function actionPattern(receipt) {
|
|
37
|
+
const action = ACTIONS[receipt.stage];
|
|
38
|
+
if (!action)
|
|
39
|
+
return null;
|
|
40
|
+
const escaped = action.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
41
|
+
return new RegExp(`^${escaped} \\(Round ${receipt.round}(?:, (?:fix for review-code(?:-r[2-9]|-r[1-9]\\d+)?.md|decision II-[1-9]\\d*))?\\)$`, 'u');
|
|
42
|
+
}
|
|
43
|
+
function openRows(section) {
|
|
44
|
+
return pairEntries([...section.entries]).filter((row) => row.started !== '' && row.done === '');
|
|
45
|
+
}
|
|
46
|
+
function rowForReceipt(rows, receipt) {
|
|
47
|
+
const pattern = actionPattern(receipt);
|
|
48
|
+
return pattern ? rows.filter((row) => pattern.test(row.step)) : [];
|
|
49
|
+
}
|
|
50
|
+
function readLifecycleStore(options, repoRoot) {
|
|
51
|
+
return options.lifecycleStore ?? createCodexLifecycleStore({
|
|
52
|
+
root: resolveAgentRuntimeStoreRoot({ repoRoot, store: 'lifecycle' }),
|
|
53
|
+
cliVersion: 'recovery',
|
|
54
|
+
now: options.now
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function readStoredEvidence(store, childId) {
|
|
58
|
+
try {
|
|
59
|
+
return store.read(childId);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
63
|
+
if (message.includes('was not found uniquely'))
|
|
64
|
+
return { missing: true };
|
|
65
|
+
return { error: error instanceof Error ? error : new Error(message) };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function validateStopRecord(record, receipt) {
|
|
69
|
+
const start = record.state.startEvidence;
|
|
70
|
+
const stop = record.state.stopEvidence;
|
|
71
|
+
const provenance = receipt.lifecycleProvenance;
|
|
72
|
+
const host = receipt.hostEvidence;
|
|
73
|
+
if (receipt.client !== 'codex'
|
|
74
|
+
|| !provenance
|
|
75
|
+
|| host?.kind !== 'codex-lifecycle-v2'
|
|
76
|
+
|| !start
|
|
77
|
+
|| !stop
|
|
78
|
+
|| record.state.status !== 'stop-ready'
|
|
79
|
+
|| stop.terminalStatus !== 'completed'
|
|
80
|
+
|| stop.hookStopObserved !== true
|
|
81
|
+
|| start.childThreadId !== receipt.childId
|
|
82
|
+
|| start.parentThreadId !== receipt.parentId
|
|
83
|
+
|| managedDelegationRole(start.nativeAgent) !== receipt.role
|
|
84
|
+
|| start.hookDefinitionHash !== provenance.hookDefinitionHash
|
|
85
|
+
|| host.hookDefinitionHash !== provenance.hookDefinitionHash
|
|
86
|
+
|| host.capabilitySessionId !== provenance.capabilitySessionId
|
|
87
|
+
|| host.capabilityTurnId !== provenance.capabilityTurnId
|
|
88
|
+
|| host.capabilityToolUseId !== provenance.capabilityToolUseId
|
|
89
|
+
|| host.spawnToolUseId !== start.spawnToolUseId
|
|
90
|
+
|| !host.spawnObservedAt
|
|
91
|
+
|| host.spawnObservedAt !== record.spawnObservedAt
|
|
92
|
+
|| host.controllerInstanceDigest !== provenance.controllerInstanceDigest
|
|
93
|
+
|| host.controlGeneration !== provenance.controlGeneration
|
|
94
|
+
|| typeof host.controllerInstanceDigest !== 'string'
|
|
95
|
+
|| typeof host.controlGeneration !== 'string'
|
|
96
|
+
|| host.startRevision < 1
|
|
97
|
+
|| record.revision <= host.startRevision
|
|
98
|
+
|| record.state.child?.childThreadId !== receipt.childId
|
|
99
|
+
|| record.state.stop?.childThreadId !== receipt.childId) {
|
|
100
|
+
return { ok: false, code: 'RECOVERY_STOP_EVIDENCE_INVALID', message: 'Codex stop evidence does not match the activated receipt' };
|
|
101
|
+
}
|
|
102
|
+
if (record.consumer !== null && record.consumer !== receipt.id) {
|
|
103
|
+
return { ok: false, code: 'RECOVERY_CONSUMER_CONFLICT', message: `Codex lifecycle evidence was consumed by '${record.consumer}'` };
|
|
104
|
+
}
|
|
105
|
+
return { ok: true };
|
|
106
|
+
}
|
|
107
|
+
function abortedCandidates(run, rows) {
|
|
108
|
+
const candidates = run.receipts.flatMap((receipt) => {
|
|
109
|
+
if (receipt.status !== 'aborted' || receipt.activatedAt === null || receipt.client !== 'codex')
|
|
110
|
+
return [];
|
|
111
|
+
const matches = rowForReceipt(rows, receipt);
|
|
112
|
+
return matches.length === 1 ? [{ receipt, row: matches[0] }] : [];
|
|
113
|
+
});
|
|
114
|
+
return candidates;
|
|
115
|
+
}
|
|
116
|
+
function appendRecoveryLog(request, taskId, section, candidate, repoRoot, writer) {
|
|
117
|
+
const metadata = captureTaskWriteMetadata();
|
|
118
|
+
const body = appendActivityEntry(section, {
|
|
119
|
+
time: metadata.timestamp,
|
|
120
|
+
step: `${candidate.row.step} [aborted]`,
|
|
121
|
+
agent: request.agent,
|
|
122
|
+
note: `terminated Codex delegation; receipt=${candidate.receipt.id}; child=${candidate.receipt.childId}`
|
|
123
|
+
});
|
|
124
|
+
const written = writer({
|
|
125
|
+
taskRef: taskId,
|
|
126
|
+
expectedState: 'active',
|
|
127
|
+
mutations: [{ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body }]
|
|
128
|
+
}, { repoRoot, metadataProvider: () => metadata });
|
|
129
|
+
if (written.status === 'failed') {
|
|
130
|
+
return failure(request, 'owner-unknown', 'RECOVERY_LOG_WRITE_FAILED', written.error.message, {
|
|
131
|
+
taskId, receiptId: candidate.receipt.id, childId: candidate.receipt.childId
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return result(request, 'applied', {
|
|
135
|
+
taskId, receiptId: candidate.receipt.id, childId: candidate.receipt.childId
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function recoverStartedLifecycleUnderLock(request, options = {}) {
|
|
139
|
+
if (!request || request.intent !== 'recover-started' || request.auto !== true || normalizeAgentToken(request.agent) !== 'codex') {
|
|
140
|
+
return failure(request, 'conflict', 'RECOVERY_PAYLOAD_INVALID', 'automatic recovery requires a task and the Codex agent');
|
|
141
|
+
}
|
|
142
|
+
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
143
|
+
if (!resolved.ok)
|
|
144
|
+
return failure(request, 'conflict', resolved.code, resolved.message);
|
|
145
|
+
const taskId = resolved.taskId;
|
|
146
|
+
let content;
|
|
147
|
+
try {
|
|
148
|
+
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
149
|
+
parseTypedTaskFrontmatter(content);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
return failure(request, 'conflict', 'RECOVERY_TASK_INVALID', error instanceof Error ? error.message : String(error), { taskId });
|
|
153
|
+
}
|
|
154
|
+
const section = locateActivityLog(content);
|
|
155
|
+
if (!section)
|
|
156
|
+
return failure(request, 'conflict', 'RECOVERY_LOG_INVALID', 'task has no unique Activity Log section', { taskId });
|
|
157
|
+
let run;
|
|
158
|
+
try {
|
|
159
|
+
run = readRun(resolved.taskDir, options.orchestration);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
return failure(request, 'owner-unknown', 'RECOVERY_ORCHESTRATION_UNKNOWN', error instanceof Error ? error.message : String(error), { taskId });
|
|
163
|
+
}
|
|
164
|
+
if (!run)
|
|
165
|
+
return result(request, 'no-op', { taskId });
|
|
166
|
+
const rows = openRows(section);
|
|
167
|
+
const pending = run.pendingDelegation;
|
|
168
|
+
if (!pending) {
|
|
169
|
+
const candidates = abortedCandidates(run, rows);
|
|
170
|
+
if (candidates.length === 0)
|
|
171
|
+
return result(request, 'no-op', { taskId });
|
|
172
|
+
if (candidates.length > 1) {
|
|
173
|
+
return failure(request, 'conflict', 'RECOVERY_CANDIDATE_AMBIGUOUS', 'more than one aborted delegation has an open lifecycle row', { taskId });
|
|
174
|
+
}
|
|
175
|
+
return appendRecoveryLog(request, taskId, section, candidates[0], resolved.repoRoot, options.writeTask ?? writeTask);
|
|
176
|
+
}
|
|
177
|
+
if (pending.status !== 'activated')
|
|
178
|
+
return result(request, 'no-op', { taskId });
|
|
179
|
+
if (pending.client !== 'codex' || run.status !== 'running' || run.pause !== null) {
|
|
180
|
+
return failure(request, 'conflict', 'RECOVERY_ORCHESTRATION_INVALID', 'activated recovery requires one running Codex delegation', {
|
|
181
|
+
taskId, receiptId: pending.id, childId: pending.childId
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const matchingRows = rowForReceipt(rows, pending);
|
|
185
|
+
if (matchingRows.length !== 1 || normalizeAgentToken(matchingRows[0].agent) !== 'codex' || matchingRows[0].note !== 'started') {
|
|
186
|
+
return failure(request, 'conflict', 'RECOVERY_LOG_CONFLICT', 'activated delegation does not have one matching open lifecycle row', {
|
|
187
|
+
taskId, receiptId: pending.id, childId: pending.childId
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (!pending.childId) {
|
|
191
|
+
return failure(request, 'conflict', 'RECOVERY_DELEGATION_INVALID', 'activated delegation has no child identity', { taskId, receiptId: pending.id });
|
|
192
|
+
}
|
|
193
|
+
const store = readLifecycleStore(options, resolved.repoRoot);
|
|
194
|
+
const stored = readStoredEvidence(store, pending.childId);
|
|
195
|
+
if ('error' in stored)
|
|
196
|
+
return failure(request, 'owner-unknown', 'RECOVERY_STORE_UNKNOWN', stored.error.message, { taskId, receiptId: pending.id, childId: pending.childId });
|
|
197
|
+
if ('missing' in stored)
|
|
198
|
+
return failure(request, 'owner-unknown', 'RECOVERY_STOP_EVIDENCE_MISSING', 'matching Codex lifecycle stop evidence is missing', { taskId, receiptId: pending.id, childId: pending.childId });
|
|
199
|
+
const evidence = validateStopRecord(stored, pending);
|
|
200
|
+
if (!evidence.ok)
|
|
201
|
+
return failure(request, evidence.code === 'RECOVERY_CONSUMER_CONFLICT' ? 'conflict' : 'owner-unknown', evidence.code, evidence.message, { taskId, receiptId: pending.id, childId: pending.childId });
|
|
202
|
+
let consumed = stored;
|
|
203
|
+
if (stored.consumer === null) {
|
|
204
|
+
try {
|
|
205
|
+
consumed = store.consume(pending.childId, pending.id, pending.hostEvidence?.hookDefinitionHash);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
return failure(request, 'owner-unknown', 'RECOVERY_EVIDENCE_CONSUME_FAILED', error instanceof Error ? error.message : String(error), { taskId, receiptId: pending.id, childId: pending.childId });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (!consumed.consumedAt) {
|
|
212
|
+
return failure(request, 'owner-unknown', 'RECOVERY_EVIDENCE_INVALID', 'consumed stop evidence has no timestamp', { taskId, receiptId: pending.id, childId: pending.childId });
|
|
213
|
+
}
|
|
214
|
+
const recovered = recoverActivatedOrchestrationDelegationUnderLock(taskId, {
|
|
215
|
+
receiptId: pending.id,
|
|
216
|
+
stage: pending.stage,
|
|
217
|
+
round: pending.round,
|
|
218
|
+
artifact: pending.artifact,
|
|
219
|
+
startedAgent: request.agent,
|
|
220
|
+
childId: pending.childId,
|
|
221
|
+
stopRevision: consumed.revision,
|
|
222
|
+
consumer: pending.id,
|
|
223
|
+
consumedAt: consumed.consumedAt
|
|
224
|
+
}, { ...options.orchestration, repoRoot: resolved.repoRoot, now: options.now });
|
|
225
|
+
if (recovered.status === 'failed' || !recovered.run) {
|
|
226
|
+
return failure(request, 'owner-unknown', recovered.error?.code ?? 'RECOVERY_ORCHESTRATION_FAILED', recovered.error?.message ?? 'orchestration recovery failed', { taskId, receiptId: pending.id, childId: pending.childId });
|
|
227
|
+
}
|
|
228
|
+
return appendRecoveryLog(request, taskId, section, { receipt: recovered.run.receipts.at(-1), row: matchingRows[0] }, resolved.repoRoot, options.writeTask ?? writeTask);
|
|
229
|
+
}
|
|
230
|
+
function recoverStartedLifecycleFromAdapter(request, options = {}) {
|
|
231
|
+
return recoverStartedLifecycleUnderLock(request, options);
|
|
232
|
+
}
|
|
233
|
+
export { recoverStartedLifecycleFromAdapter, recoverStartedLifecycleUnderLock };
|
|
234
|
+
//# sourceMappingURL=recovery.js.map
|
|
@@ -5,8 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import semver from 'semver';
|
|
7
7
|
import * as toml from 'smol-toml';
|
|
8
|
-
import { requestCodexControllerClose, requestCodexControllerOpen, requestCodexControllerVerify
|
|
9
|
-
import { readSandboxControlStatus } from '../../../sandbox/control/state.js';
|
|
8
|
+
import { requestCodexControllerClose, requestCodexControllerOpen, requestCodexControllerVerify } from '../../../sandbox/control/client.js';
|
|
10
9
|
import { getProcessStartTime } from '../../../server/process-state.js';
|
|
11
10
|
import { LIFECYCLE_PROTOCOL_VERSION } from './build-identity.js';
|
|
12
11
|
import { contextFromControllerLease, controllerProofFromContext, verifyCodexSandboxControllerContextWithWarnings as verifyContextFileWithWarnings, writeCodexSandboxControllerContext } from './controller-context.js';
|
|
@@ -56,44 +55,18 @@ function verifyRuntime(runtimeDir) {
|
|
|
56
55
|
fs.rmSync(probe, { force: true });
|
|
57
56
|
}
|
|
58
57
|
}
|
|
59
|
-
function
|
|
58
|
+
function verifyControllerBinding(context, control, requestControllerVerify) {
|
|
60
59
|
verifyRuntime(control.runtimeDir);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
64
|
-
try {
|
|
65
|
-
status = readSandboxControlStatus(control.statusDir);
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
lastError = error;
|
|
70
|
-
if (attempt < 4)
|
|
71
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (!status) {
|
|
75
|
-
throw new Error(`CODEX_SANDBOX_CONTROLLER_CONTROL_UNAVAILABLE: ${String(lastError)}`);
|
|
76
|
-
}
|
|
77
|
-
if (status.generation !== control.generation
|
|
78
|
-
|| status.state !== 'healthy'
|
|
79
|
-
|| Date.now() - status.updatedAt > 1_500) {
|
|
80
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTROL_UNAVAILABLE');
|
|
81
|
-
}
|
|
82
|
-
const response = requestSandboxControl({
|
|
83
|
-
family: 'task-orchestration',
|
|
84
|
-
args: [taskId, 'status'],
|
|
60
|
+
const verified = requestControllerVerify({
|
|
61
|
+
controllerProof: controllerProofFromContext(context),
|
|
85
62
|
...control,
|
|
86
63
|
timeoutMs: 30_000
|
|
87
64
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
65
|
+
if (verified.binding.taskId !== context.taskId
|
|
66
|
+
|| verified.binding.controlGeneration !== context.controlGeneration
|
|
67
|
+
|| verified.binding.controllerInstanceDigest !== context.controllerInstanceDigest) {
|
|
93
68
|
throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_BINDING_INVALID');
|
|
94
69
|
}
|
|
95
|
-
if (payload.taskId !== taskId)
|
|
96
|
-
throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_BINDING_INVALID');
|
|
97
70
|
}
|
|
98
71
|
function verifyCodexSandboxControllerContextWithWarnings(contextPath, options = {}) {
|
|
99
72
|
const control = options.control ?? controlFromEnvironment();
|
|
@@ -272,7 +245,7 @@ function prepareCodexSandboxController(input, options = {}) {
|
|
|
272
245
|
|| opened.lease.controllerProcess.startTime !== parentStartTime) {
|
|
273
246
|
throw new Error('SANDBOX_CONTROL_RESULT_INVALID');
|
|
274
247
|
}
|
|
275
|
-
(options.
|
|
248
|
+
verifyControllerBinding(context, control, options.verifyController ?? requestCodexControllerVerify);
|
|
276
249
|
const contextPath = path.join(home, 'controller-context.json');
|
|
277
250
|
writeCodexSandboxControllerContext(contextPath, context);
|
|
278
251
|
const shimDir = path.join(home, 'bin');
|
|
@@ -6,19 +6,9 @@ import { resolveAgentRuntimeStoreRoot } from '../../../runtime/agent-runtime.js'
|
|
|
6
6
|
const LOCK_RETRY_MS = 10;
|
|
7
7
|
const LOCK_TIMEOUT_MS = 1_000;
|
|
8
8
|
const LOCK_STALE_MS = 30_000;
|
|
9
|
-
const RECOVERY_CONSUMER_RE = /^lifecycle-recovery:([^:\r\n]+):([^:\r\n]+)$/u;
|
|
10
9
|
function digest(value) {
|
|
11
10
|
return crypto.createHash('sha256').update(value).digest('hex');
|
|
12
11
|
}
|
|
13
|
-
function isRecoveryConsumer(value) {
|
|
14
|
-
return RECOVERY_CONSUMER_RE.test(value);
|
|
15
|
-
}
|
|
16
|
-
function recoveryConsumer(taskId, receiptId) {
|
|
17
|
-
if (!/^[^:\r\n]+$/u.test(taskId) || !/^[^:\r\n]+$/u.test(receiptId)) {
|
|
18
|
-
throw new Error('Codex lifecycle recovery claim identity is invalid');
|
|
19
|
-
}
|
|
20
|
-
return `lifecycle-recovery:${taskId}:${receiptId}`;
|
|
21
|
-
}
|
|
22
12
|
function readRecord(file) {
|
|
23
13
|
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
24
14
|
if (value.schemaVersion !== 1
|
|
@@ -255,29 +245,8 @@ function createCodexLifecycleStore(options) {
|
|
|
255
245
|
function consume(childThreadId, consumer, expectedHookDefinitionHash) {
|
|
256
246
|
if (!consumer.trim())
|
|
257
247
|
throw new Error('Codex lifecycle consumer is required');
|
|
258
|
-
if (consumer.startsWith('lifecycle-recovery:')) {
|
|
259
|
-
throw new Error('Codex lifecycle recovery claims require claimRecovery');
|
|
260
|
-
}
|
|
261
248
|
return consumeInternal(childThreadId, consumer, expectedHookDefinitionHash);
|
|
262
249
|
}
|
|
263
|
-
function claimRecovery(childThreadId, taskId, receiptId, expectedHookDefinitionHash) {
|
|
264
|
-
return consumeInternal(childThreadId, recoveryConsumer(taskId, receiptId), expectedHookDefinitionHash);
|
|
265
|
-
}
|
|
266
|
-
function releaseRecovery(childThreadId, consumer) {
|
|
267
|
-
if (!/^lifecycle-recovery:[^:\r\n]+:[^:\r\n]+$/.test(consumer))
|
|
268
|
-
return false;
|
|
269
|
-
return withWriteLock(() => {
|
|
270
|
-
const matches = findByChild(childThreadId);
|
|
271
|
-
if (matches.length !== 1)
|
|
272
|
-
return false;
|
|
273
|
-
const file = matches[0];
|
|
274
|
-
const current = readRecord(file);
|
|
275
|
-
if (current.state.status !== 'stop-ready' || current.consumer !== consumer || !current.consumedAt)
|
|
276
|
-
return false;
|
|
277
|
-
fs.unlinkSync(file);
|
|
278
|
-
return true;
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
250
|
function expireBefore(cutoff) {
|
|
282
251
|
return withWriteLock(() => {
|
|
283
252
|
let changed = 0;
|
|
@@ -285,8 +254,6 @@ function createCodexLifecycleStore(options) {
|
|
|
285
254
|
const current = readRecord(file);
|
|
286
255
|
if (current.updatedAt >= cutoff)
|
|
287
256
|
continue;
|
|
288
|
-
if (current.consumer && isRecoveryConsumer(current.consumer))
|
|
289
|
-
continue;
|
|
290
257
|
if (current.consumer || ['invalid', 'expired', 'stop-ready'].includes(current.state.status)) {
|
|
291
258
|
fs.unlinkSync(file);
|
|
292
259
|
changed += 1;
|
|
@@ -306,7 +273,7 @@ function createCodexLifecycleStore(options) {
|
|
|
306
273
|
return changed;
|
|
307
274
|
});
|
|
308
275
|
}
|
|
309
|
-
return Object.freeze({ root, apply, applyToSpawn, consume,
|
|
276
|
+
return Object.freeze({ root, apply, applyToSpawn, consume, expireBefore, findByParent, read });
|
|
310
277
|
}
|
|
311
278
|
export { createCodexLifecycleStore, hasActiveCodexLifecycleEvidence };
|
|
312
279
|
//# sourceMappingURL=store.js.map
|
|
@@ -24,6 +24,16 @@ const codexAdapter = defineAgentClientAdapter({
|
|
|
24
24
|
actualModel: 'app-server',
|
|
25
25
|
actualReasoningEffort: 'app-server'
|
|
26
26
|
},
|
|
27
|
+
orchestrationAdapter: {
|
|
28
|
+
prepareDelegation: async (...args) => {
|
|
29
|
+
const { prepareCodexOrchestrationDelegation } = await import('../../task/codex-orchestration.js');
|
|
30
|
+
return prepareCodexOrchestrationDelegation(...args);
|
|
31
|
+
},
|
|
32
|
+
recoverStarted: async (...args) => {
|
|
33
|
+
const { recoverStartedLifecycleFromAdapter } = await import('./codex-lifecycle/recovery.js');
|
|
34
|
+
return recoverStartedLifecycleFromAdapter(...args);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
27
37
|
project: {
|
|
28
38
|
ownedPathPrefixes: ['.codex/'],
|
|
29
39
|
managed: ['.codex/hooks.json', '.codex/agents/'],
|
|
@@ -5,7 +5,7 @@ import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
|
5
5
|
import { applyTaskEvent } from '../task/events.js';
|
|
6
6
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
|
|
7
7
|
import { normalizeAgentToken } from '../agent-clients/tokens.js';
|
|
8
|
-
import { createManualValidationReceipt, manualValidationFinalSummaryDigest, manualValidationFinalSummaryProjectionMatches, readManualValidationReceipt, writeManualValidationReceiptAtomic } from '../task/manual-validation-receipt.js';
|
|
8
|
+
import { MANUAL_VALIDATION_RECEIPT_PLACEHOLDER, createManualValidationReceipt, manualValidationFinalSummaryDigest, manualValidationFinalSummaryProjectionMatches, readManualValidationReceipt, writeManualValidationReceiptAtomic } from '../task/manual-validation-receipt.js';
|
|
9
9
|
import { archiveManualValidationGeneration, createManualValidationTransaction, readManualValidationTransaction, retryManualValidationTransaction, summaryPreimageDigest, transitionManualValidationTransaction, validateManualValidationGenerationArchive, writeManualValidationTransactionAtomic } from '../task/manual-validation-transaction.js';
|
|
10
10
|
import { sha256File } from '../task/artifact-receipts.js';
|
|
11
11
|
import { summaryCommentState, syncPullRequestSummary } from '../platform/pr-summary.js';
|
|
@@ -22,28 +22,22 @@ function printFailure(format, error) {
|
|
|
22
22
|
process.stderr.write(`${error.code}: ${error.message}\n`);
|
|
23
23
|
process.exitCode = 1;
|
|
24
24
|
}
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
function manualSummaryBody(body, phase, transactionId, receiptDigest = '', evidenceDigest = '', prHeadSha = '') {
|
|
25
|
+
const MANUAL_VALIDATION_STATUS_HEADING = /^###\s+(?:⚠️\s+(?:需人工校验|Manual Validation Required)|✅\s+(?:人工验证已通过|无需人工校验|Manual Validation Passed|No Manual Validation Required)|⏳\s+(?:人工验证待收尾|Manual Validation Pending))\s*$/mu;
|
|
26
|
+
function manualSummaryBody(body, phase) {
|
|
28
27
|
const chinese = /###\s+(?:⚠️\s+需人工校验|✅\s+(?:人工验证已通过|无需人工校验)|⏳\s+人工验证待收尾)\s*$/mu.test(body);
|
|
29
|
-
const
|
|
30
|
-
? chinese
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return trailingPlaceholder
|
|
43
|
-
? `${trailingPlaceholder.trim()}\n\n${section}\n\n`
|
|
44
|
-
: `${section}\n\n`;
|
|
45
|
-
});
|
|
46
|
-
return inserted ? `${updated.replace(/\s+$/u, '')}\n` : `${body.replace(/\s+$/u, '')}\n\n${section}\n`;
|
|
28
|
+
const heading = phase === 'pending'
|
|
29
|
+
? chinese ? '### ⏳ 人工验证待收尾' : '### ⏳ Manual Validation Pending'
|
|
30
|
+
: chinese ? '### ✅ 人工验证已通过' : '### ✅ Manual Validation Passed';
|
|
31
|
+
const marker = phase === 'final'
|
|
32
|
+
? `\n\n${MANUAL_VALIDATION_RECEIPT_PLACEHOLDER}`
|
|
33
|
+
: '';
|
|
34
|
+
if (MANUAL_VALIDATION_STATUS_HEADING.test(body)) {
|
|
35
|
+
return `${body.replace(MANUAL_VALIDATION_STATUS_HEADING, `${heading}${marker}`).replace(/\s+$/u, '')}\n`;
|
|
36
|
+
}
|
|
37
|
+
const fallback = phase === 'pending'
|
|
38
|
+
? chinese ? '人工验证正在等待最终完成。' : 'Manual validation is awaiting final promotion.'
|
|
39
|
+
: chinese ? '人工验证已通过。' : 'Manual validation passed.';
|
|
40
|
+
return `${body.replace(/\s+$/u, '')}\n\n${heading}${marker}\n\n${fallback}\n`;
|
|
47
41
|
}
|
|
48
42
|
function openManualValidationStarted(taskMdPath) {
|
|
49
43
|
let content;
|
|
@@ -102,7 +96,9 @@ async function executeManualValidationTransactionLocked(taskRef, values, cwd, re
|
|
|
102
96
|
const pullRequest = currentState.pullRequest;
|
|
103
97
|
const preimageBody = currentState.comment?.body ?? '';
|
|
104
98
|
const preimage = { commentId: currentState.comment?.id ?? null, body: preimageBody, digest: summaryPreimageDigest(preimageBody) };
|
|
105
|
-
const
|
|
99
|
+
const summaryPath = path.resolve(cwd, values.summaryFile);
|
|
100
|
+
const finalBodyWithoutReceipt = fs.readFileSync(summaryPath, 'utf8');
|
|
101
|
+
const evidenceDigest = sha256File(summaryPath);
|
|
106
102
|
const archiveGeneration = options.archiveGeneration ?? archiveManualValidationGeneration;
|
|
107
103
|
let transactionResult = readManualValidationTransaction(resolved.taskDir, { taskId: resolved.taskId, prNumber: pullRequest.number, prHeadSha: pullRequest.head.sha, evidenceDigest, artifact: values.artifact });
|
|
108
104
|
let previousTransaction = null;
|
|
@@ -135,8 +131,7 @@ async function executeManualValidationTransactionLocked(taskRef, values, cwd, re
|
|
|
135
131
|
const transactionId = transactionResult.ok
|
|
136
132
|
? transactionResult.value.transactionId
|
|
137
133
|
: (openStarted.transactionId ?? values.transactionId ?? `mv-${Date.now()}`);
|
|
138
|
-
const
|
|
139
|
-
const pendingBody = manualSummaryBody(finalBodyWithoutReceipt, 'pending', transactionId);
|
|
134
|
+
const pendingBody = manualSummaryBody(finalBodyWithoutReceipt, 'pending');
|
|
140
135
|
const createTransaction = () => createManualValidationTransaction({
|
|
141
136
|
transactionId,
|
|
142
137
|
taskId: resolved.taskId,
|
|
@@ -145,7 +140,7 @@ async function executeManualValidationTransactionLocked(taskRef, values, cwd, re
|
|
|
145
140
|
evidenceDigest,
|
|
146
141
|
summaryPreimage: preimage,
|
|
147
142
|
pendingSummaryDigest: summaryPreimageDigest(pendingBody),
|
|
148
|
-
finalSummaryDigest: manualValidationFinalSummaryDigest(manualSummaryBody(finalBodyWithoutReceipt, 'final'
|
|
143
|
+
finalSummaryDigest: manualValidationFinalSummaryDigest(manualSummaryBody(finalBodyWithoutReceipt, 'final')),
|
|
149
144
|
artifact: values.artifact,
|
|
150
145
|
attempt: 1,
|
|
151
146
|
createdAt: new Date().toISOString(),
|
|
@@ -249,7 +244,7 @@ async function executeManualValidationTransactionLocked(taskRef, values, cwd, re
|
|
|
249
244
|
transitionAndPersist('final-promotion-in-progress');
|
|
250
245
|
}
|
|
251
246
|
const promoted = await syncPullRequestSummary(taskRef, {
|
|
252
|
-
cwd, client: options.client, agent, body: manualSummaryBody(finalBodyWithoutReceipt, 'final'
|
|
247
|
+
cwd, client: options.client, agent, body: manualSummaryBody(finalBodyWithoutReceipt, 'final'), changeReportFile: path.resolve(cwd, values.changeReportFile), primaryResult, strict: true,
|
|
253
248
|
manualValidation: { phase: 'final', transactionId: transaction.transactionId, receiptDigest: receipt.receiptDigest, prHeadSha: receipt.prHeadSha, authority: 'coordinator' }, lockAlreadyHeld: true
|
|
254
249
|
});
|
|
255
250
|
if (!['applied', 'no-op'].includes(promoted.status))
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { recoverSandboxControl, requestSandboxControl, requestSandboxTaskFinalization,
|
|
1
|
+
import { recoverSandboxControl, requestSandboxControl, requestSandboxTaskFinalization, SandboxControlClientError } from '../sandbox/control/client.js';
|
|
2
2
|
import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.js';
|
|
3
3
|
import { serveSandboxControl } from '../sandbox/control/server.js';
|
|
4
4
|
import { runSandboxControlExecutor } from '../sandbox/control/executor.js';
|
|
5
5
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
|
|
6
6
|
import { parseTaskCreateResult, taskCreateExitCode } from '../task/create-service.js';
|
|
7
|
-
import { createTaskWorkflowRequest } from '../sandbox/control/task-workflow.js';
|
|
8
7
|
function writeFinalizationEnvelope(status, accepted, error, changed = false, result = null) {
|
|
9
8
|
process.stdout.write(`${JSON.stringify({ version: 1, status, changed, accepted, result, error })}\n`);
|
|
10
9
|
}
|
|
@@ -132,47 +131,6 @@ async function sandboxControl(args) {
|
|
|
132
131
|
sandboxFinalizationClient(commandArgs);
|
|
133
132
|
return;
|
|
134
133
|
}
|
|
135
|
-
if (family === 'task-workflow') {
|
|
136
|
-
const command = commandArgs[0];
|
|
137
|
-
const taskId = process.env.AGENT_INFRA_TASK_ID;
|
|
138
|
-
const generation = process.env.AGENT_INFRA_CONTROL_GENERATION;
|
|
139
|
-
if (!command || !taskId || !generation) {
|
|
140
|
-
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'SANDBOX_CONTROL_REQUEST_INVALID', message: 'task-workflow requires a bound task and generation' } })}\n`);
|
|
141
|
-
process.exitCode = 1;
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
let workflow;
|
|
145
|
-
try {
|
|
146
|
-
const args = commandArgs.slice(1);
|
|
147
|
-
if (/^\d+$/.test(args[0] ?? '')) {
|
|
148
|
-
const resolved = resolveVisibleActiveShortId(args[0]);
|
|
149
|
-
if (resolved !== taskId)
|
|
150
|
-
throw new Error('SANDBOX_TASK_REF_MISMATCH');
|
|
151
|
-
args[0] = resolved;
|
|
152
|
-
}
|
|
153
|
-
workflow = createTaskWorkflowRequest(command, args, taskId, generation);
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'SANDBOX_CONTROL_REQUEST_INVALID', message: error instanceof Error ? error.message : String(error) } })}\n`);
|
|
157
|
-
process.exitCode = 1;
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
let response;
|
|
161
|
-
try {
|
|
162
|
-
response = requestSandboxTaskWorkflow({ workflow });
|
|
163
|
-
}
|
|
164
|
-
catch (error) {
|
|
165
|
-
if (!(error instanceof SandboxControlClientError))
|
|
166
|
-
throw error;
|
|
167
|
-
writeClientError(error);
|
|
168
|
-
process.exitCode = error.detail.retryable ? 75 : 1;
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
process.stdout.write(response.stdout);
|
|
172
|
-
process.stderr.write(response.stderr);
|
|
173
|
-
process.exitCode = response.phase === 'rejected' ? response.error?.retryable ? 75 : 1 : response.exitCode ?? 1;
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
134
|
let response;
|
|
177
135
|
try {
|
|
178
136
|
response = requestSandboxControl({ family, args: commandArgs });
|
|
@@ -2,7 +2,7 @@ import { createDirectHostExecutionContext, dispatchTaskControlOperation, parseTa
|
|
|
2
2
|
import { lifecycleIntentCatalog } from '../task/lifecycle.js';
|
|
3
3
|
import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
|
|
4
4
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
|
|
5
|
-
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nrecover-started: --
|
|
5
|
+
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`;
|
|
6
6
|
function usageFailure(message) {
|
|
7
7
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LIFECYCLE_PAYLOAD_INVALID', message } })}\n`);
|
|
8
8
|
process.stderr.write(USAGE);
|
|
@@ -229,6 +229,8 @@ export function resolveDelegatedTaskOperation(args) {
|
|
|
229
229
|
const [operation, ...rest] = args;
|
|
230
230
|
if (operation !== 'client' || !rest[0])
|
|
231
231
|
return null;
|
|
232
|
+
if (rest[0] === 'task-orchestration')
|
|
233
|
+
return null;
|
|
232
234
|
return resolveTaskOperation('internal', rest[0], rest.slice(1));
|
|
233
235
|
}
|
|
234
236
|
const TASK_MARKER_KEYS = [
|
|
@@ -352,14 +354,16 @@ export function resolveSandboxControlTransport(env = process.env, options = {})
|
|
|
352
354
|
if (!hasCompleteConfig) {
|
|
353
355
|
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
|
|
354
356
|
}
|
|
355
|
-
return { kind: 'broker-client', reasonCode: null };
|
|
357
|
+
return { kind: options.localWorkflow ? 'sandbox-local' : 'broker-client', reasonCode: null };
|
|
356
358
|
}
|
|
357
359
|
if (!hasAnyMarker)
|
|
358
360
|
return { kind: 'direct-host', reasonCode: null };
|
|
359
361
|
if (!hasCompleteConfig || (taskBound && !runtime) || (!taskBound && runtime)) {
|
|
360
362
|
return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
|
|
361
363
|
}
|
|
362
|
-
return
|
|
364
|
+
return options.localWorkflow
|
|
365
|
+
? { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_MISSING' }
|
|
366
|
+
: { kind: 'broker-client', reasonCode: null };
|
|
363
367
|
}
|
|
364
368
|
export function hasTaskBoundMarker(env = process.env) {
|
|
365
369
|
return taskMarkerState(env) === 'task-bound';
|