@fitlab-ai/agent-infra 0.9.6 → 0.9.8
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 +10 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +47 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +245 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +398 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +23 -6
- package/dist/lib/decide.js +68 -14
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -3
- package/dist/lib/init.js +1 -1
- package/dist/lib/internal/codex-lifecycle.js +69 -1
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/release-workflow.js +1 -3
- package/dist/lib/internal/task-activity.js +57 -7
- package/dist/lib/internal/task-event.js +57 -4
- package/dist/lib/internal/task-ledger.js +59 -5
- package/dist/lib/internal/task-lifecycle.js +59 -8
- package/dist/lib/internal/task-orchestration.js +48 -8
- package/dist/lib/internal/task-override.js +131 -0
- package/dist/lib/internal/task-review.js +64 -6
- package/dist/lib/internal/task-verify.js +65 -3
- package/dist/lib/internal/task-warning.js +57 -4
- package/dist/lib/platform/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- package/dist/lib/platform/pr-summary.js +5 -3
- package/dist/lib/process-data/checkpoint.js +319 -0
- package/dist/lib/process-data/index.js +128 -60
- package/dist/lib/process-data/materialize.js +84 -0
- package/dist/lib/process-data/normalize.js +34 -1
- package/dist/lib/process-data/reconcile.js +55 -1
- package/dist/lib/process-data/sources.js +422 -1
- package/dist/lib/process-data/store.js +341 -11
- package/dist/lib/runtime/agent-runtime.js +56 -0
- package/dist/lib/sandbox/commands/create.js +394 -264
- package/dist/lib/sandbox/commands/rm.js +64 -15
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +23 -9
- package/dist/lib/sandbox/control/lifecycle.js +590 -61
- package/dist/lib/sandbox/control/protocol.js +7 -0
- package/dist/lib/sandbox/control/server.js +202 -26
- package/dist/lib/sandbox/control/state.js +51 -25
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/engine.js +14 -7
- package/dist/lib/sandbox/mounts.js +7 -0
- package/dist/lib/sandbox/recovery.js +135 -50
- package/dist/lib/sandbox/workspace-view.js +59 -20
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +11 -1
- package/dist/lib/server/process-state.js +107 -20
- package/dist/lib/task/activity-intent.js +4 -1
- package/dist/lib/task/artifact-lifecycle.js +82 -11
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +81 -6
- package/dist/lib/task/commands/validate.js +1 -1
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/decision-intents.js +4 -2
- package/dist/lib/task/delegation-receipts.js +120 -17
- package/dist/lib/task/events.js +148 -7
- package/dist/lib/task/guard-override.js +31 -0
- package/dist/lib/task/human-override.js +1240 -0
- package/dist/lib/task/ledger-intents.js +6 -3
- package/dist/lib/task/ledger.js +13 -1
- package/dist/lib/task/lifecycle.js +69 -14
- package/dist/lib/task/orchestration.js +293 -18
- package/dist/lib/task/review-finalization.js +3 -2
- package/dist/lib/task/sections.js +5 -1
- package/dist/lib/task/task-execution-lock.js +5 -4
- package/dist/lib/task/verification-engine.js +80 -22
- package/dist/lib/task/verification.js +13 -1
- package/dist/lib/task/workflow-warning-intents.js +4 -2
- package/dist/lib/task/write.js +2 -1
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/claude-code.ts +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +62 -4
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +322 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +480 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +35 -5
- package/lib/decide.ts +59 -14
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -2
- package/lib/init.ts +1 -1
- package/lib/internal/codex-lifecycle.ts +68 -1
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/release-workflow.ts +1 -3
- package/lib/internal/task-activity.ts +46 -6
- package/lib/internal/task-event.ts +53 -4
- package/lib/internal/task-ledger.ts +48 -4
- package/lib/internal/task-lifecycle.ts +56 -12
- package/lib/internal/task-orchestration.ts +43 -8
- package/lib/internal/task-override.ts +138 -0
- package/lib/internal/task-review.ts +51 -6
- package/lib/internal/task-verify.ts +52 -3
- package/lib/internal/task-warning.ts +44 -3
- package/lib/platform/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- package/lib/platform/pr-summary.ts +10 -3
- package/lib/process-data/checkpoint.ts +335 -0
- package/lib/process-data/index.ts +112 -60
- package/lib/process-data/materialize.ts +83 -0
- package/lib/process-data/normalize.ts +29 -1
- package/lib/process-data/reconcile.ts +64 -2
- package/lib/process-data/sources.ts +449 -2
- package/lib/process-data/store.ts +304 -11
- package/lib/process-data/types.ts +124 -2
- package/lib/runtime/agent-runtime.ts +76 -0
- package/lib/sandbox/commands/create.ts +139 -6
- package/lib/sandbox/commands/rm.ts +64 -14
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/config.ts +2 -1
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +24 -12
- package/lib/sandbox/control/lifecycle.ts +685 -67
- package/lib/sandbox/control/protocol.ts +35 -6
- package/lib/sandbox/control/server.ts +184 -24
- package/lib/sandbox/control/state.ts +67 -33
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/engine.ts +20 -9
- package/lib/sandbox/mounts.ts +8 -0
- package/lib/sandbox/recovery.ts +159 -49
- package/lib/sandbox/workspace-view.ts +73 -22
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +13 -3
- package/lib/server/process-state.ts +129 -27
- package/lib/task/activity-intent.ts +4 -2
- package/lib/task/artifact-lifecycle.ts +73 -10
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +91 -5
- package/lib/task/commands/validate.ts +1 -1
- package/lib/task/create.ts +11 -2
- package/lib/task/decision-intents.ts +4 -2
- package/lib/task/delegation-receipts.ts +205 -25
- package/lib/task/events.ts +132 -6
- package/lib/task/guard-override.ts +50 -0
- package/lib/task/human-override.ts +1503 -0
- package/lib/task/ledger-intents.ts +6 -3
- package/lib/task/ledger.ts +16 -1
- package/lib/task/lifecycle.ts +71 -14
- package/lib/task/orchestration.ts +443 -22
- package/lib/task/review-finalization.ts +6 -2
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +93 -32
- package/lib/task/verification.ts +15 -1
- package/lib/task/workflow-warning-intents.ts +4 -2
- package/lib/task/write.ts +4 -1
- package/package.json +1 -1
- package/templates/.agents/README.en.md +7 -0
- package/templates/.agents/README.zh-CN.md +5 -0
- package/templates/.agents/hooks/lifecycle-delegation.js +33 -4
- package/templates/.agents/rules/lifecycle-orchestration.en.md +10 -3
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +10 -3
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.en.md +3 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -1
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- package/templates/.agents/workspace/README.en.md +0 -26
- package/templates/.agents/workspace/README.zh-CN.md +0 -26
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
preflightCodexLifecycleEvidence,
|
|
3
5
|
resolveCodexSpawnedChild,
|
|
@@ -5,9 +7,16 @@ import {
|
|
|
5
7
|
resolveCodexThread
|
|
6
8
|
} from '../agent-clients/adapters/codex-lifecycle/app-server.ts';
|
|
7
9
|
import { createCodexLifecycleStore } from '../agent-clients/adapters/codex-lifecycle/store.ts';
|
|
10
|
+
import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
|
|
11
|
+
import { computeLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.ts';
|
|
12
|
+
import type { LifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.ts';
|
|
13
|
+
import { verifyCodexSandboxControllerContext } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.ts';
|
|
8
14
|
import type { AgentClientId } from '../agent-clients/types.ts';
|
|
15
|
+
import { resolveTaskRef } from './resolve-ref.ts';
|
|
9
16
|
import {
|
|
10
17
|
activateMatchingOrchestrationDelegation,
|
|
18
|
+
hasActivatableOrchestrationDelegation,
|
|
19
|
+
hasSealableOrchestrationDelegation,
|
|
11
20
|
pauseMatchingOrchestrationDelegation,
|
|
12
21
|
prepareOrchestrationDelegation,
|
|
13
22
|
reconcileMatchingOrchestrationDelegation,
|
|
@@ -16,12 +25,15 @@ import {
|
|
|
16
25
|
import type { OrchestrationOptions, OrchestrationResult } from './orchestration.ts';
|
|
17
26
|
|
|
18
27
|
type LifecycleStore = ReturnType<typeof createCodexLifecycleStore>;
|
|
28
|
+
type CapabilityStore = ReturnType<typeof createCodexCapabilityStore>;
|
|
19
29
|
type CodexBridgeOptions = Readonly<{
|
|
20
30
|
repoRoot?: string;
|
|
21
31
|
store?: LifecycleStore;
|
|
22
32
|
preflight?: typeof preflightCodexLifecycleEvidence;
|
|
23
33
|
resolveThread?: typeof resolveCodexThread;
|
|
24
34
|
resolveTerminal?: typeof resolveCodexTerminal;
|
|
35
|
+
capabilityStore?: CapabilityStore;
|
|
36
|
+
buildIdentity?: LifecycleBuildIdentity;
|
|
25
37
|
orchestrationOptions?: OrchestrationOptions;
|
|
26
38
|
}>;
|
|
27
39
|
|
|
@@ -59,25 +71,68 @@ function requiredStore(options: CodexBridgeOptions): LifecycleStore {
|
|
|
59
71
|
return options.store;
|
|
60
72
|
}
|
|
61
73
|
|
|
74
|
+
function controllerContext(taskId: string, repoRoot: string) {
|
|
75
|
+
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
76
|
+
return contextPath
|
|
77
|
+
? verifyCodexSandboxControllerContext(contextPath, taskId, { repoRoot })
|
|
78
|
+
: null;
|
|
79
|
+
}
|
|
80
|
+
|
|
62
81
|
async function prepareCodexOrchestrationDelegation(
|
|
63
82
|
taskRef: string,
|
|
64
83
|
input: Readonly<{
|
|
65
84
|
client: AgentClientId;
|
|
66
85
|
requestedModel?: string;
|
|
67
86
|
requestedReasoningEffort?: string;
|
|
87
|
+
capabilityToken?: string;
|
|
68
88
|
}>,
|
|
69
89
|
options: CodexBridgeOptions = {}
|
|
70
90
|
): Promise<OrchestrationResult> {
|
|
71
91
|
if (input.client !== 'codex') return prepareOrchestrationDelegation(taskRef, input, coreOptions(options));
|
|
72
92
|
try {
|
|
73
|
-
|
|
93
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
94
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot });
|
|
95
|
+
if (!resolved.ok) return bridgeFailure(resolved.code, resolved.message);
|
|
96
|
+
const preflight = await (options.preflight ?? preflightCodexLifecycleEvidence)(repoRoot);
|
|
97
|
+
if (!input.capabilityToken) {
|
|
98
|
+
return bridgeFailure(
|
|
99
|
+
'ORCHESTRATION_CODEX_CAPABILITY_REQUIRED',
|
|
100
|
+
'Codex prepare requires a current-session capability token'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
|
|
104
|
+
const controller = controllerContext(resolved.taskId, repoRoot);
|
|
105
|
+
const capabilityStore = options.capabilityStore ?? createCodexCapabilityStore();
|
|
106
|
+
const consumed = capabilityStore.consume(input.capabilityToken, {
|
|
107
|
+
taskId: resolved.taskId,
|
|
108
|
+
hookDefinitionHash: preflight.hookDefinitionHash,
|
|
109
|
+
buildIdentity,
|
|
110
|
+
...(controller ? { controller: {
|
|
111
|
+
instanceDigest: controller.controllerInstanceDigest,
|
|
112
|
+
controlGeneration: controller.controlGeneration
|
|
113
|
+
} } : {})
|
|
114
|
+
});
|
|
115
|
+
return prepareOrchestrationDelegation(taskRef, {
|
|
116
|
+
...input,
|
|
117
|
+
lifecycleProvenance: {
|
|
118
|
+
...consumed.buildIdentity,
|
|
119
|
+
hookDefinitionHash: preflight.hookDefinitionHash,
|
|
120
|
+
...preflight.hookProvenance,
|
|
121
|
+
capabilitySessionId: consumed.sessionId!,
|
|
122
|
+
capabilityTurnId: consumed.turnId!,
|
|
123
|
+
capabilityToolUseId: consumed.toolUseId!,
|
|
124
|
+
controllerInstanceDigest: controller?.controllerInstanceDigest ?? null,
|
|
125
|
+
controlGeneration: controller?.controlGeneration ?? null
|
|
126
|
+
}
|
|
127
|
+
}, coreOptions(options));
|
|
74
128
|
} catch (error) {
|
|
75
129
|
return bridgeFailure(
|
|
76
|
-
'
|
|
130
|
+
error instanceof Error && error.name.startsWith('CODEX_CAPABILITY_')
|
|
131
|
+
? error.name
|
|
132
|
+
: 'ORCHESTRATION_CLIENT_PREFLIGHT_FAILED',
|
|
77
133
|
error instanceof Error ? error.message : String(error)
|
|
78
134
|
);
|
|
79
135
|
}
|
|
80
|
-
return prepareOrchestrationDelegation(taskRef, input, coreOptions(options));
|
|
81
136
|
}
|
|
82
137
|
|
|
83
138
|
async function activateCodexOrchestrationDelegation(
|
|
@@ -85,6 +140,9 @@ async function activateCodexOrchestrationDelegation(
|
|
|
85
140
|
options: CodexBridgeOptions = {}
|
|
86
141
|
): Promise<OrchestrationResult> {
|
|
87
142
|
try {
|
|
143
|
+
if (!hasActivatableOrchestrationDelegation('codex', childThreadId, coreOptions(options))) {
|
|
144
|
+
return bridgeFailure('ORCHESTRATION_DELEGATION_MISSING', 'No matching Codex delegation is active');
|
|
145
|
+
}
|
|
88
146
|
const store = requiredStore(options);
|
|
89
147
|
const resolved = await (options.resolveThread ?? resolveCodexThread)(childThreadId);
|
|
90
148
|
store.apply(resolved.resolution.thread);
|
|
@@ -95,6 +153,23 @@ async function activateCodexOrchestrationDelegation(
|
|
|
95
153
|
if (record.state.status !== 'start-ready' || !evidence) {
|
|
96
154
|
return pauseBridge('ORCHESTRATION_CODEX_START_EVIDENCE_INVALID', 'Codex lifecycle start evidence is not ready', options);
|
|
97
155
|
}
|
|
156
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
157
|
+
const preflight = await (options.preflight ?? preflightCodexLifecycleEvidence)(repoRoot, {
|
|
158
|
+
sessionId: evidence.parentThreadId,
|
|
159
|
+
turnId: evidence.parentTurnId,
|
|
160
|
+
toolUseId: evidence.spawnToolUseId
|
|
161
|
+
});
|
|
162
|
+
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
|
|
163
|
+
const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
|
|
164
|
+
const contextTaskId = contextPath
|
|
165
|
+
? (JSON.parse(fs.readFileSync(contextPath, 'utf8')) as { taskId?: unknown }).taskId
|
|
166
|
+
: null;
|
|
167
|
+
if (contextPath && typeof contextTaskId !== 'string') {
|
|
168
|
+
throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
|
|
169
|
+
}
|
|
170
|
+
const controller = contextPath
|
|
171
|
+
? verifyCodexSandboxControllerContext(contextPath, contextTaskId as string, { repoRoot })
|
|
172
|
+
: null;
|
|
98
173
|
return activateMatchingOrchestrationDelegation('codex', {
|
|
99
174
|
nativeAgent: evidence.nativeAgent,
|
|
100
175
|
childId: evidence.childThreadId,
|
|
@@ -107,9 +182,17 @@ async function activateCodexOrchestrationDelegation(
|
|
|
107
182
|
? { reasoningEffortFallbackReason: evidence.reasoningEffortFallbackReason }
|
|
108
183
|
: {}),
|
|
109
184
|
hostEvidence: {
|
|
110
|
-
kind: 'codex-lifecycle-
|
|
185
|
+
kind: 'codex-lifecycle-v2',
|
|
111
186
|
hookDefinitionHash: evidence.hookDefinitionHash,
|
|
112
|
-
startRevision: record.revision
|
|
187
|
+
startRevision: record.revision,
|
|
188
|
+
...buildIdentity,
|
|
189
|
+
...preflight.hookProvenance,
|
|
190
|
+
capabilitySessionId: evidence.parentThreadId,
|
|
191
|
+
capabilityTurnId: evidence.parentTurnId,
|
|
192
|
+
spawnToolUseId: evidence.spawnToolUseId,
|
|
193
|
+
spawnObservedAt: record.spawnObservedAt ?? undefined,
|
|
194
|
+
controllerInstanceDigest: controller?.controllerInstanceDigest ?? null,
|
|
195
|
+
controlGeneration: controller?.controlGeneration ?? null
|
|
113
196
|
}
|
|
114
197
|
}, coreOptions(options));
|
|
115
198
|
} catch (error) {
|
|
@@ -149,6 +232,9 @@ async function sealCodexOrchestrationDelegation(
|
|
|
149
232
|
options: CodexBridgeOptions = {}
|
|
150
233
|
): Promise<OrchestrationResult> {
|
|
151
234
|
try {
|
|
235
|
+
if (!hasSealableOrchestrationDelegation('codex', childThreadId, coreOptions(options))) {
|
|
236
|
+
return bridgeFailure('ORCHESTRATION_DELEGATION_MISSING', 'No matching Codex delegation is active');
|
|
237
|
+
}
|
|
152
238
|
const store = requiredStore(options);
|
|
153
239
|
const existing = store.read(childThreadId);
|
|
154
240
|
if (!existing.consumer) {
|
|
@@ -169,7 +169,7 @@ function inplaceValidation(config: ReturnType<typeof loadConfig>, target: Return
|
|
|
169
169
|
const nonce = randomUUID();
|
|
170
170
|
const issuedAt = Date.now();
|
|
171
171
|
const lease: SandboxControlLease = {
|
|
172
|
-
version:
|
|
172
|
+
version: 2, generation: manifest.generation, nonce,
|
|
173
173
|
owner: { pid: process.pid, startTime }, issuedAt,
|
|
174
174
|
expiresAt: issuedAt + Math.min(options.timeoutMs + RECOVERY_GRACE_MS, MAX_TIMEOUT_MS + RECOVERY_GRACE_MS),
|
|
175
175
|
taskId: target.workspace.mode === 'task-bound' ? target.workspace.taskId : null,
|
package/lib/task/create.ts
CHANGED
|
@@ -231,6 +231,14 @@ function assertRealDirectory(directory: string, code: string): void {
|
|
|
231
231
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error(code);
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
function ensureRealDirectory(directory: string, code: string): void {
|
|
235
|
+
if (fs.existsSync(directory)) {
|
|
236
|
+
assertRealDirectory(directory, code);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
fs.mkdirSync(directory, { mode: 0o700 });
|
|
240
|
+
}
|
|
241
|
+
|
|
234
242
|
function withCreateLock<T>(repoRoot: string, workspaceRoot: string, operation: () => T): T {
|
|
235
243
|
const lockRoot = path.join(workspaceRoot, '.task-create.lock');
|
|
236
244
|
fs.mkdirSync(lockRoot, { recursive: true, mode: 0o700 });
|
|
@@ -289,8 +297,9 @@ function createLocalTask(value: unknown, options: LocalTaskCreateOptions): Local
|
|
|
289
297
|
const repoRoot = fs.realpathSync.native(options.repoRoot);
|
|
290
298
|
const workspaceRoot = path.join(repoRoot, '.agents', 'workspace');
|
|
291
299
|
const activeRoot = path.join(workspaceRoot, 'active');
|
|
292
|
-
|
|
293
|
-
|
|
300
|
+
ensureRealDirectory(path.join(repoRoot, '.agents'), 'TASK_CREATE_WORKSPACE_INVALID');
|
|
301
|
+
ensureRealDirectory(workspaceRoot, 'TASK_CREATE_WORKSPACE_INVALID');
|
|
302
|
+
ensureRealDirectory(activeRoot, 'TASK_CREATE_WORKSPACE_INVALID');
|
|
294
303
|
const project = readProject(repoRoot);
|
|
295
304
|
const canonical = canonicalTaskCreateCandidate(candidate);
|
|
296
305
|
const candidateDigest = sha256(canonical);
|
|
@@ -16,6 +16,7 @@ import { resolveTaskContext } from './resolve-ref.ts';
|
|
|
16
16
|
import { extractSection, extractSubSection, findSectionHeading } from './sections.ts';
|
|
17
17
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
18
18
|
import type { TaskMutation, TaskOperationSummary, TaskWriteOptions } from './write.ts';
|
|
19
|
+
import { allowsManualOverride } from './guard-override.ts';
|
|
19
20
|
|
|
20
21
|
type HumanDecisionRequest = {
|
|
21
22
|
taskRef?: string;
|
|
@@ -85,7 +86,8 @@ function applyHumanDecision(request: HumanDecisionRequest, options: TaskWriteOpt
|
|
|
85
86
|
if (!request.selector || !request.decision.trim()) return failed('DECISION_PAYLOAD_INVALID', 'selector and decision are required');
|
|
86
87
|
const resolved = resolveTaskContext(request.taskRef, { repoRoot: options.repoRoot });
|
|
87
88
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
88
|
-
|
|
89
|
+
const stateOverride = allowsManualOverride(options.manualOverride, 'decision-intent', 'TASK_STATE_MISMATCH');
|
|
90
|
+
if (resolved.state !== 'active' && !stateOverride) return failed('TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
89
91
|
let content: string;
|
|
90
92
|
let rows: LedgerRow[];
|
|
91
93
|
try { content = fs.readFileSync(resolved.taskMdPath, 'utf8'); rows = parseLedger(content); }
|
|
@@ -170,7 +172,7 @@ function applyHumanDecision(request: HumanDecisionRequest, options: TaskWriteOpt
|
|
|
170
172
|
kind: 'section', aliases: ACTIVITY_ALIASES, heading: activity.heading,
|
|
171
173
|
body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Human Decision', agent: 'human', note: `${row.id} decided → ${recordId}` })
|
|
172
174
|
});
|
|
173
|
-
const writeResult = writeTask({ taskRef: resolved.taskId, expectedState: 'active', mutations, dryRun: request.dryRun }, {
|
|
175
|
+
const writeResult = writeTask({ taskRef: resolved.taskId, expectedState: stateOverride ? resolved.state : 'active', mutations, dryRun: request.dryRun }, {
|
|
174
176
|
...options,
|
|
175
177
|
taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state },
|
|
176
178
|
metadataProvider: () => metadata
|
|
@@ -7,12 +7,41 @@ type DelegationRole = 'executor' | 'reviewer';
|
|
|
7
7
|
type DelegationStage = 'analysis' | 'review-analysis' | 'plan' | 'review-plan' | 'code' | 'review-code' | 'commit';
|
|
8
8
|
type DelegationStatus = 'prepared' | 'activated' | 'stage-completed' | 'sealed' | 'consumed' | 'aborted' | 'expired';
|
|
9
9
|
type DelegationHostEvidence = Readonly<{
|
|
10
|
-
kind: 'codex-lifecycle-v1';
|
|
10
|
+
kind: 'codex-lifecycle-v1' | 'codex-lifecycle-v2';
|
|
11
11
|
hookDefinitionHash: string;
|
|
12
12
|
startRevision: number;
|
|
13
13
|
stopRevision: number | null;
|
|
14
14
|
consumer: string | null;
|
|
15
15
|
consumedAt: string | null;
|
|
16
|
+
protocolVersion?: number;
|
|
17
|
+
packageVersion?: string;
|
|
18
|
+
internalExecutableBuildHash?: string;
|
|
19
|
+
lifecycleContractHash?: string;
|
|
20
|
+
hookSource?: 'project' | 'managed' | 'isolated-user';
|
|
21
|
+
hookSourcePathDigest?: string;
|
|
22
|
+
hookSourceHash?: string;
|
|
23
|
+
capabilitySessionId?: string;
|
|
24
|
+
capabilityTurnId?: string;
|
|
25
|
+
capabilityToolUseId?: string;
|
|
26
|
+
spawnToolUseId?: string;
|
|
27
|
+
spawnObservedAt?: string;
|
|
28
|
+
controllerInstanceDigest?: string | null;
|
|
29
|
+
controlGeneration?: string | null;
|
|
30
|
+
}>;
|
|
31
|
+
type DelegationLifecycleProvenance = Readonly<{
|
|
32
|
+
protocolVersion: number;
|
|
33
|
+
packageVersion: string;
|
|
34
|
+
internalExecutableBuildHash: string;
|
|
35
|
+
lifecycleContractHash: string;
|
|
36
|
+
hookDefinitionHash: string;
|
|
37
|
+
hookSource: 'project' | 'managed' | 'isolated-user';
|
|
38
|
+
hookSourcePathDigest: string;
|
|
39
|
+
hookSourceHash: string;
|
|
40
|
+
capabilitySessionId: string;
|
|
41
|
+
capabilityTurnId: string;
|
|
42
|
+
capabilityToolUseId: string;
|
|
43
|
+
controllerInstanceDigest: string | null;
|
|
44
|
+
controlGeneration: string | null;
|
|
16
45
|
}>;
|
|
17
46
|
|
|
18
47
|
type DelegationReceipt = Readonly<{
|
|
@@ -36,11 +65,19 @@ type DelegationReceipt = Readonly<{
|
|
|
36
65
|
agent: string | null;
|
|
37
66
|
status: DelegationStatus;
|
|
38
67
|
workspaceSnapshotScope?: 'task';
|
|
68
|
+
lifecycleProvenance?: DelegationLifecycleProvenance | null;
|
|
39
69
|
hostEvidence?: DelegationHostEvidence | null;
|
|
40
70
|
beforeFingerprint: string;
|
|
41
71
|
afterFingerprint: string | null;
|
|
42
72
|
changedPaths: readonly string[];
|
|
43
73
|
createdAt: string;
|
|
74
|
+
preparedMonotonicMs: number;
|
|
75
|
+
spawnDispatchMonotonicMs: number | null;
|
|
76
|
+
activationDeadlineMonotonicMs: number | null;
|
|
77
|
+
spawnDispatchedAt: string | null;
|
|
78
|
+
activationDeadlineAt: string | null;
|
|
79
|
+
startEvidenceMonotonicMs: number | null;
|
|
80
|
+
activatedMonotonicMs: number | null;
|
|
44
81
|
activatedAt: string | null;
|
|
45
82
|
sealedAt: string | null;
|
|
46
83
|
consumedAt: string | null;
|
|
@@ -63,10 +100,16 @@ function fail(code: string, message: string): ReceiptFailure {
|
|
|
63
100
|
return { ok: false, code, message };
|
|
64
101
|
}
|
|
65
102
|
|
|
103
|
+
// 空白串折叠为 null,保持"缺失即 null"的单一表示(Start/Stop 两处共用,避免任一入口把 " " 当成已观察到的值入账)
|
|
104
|
+
function foldBlankToNull(value: string | undefined): string | null {
|
|
105
|
+
return value?.trim() ? value : null;
|
|
106
|
+
}
|
|
107
|
+
|
|
66
108
|
function prepareDelegation(
|
|
67
|
-
input: Omit<DelegationReceipt, 'id' | 'requestedModel' | 'requestedReasoningEffort' | 'actualModel' | 'actualReasoningEffort' | 'modelFallbackReason' | 'reasoningEffortFallbackReason' | 'parentId' | 'childId' | 'spawnMode' | 'agent' | 'status' | 'hostEvidence' | 'afterFingerprint' | 'changedPaths' | 'createdAt' | 'activatedAt' | 'sealedAt' | 'consumedAt'> & Readonly<{ requestedModel: string; requestedReasoningEffort: string }>,
|
|
68
|
-
options: { id?: () => string; now?: () => string } = {}
|
|
109
|
+
input: Omit<DelegationReceipt, 'id' | 'requestedModel' | 'requestedReasoningEffort' | 'actualModel' | 'actualReasoningEffort' | 'modelFallbackReason' | 'reasoningEffortFallbackReason' | 'parentId' | 'childId' | 'spawnMode' | 'agent' | 'status' | 'hostEvidence' | 'afterFingerprint' | 'changedPaths' | 'createdAt' | 'preparedMonotonicMs' | 'spawnDispatchMonotonicMs' | 'activationDeadlineMonotonicMs' | 'spawnDispatchedAt' | 'activationDeadlineAt' | 'startEvidenceMonotonicMs' | 'activatedMonotonicMs' | 'activatedAt' | 'sealedAt' | 'consumedAt'> & Readonly<{ requestedModel: string; requestedReasoningEffort: string }>,
|
|
110
|
+
options: { id?: () => string; now?: () => string; monotonicNow?: () => number } = {}
|
|
69
111
|
): DelegationReceipt {
|
|
112
|
+
const monotonic = (options.monotonicNow ?? (() => Number(process.hrtime.bigint() / 1_000_000n)))();
|
|
70
113
|
return Object.freeze({
|
|
71
114
|
...input,
|
|
72
115
|
id: (options.id ?? randomUUID)(),
|
|
@@ -83,60 +126,136 @@ function prepareDelegation(
|
|
|
83
126
|
afterFingerprint: null,
|
|
84
127
|
changedPaths: Object.freeze([]),
|
|
85
128
|
createdAt: (options.now ?? (() => new Date().toISOString()))(),
|
|
129
|
+
preparedMonotonicMs: monotonic,
|
|
130
|
+
spawnDispatchMonotonicMs: null,
|
|
131
|
+
activationDeadlineMonotonicMs: null,
|
|
132
|
+
spawnDispatchedAt: null,
|
|
133
|
+
activationDeadlineAt: null,
|
|
134
|
+
startEvidenceMonotonicMs: null,
|
|
135
|
+
activatedMonotonicMs: null,
|
|
86
136
|
activatedAt: null,
|
|
87
137
|
sealedAt: null,
|
|
88
138
|
consumedAt: null
|
|
89
139
|
});
|
|
90
140
|
}
|
|
91
141
|
|
|
142
|
+
function dispatchDelegation(
|
|
143
|
+
receipt: DelegationReceipt,
|
|
144
|
+
options: { now?: () => string; monotonicNow?: () => number; activationWindowMs?: number } = {}
|
|
145
|
+
): ReceiptResult {
|
|
146
|
+
if (receipt.status !== 'prepared') {
|
|
147
|
+
return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected prepared`);
|
|
148
|
+
}
|
|
149
|
+
if (receipt.spawnDispatchMonotonicMs != null || receipt.spawnDispatchedAt != null) {
|
|
150
|
+
return fail('DELEGATION_DISPATCH_REPLAY', `delegation ${receipt.id} was already dispatched`);
|
|
151
|
+
}
|
|
152
|
+
const monotonic = (options.monotonicNow ?? (() => Number(process.hrtime.bigint() / 1_000_000n)))();
|
|
153
|
+
const dispatchedAt = (options.now ?? (() => new Date().toISOString()))();
|
|
154
|
+
const window = options.activationWindowMs ?? 15_000;
|
|
155
|
+
return { ok: true, receipt: Object.freeze({
|
|
156
|
+
...receipt,
|
|
157
|
+
spawnDispatchMonotonicMs: monotonic,
|
|
158
|
+
activationDeadlineMonotonicMs: monotonic + window,
|
|
159
|
+
spawnDispatchedAt: dispatchedAt,
|
|
160
|
+
activationDeadlineAt: new Date(Date.parse(dispatchedAt) + window).toISOString()
|
|
161
|
+
}) };
|
|
162
|
+
}
|
|
163
|
+
|
|
92
164
|
function activateDelegation(
|
|
93
165
|
receipt: DelegationReceipt,
|
|
94
166
|
event: Readonly<{
|
|
95
167
|
nativeAgent: string;
|
|
96
168
|
childId: string;
|
|
97
169
|
parentId: string;
|
|
98
|
-
spawnMode
|
|
170
|
+
spawnMode?: string;
|
|
99
171
|
actualModel?: string;
|
|
100
172
|
actualReasoningEffort?: string;
|
|
101
173
|
modelFallbackReason?: string;
|
|
102
174
|
reasoningEffortFallbackReason?: string;
|
|
103
175
|
hostEvidence?: Readonly<{
|
|
104
|
-
kind: 'codex-lifecycle-v1';
|
|
176
|
+
kind: 'codex-lifecycle-v1' | 'codex-lifecycle-v2';
|
|
105
177
|
hookDefinitionHash: string;
|
|
106
178
|
startRevision: number;
|
|
179
|
+
protocolVersion?: number;
|
|
180
|
+
packageVersion?: string;
|
|
181
|
+
internalExecutableBuildHash?: string;
|
|
182
|
+
lifecycleContractHash?: string;
|
|
183
|
+
hookSource?: 'project' | 'managed' | 'isolated-user';
|
|
184
|
+
hookSourcePathDigest?: string;
|
|
185
|
+
hookSourceHash?: string;
|
|
186
|
+
capabilitySessionId?: string;
|
|
187
|
+
capabilityTurnId?: string;
|
|
188
|
+
capabilityToolUseId?: string;
|
|
189
|
+
spawnToolUseId?: string;
|
|
190
|
+
spawnObservedAt?: string;
|
|
191
|
+
controllerInstanceDigest?: string | null;
|
|
192
|
+
controlGeneration?: string | null;
|
|
107
193
|
}>;
|
|
108
194
|
}>,
|
|
109
|
-
options: { now?: () => string } = {}
|
|
195
|
+
options: { now?: () => string; monotonicNow?: () => number } = {}
|
|
110
196
|
): ReceiptResult {
|
|
111
197
|
const managedRole = managedDelegationRole(event.nativeAgent);
|
|
112
198
|
if (!managedRole) return fail('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
|
|
113
199
|
if (receipt.status !== 'prepared') return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected prepared`);
|
|
200
|
+
if (
|
|
201
|
+
receipt.spawnDispatchMonotonicMs == null
|
|
202
|
+
|| receipt.spawnDispatchedAt == null
|
|
203
|
+
|| receipt.activationDeadlineAt == null
|
|
204
|
+
|| receipt.activationDeadlineMonotonicMs == null
|
|
205
|
+
|| !Number.isFinite(Date.parse(receipt.spawnDispatchedAt))
|
|
206
|
+
|| !Number.isFinite(Date.parse(receipt.activationDeadlineAt))
|
|
207
|
+
) {
|
|
208
|
+
return fail('DELEGATION_NOT_DISPATCHED', `delegation ${receipt.id} has not reached the native spawn dispatch boundary`);
|
|
209
|
+
}
|
|
210
|
+
const monotonic = (options.monotonicNow ?? (() => Number(process.hrtime.bigint() / 1_000_000n)))();
|
|
211
|
+
const wallNow = Date.parse((options.now ?? (() => new Date().toISOString()))());
|
|
212
|
+
if (wallNow > Date.parse(receipt.activationDeadlineAt)) {
|
|
213
|
+
return fail('DELEGATION_ACTIVATION_TIMEOUT', `delegation ${receipt.id} activation evidence arrived after its deadline`);
|
|
214
|
+
}
|
|
114
215
|
if (managedRole !== receipt.role) return fail('DELEGATION_ROLE_MISMATCH', `managed role ${managedRole} does not match ${receipt.role}`);
|
|
115
216
|
if (!event.parentId || (receipt.parentId !== null && event.parentId !== receipt.parentId) || !event.childId || event.childId === event.parentId) {
|
|
116
217
|
return fail('DELEGATION_IDENTITY_INVALID', 'native parent/child identity does not match the prepared delegation');
|
|
117
218
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
219
|
+
// spawnMode 的必需性按 client 判断:claude-code 宿主结构上不提供该字段,跳过;其余 client 维持硬校验
|
|
220
|
+
if (receipt.client !== 'claude-code' && event.spawnMode !== 'fresh') {
|
|
221
|
+
return fail('DELEGATION_FORK_FORBIDDEN', `spawn mode '${event.spawnMode}' is not fresh`);
|
|
222
|
+
}
|
|
223
|
+
// model/effort 维度:claude-code 如实记录、不做门禁;其余 client 维持现状硬校验(HDR-2)
|
|
224
|
+
const actualModel = foldBlankToNull(event.actualModel);
|
|
225
|
+
const actualReasoningEffort = foldBlankToNull(event.actualReasoningEffort);
|
|
226
|
+
if (receipt.client !== 'claude-code') {
|
|
227
|
+
if (!actualModel || actualModel.trim() !== actualModel) {
|
|
228
|
+
return fail('DELEGATION_MODEL_IDENTITY_MISSING', 'native start event must provide a non-empty actual model identity');
|
|
229
|
+
}
|
|
122
230
|
}
|
|
123
|
-
if (actualModel !==
|
|
124
|
-
|
|
231
|
+
if (actualModel !== null && actualModel !== receipt.requestedModel) {
|
|
232
|
+
if (receipt.client !== 'claude-code' && (!event.modelFallbackReason || event.modelFallbackReason.trim() === '')) {
|
|
233
|
+
return fail('DELEGATION_MODEL_FALLBACK_UNRECORDED', 'actual model differs from requested model without a fallback reason');
|
|
234
|
+
}
|
|
125
235
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
236
|
+
if (receipt.client !== 'claude-code') {
|
|
237
|
+
if (!actualReasoningEffort || actualReasoningEffort.trim() !== actualReasoningEffort) {
|
|
238
|
+
return fail('DELEGATION_REASONING_EFFORT_MISSING', 'native start event must provide a non-empty actual reasoning effort');
|
|
239
|
+
}
|
|
129
240
|
}
|
|
130
241
|
if (
|
|
131
|
-
|
|
132
|
-
&&
|
|
133
|
-
)
|
|
134
|
-
|
|
242
|
+
receipt.client === 'codex'
|
|
243
|
+
&& event.hostEvidence?.kind !== 'codex-lifecycle-v2'
|
|
244
|
+
) return fail('DELEGATION_HOST_EVIDENCE_REQUIRED', 'Codex activation requires trusted lifecycle-v2 host evidence');
|
|
245
|
+
if (actualReasoningEffort !== null && actualReasoningEffort !== receipt.requestedReasoningEffort) {
|
|
246
|
+
if (receipt.client !== 'claude-code' && (!event.reasoningEffortFallbackReason || event.reasoningEffortFallbackReason.trim() === '')) {
|
|
247
|
+
return fail('DELEGATION_REASONING_EFFORT_FALLBACK_UNRECORDED', 'actual reasoning effort differs from requested effort without a fallback reason');
|
|
248
|
+
}
|
|
135
249
|
}
|
|
136
|
-
|
|
250
|
+
// "无关 fallback 理由"检查对所有 client 保持不变——防止编造理由的红线,不属于 HDR-2 放宽范围
|
|
251
|
+
// actualModel/actualReasoningEffort 为 null(未观察到)时同样没有"实际不同"这回事,理由一样是无关的
|
|
252
|
+
if ((actualModel === null || actualModel === receipt.requestedModel) && event.modelFallbackReason) {
|
|
137
253
|
return fail('DELEGATION_MODEL_FALLBACK_INVALID', 'model fallback reason is only valid when actual model differs');
|
|
138
254
|
}
|
|
139
|
-
if (
|
|
255
|
+
if (
|
|
256
|
+
(actualReasoningEffort === null || actualReasoningEffort === receipt.requestedReasoningEffort)
|
|
257
|
+
&& event.reasoningEffortFallbackReason
|
|
258
|
+
) {
|
|
140
259
|
return fail('DELEGATION_REASONING_EFFORT_FALLBACK_INVALID', 'reasoning-effort fallback reason is only valid when actual effort differs');
|
|
141
260
|
}
|
|
142
261
|
if (
|
|
@@ -148,12 +267,39 @@ function activateDelegation(
|
|
|
148
267
|
|| event.hostEvidence.startRevision < 1
|
|
149
268
|
)
|
|
150
269
|
) return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex start evidence reference is invalid');
|
|
270
|
+
if (event.hostEvidence?.kind === 'codex-lifecycle-v2') {
|
|
271
|
+
const expected = receipt.lifecycleProvenance;
|
|
272
|
+
const spawnObservedAt = Date.parse(event.hostEvidence.spawnObservedAt ?? '');
|
|
273
|
+
const spawnDispatchedAt = Date.parse(receipt.spawnDispatchedAt);
|
|
274
|
+
const activationDeadlineAt = Date.parse(receipt.activationDeadlineAt);
|
|
275
|
+
if (
|
|
276
|
+
!expected
|
|
277
|
+
|| event.hostEvidence.protocolVersion !== expected.protocolVersion
|
|
278
|
+
|| event.hostEvidence.packageVersion !== expected.packageVersion
|
|
279
|
+
|| event.hostEvidence.internalExecutableBuildHash !== expected.internalExecutableBuildHash
|
|
280
|
+
|| event.hostEvidence.lifecycleContractHash !== expected.lifecycleContractHash
|
|
281
|
+
|| event.hostEvidence.hookDefinitionHash !== expected.hookDefinitionHash
|
|
282
|
+
|| event.hostEvidence.hookSource !== expected.hookSource
|
|
283
|
+
|| event.hostEvidence.hookSourcePathDigest !== expected.hookSourcePathDigest
|
|
284
|
+
|| event.hostEvidence.hookSourceHash !== expected.hookSourceHash
|
|
285
|
+
|| event.hostEvidence.capabilitySessionId !== expected.capabilitySessionId
|
|
286
|
+
|| event.hostEvidence.capabilityTurnId !== expected.capabilityTurnId
|
|
287
|
+
|| !event.hostEvidence.spawnToolUseId?.trim()
|
|
288
|
+
|| event.hostEvidence.spawnToolUseId === expected.capabilityToolUseId
|
|
289
|
+
|| !Number.isFinite(spawnObservedAt)
|
|
290
|
+
|| spawnObservedAt < spawnDispatchedAt
|
|
291
|
+
|| spawnObservedAt > activationDeadlineAt
|
|
292
|
+
|| event.parentId !== expected.capabilitySessionId
|
|
293
|
+
|| (event.hostEvidence.controllerInstanceDigest ?? null) !== expected.controllerInstanceDigest
|
|
294
|
+
|| (event.hostEvidence.controlGeneration ?? null) !== expected.controlGeneration
|
|
295
|
+
) return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex lifecycle provenance does not match the prepared receipt');
|
|
296
|
+
}
|
|
151
297
|
return { ok: true, receipt: Object.freeze({
|
|
152
298
|
...receipt,
|
|
153
299
|
status: 'activated',
|
|
154
300
|
parentId: event.parentId,
|
|
155
301
|
childId: event.childId,
|
|
156
|
-
spawnMode: event.spawnMode,
|
|
302
|
+
spawnMode: event.spawnMode ?? null,
|
|
157
303
|
actualModel,
|
|
158
304
|
actualReasoningEffort,
|
|
159
305
|
modelFallbackReason: event.modelFallbackReason ?? null,
|
|
@@ -164,10 +310,22 @@ function activateDelegation(
|
|
|
164
310
|
consumer: null,
|
|
165
311
|
consumedAt: null
|
|
166
312
|
}) : receipt.hostEvidence ?? null,
|
|
167
|
-
|
|
313
|
+
startEvidenceMonotonicMs: monotonic,
|
|
314
|
+
activatedMonotonicMs: (options.monotonicNow ?? (() => Number(process.hrtime.bigint() / 1_000_000n)))(),
|
|
315
|
+
activatedAt: new Date(wallNow).toISOString()
|
|
168
316
|
}) };
|
|
169
317
|
}
|
|
170
318
|
|
|
319
|
+
function abortPreparedDelegation(receipt: DelegationReceipt): ReceiptResult {
|
|
320
|
+
if (receipt.status !== 'prepared') {
|
|
321
|
+
return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected prepared`);
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
ok: true,
|
|
325
|
+
receipt: Object.freeze({ ...receipt, status: 'aborted' as const })
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
171
329
|
function completeDelegationStage(
|
|
172
330
|
receipt: DelegationReceipt,
|
|
173
331
|
event: Readonly<{ stage: DelegationStage; round: number; artifact: string; agent: string }>
|
|
@@ -189,11 +347,18 @@ function sealDelegation(
|
|
|
189
347
|
exitCode: number;
|
|
190
348
|
afterFingerprint: string;
|
|
191
349
|
changedPaths: readonly string[];
|
|
350
|
+
actualModel?: string;
|
|
351
|
+
actualReasoningEffort?: string;
|
|
352
|
+
modelFallbackReason?: string;
|
|
353
|
+
reasoningEffortFallbackReason?: string;
|
|
192
354
|
hostEvidence?: Readonly<{ stopRevision: number; consumer: string; consumedAt: string }>;
|
|
193
355
|
}>,
|
|
194
|
-
options: { now?: () => string } = {}
|
|
356
|
+
options: { now?: () => string; requireHostEvidence?: boolean } = {}
|
|
195
357
|
): ReceiptResult {
|
|
196
358
|
if (receipt.status !== 'stage-completed') return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected stage-completed`);
|
|
359
|
+
if (receipt.client === 'codex' && options.requireHostEvidence !== false && !event.hostEvidence) {
|
|
360
|
+
return fail('DELEGATION_HOST_EVIDENCE_REQUIRED', 'Codex sealing requires consumed lifecycle-v2 host evidence');
|
|
361
|
+
}
|
|
197
362
|
if (event.childId !== receipt.childId || event.exitCode !== 0) return fail('DELEGATION_STOP_INVALID', 'native stop identity or exit status is invalid');
|
|
198
363
|
if (receipt.role === 'reviewer') {
|
|
199
364
|
const taskRoot = `.agents/workspace/active/${receipt.taskId}/`;
|
|
@@ -219,6 +384,10 @@ function sealDelegation(
|
|
|
219
384
|
status: 'sealed',
|
|
220
385
|
afterFingerprint: event.afterFingerprint,
|
|
221
386
|
changedPaths: Object.freeze([...event.changedPaths]),
|
|
387
|
+
actualModel: receipt.actualModel ?? foldBlankToNull(event.actualModel),
|
|
388
|
+
actualReasoningEffort: receipt.actualReasoningEffort ?? foldBlankToNull(event.actualReasoningEffort),
|
|
389
|
+
modelFallbackReason: receipt.modelFallbackReason ?? event.modelFallbackReason ?? null,
|
|
390
|
+
reasoningEffortFallbackReason: receipt.reasoningEffortFallbackReason ?? event.reasoningEffortFallbackReason ?? null,
|
|
222
391
|
hostEvidence: event.hostEvidence && receipt.hostEvidence ? Object.freeze({
|
|
223
392
|
...receipt.hostEvidence,
|
|
224
393
|
...event.hostEvidence
|
|
@@ -239,10 +408,21 @@ function consumeDelegation(receipt: DelegationReceipt, options: { now?: () => st
|
|
|
239
408
|
|
|
240
409
|
export {
|
|
241
410
|
activateDelegation,
|
|
411
|
+
abortPreparedDelegation,
|
|
242
412
|
completeDelegationStage,
|
|
243
413
|
consumeDelegation,
|
|
414
|
+
dispatchDelegation,
|
|
415
|
+
foldBlankToNull,
|
|
244
416
|
managedDelegationRole,
|
|
245
417
|
prepareDelegation,
|
|
246
418
|
sealDelegation
|
|
247
419
|
};
|
|
248
|
-
export type {
|
|
420
|
+
export type {
|
|
421
|
+
DelegationHostEvidence,
|
|
422
|
+
DelegationLifecycleProvenance,
|
|
423
|
+
DelegationReceipt,
|
|
424
|
+
DelegationRole,
|
|
425
|
+
DelegationStage,
|
|
426
|
+
DelegationStatus,
|
|
427
|
+
ReceiptResult
|
|
428
|
+
};
|