@fitlab-ai/agent-infra 0.9.7 → 0.9.9
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/bin/internal-cli.js +11 -1
- package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +60 -22
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +8 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +175 -11
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +147 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +86 -69
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +2 -1
- package/dist/lib/decide.js +68 -14
- package/dist/lib/git/workflow.js +10 -1
- package/dist/lib/init.js +1 -1
- package/dist/lib/internal/codex-lifecycle.js +19 -5
- package/dist/lib/internal/codex-sandbox-controller.js +6 -13
- package/dist/lib/internal/git-workflow.js +5 -7
- package/dist/lib/internal/platform-pr.js +20 -7
- package/dist/lib/internal/release-workflow.js +3 -4
- package/dist/lib/internal/sandbox-control.js +79 -2
- package/dist/lib/internal/task-activity.js +57 -7
- package/dist/lib/internal/task-event.js +57 -4
- package/dist/lib/internal/task-finalization.js +54 -0
- 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 +161 -288
- 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/pr-summary.js +53 -13
- package/dist/lib/platform/pull-requests.js +205 -41
- package/dist/lib/runtime/agent-runtime.js +56 -0
- package/dist/lib/sandbox/commands/create.js +389 -311
- package/dist/lib/sandbox/commands/rm.js +316 -55
- package/dist/lib/sandbox/control/client.js +190 -6
- package/dist/lib/sandbox/control/container-identity.js +1 -1
- package/dist/lib/sandbox/control/controller-registration.js +273 -0
- package/dist/lib/sandbox/control/executor.js +145 -5
- package/dist/lib/sandbox/control/lifecycle.js +395 -73
- package/dist/lib/sandbox/control/protocol.js +67 -6
- package/dist/lib/sandbox/control/state.js +27 -11
- package/dist/lib/sandbox/engine.js +14 -7
- package/dist/lib/sandbox/index.js +5 -3
- package/dist/lib/sandbox/managed-fs.js +5 -0
- package/dist/lib/sandbox/mounts.js +20 -5
- package/dist/lib/sandbox/recovery.js +78 -10
- package/dist/lib/sandbox/task-resolver.js +50 -6
- package/dist/lib/sandbox/workspace-identity.js +27 -0
- package/dist/lib/sandbox/workspace-view.js +62 -40
- package/dist/lib/sandbox/worktree-safety.js +215 -2
- package/dist/lib/server/process-state.js +51 -1
- package/dist/lib/task/activity-intent.js +4 -1
- package/dist/lib/task/codex-orchestration.js +158 -33
- package/dist/lib/task/commands/status.js +59 -1
- package/dist/lib/task/commit-operation.js +335 -0
- package/dist/lib/task/commit-policy.js +18 -0
- package/dist/lib/task/decision-intents.js +4 -2
- package/dist/lib/task/delegation-receipts.js +279 -17
- package/dist/lib/task/events.js +9 -5
- package/dist/lib/task/finalization.js +567 -0
- 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/lifecycle.js +69 -14
- package/dist/lib/task/operation-outcome.js +33 -0
- package/dist/lib/task/orchestration.js +199 -719
- package/dist/lib/task/review-finalization.js +3 -2
- package/dist/lib/task/short-id.js +59 -1
- package/dist/lib/task/task-execution-lock.js +6 -1
- package/dist/lib/task/verification-engine.js +138 -59
- package/dist/lib/task/verification.js +15 -2
- package/dist/lib/task/workflow-warning-intents.js +54 -3
- package/dist/lib/task/write.js +2 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/claude-code.ts +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +63 -20
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +12 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +278 -13
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +180 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +112 -93
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +2 -1
- package/lib/decide.ts +59 -14
- package/lib/git/workflow.ts +11 -2
- package/lib/init.ts +1 -1
- package/lib/internal/codex-lifecycle.ts +18 -5
- package/lib/internal/codex-sandbox-controller.ts +6 -12
- package/lib/internal/git-workflow.ts +5 -6
- package/lib/internal/platform-pr.ts +14 -7
- package/lib/internal/release-workflow.ts +3 -4
- package/lib/internal/sandbox-control.ts +92 -2
- package/lib/internal/task-activity.ts +46 -6
- package/lib/internal/task-event.ts +53 -4
- package/lib/internal/task-finalization.ts +68 -0
- package/lib/internal/task-ledger.ts +48 -4
- package/lib/internal/task-lifecycle.ts +56 -12
- package/lib/internal/task-orchestration.ts +32 -107
- 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/pr-summary.ts +65 -14
- package/lib/platform/pull-requests.ts +211 -41
- package/lib/runtime/agent-runtime.ts +76 -0
- package/lib/sandbox/commands/create.ts +88 -4
- package/lib/sandbox/commands/rm.ts +413 -61
- package/lib/sandbox/config.ts +2 -1
- package/lib/sandbox/control/client.ts +255 -6
- package/lib/sandbox/control/container-identity.ts +3 -3
- package/lib/sandbox/control/controller-registration.ts +347 -0
- package/lib/sandbox/control/executor.ts +172 -6
- package/lib/sandbox/control/lifecycle.ts +465 -77
- package/lib/sandbox/control/protocol.ts +85 -9
- package/lib/sandbox/control/state.ts +35 -18
- package/lib/sandbox/engine.ts +20 -9
- package/lib/sandbox/index.ts +5 -3
- package/lib/sandbox/managed-fs.ts +5 -0
- package/lib/sandbox/mounts.ts +23 -5
- package/lib/sandbox/recovery.ts +90 -9
- package/lib/sandbox/task-resolver.ts +58 -7
- package/lib/sandbox/workspace-identity.ts +50 -2
- package/lib/sandbox/workspace-view.ts +73 -43
- package/lib/sandbox/worktree-safety.ts +236 -2
- package/lib/server/process-state.ts +48 -1
- package/lib/task/activity-intent.ts +4 -2
- package/lib/task/codex-orchestration.ts +167 -33
- package/lib/task/commands/status.ts +78 -1
- package/lib/task/commit-operation.ts +430 -0
- package/lib/task/commit-policy.ts +30 -0
- package/lib/task/decision-intents.ts +4 -2
- package/lib/task/delegation-receipts.ts +304 -19
- package/lib/task/events.ts +10 -5
- package/lib/task/finalization.ts +719 -0
- 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/lifecycle.ts +71 -14
- package/lib/task/operation-outcome.ts +48 -0
- package/lib/task/orchestration.ts +239 -922
- package/lib/task/review-finalization.ts +6 -2
- package/lib/task/short-id.ts +65 -1
- package/lib/task/task-execution-lock.ts +10 -1
- package/lib/task/verification-engine.ts +145 -64
- package/lib/task/verification.ts +18 -3
- package/lib/task/workflow-warning-intents.ts +61 -4
- package/lib/task/write.ts +4 -1
- package/package.json +2 -2
- package/templates/.agents/QUICKSTART.en.md +2 -2
- package/templates/.agents/QUICKSTART.zh-CN.md +2 -2
- package/templates/.agents/hooks/lifecycle-delegation.js +9 -24
- package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -3
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -3
- package/templates/.agents/rules/task-management.en.md +2 -2
- package/templates/.agents/rules/task-management.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/commit/SKILL.en.md +41 -125
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
- package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
- package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +3 -1
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +3 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
- package/templates/.codex/hooks.json +4 -4
- package/dist/lib/task/commit-finalization.js +0 -305
- package/dist/lib/task/commit-intent.js +0 -165
- package/lib/task/commit-finalization.ts +0 -419
- package/lib/task/commit-intent.ts +0 -230
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
|
|
3
|
+
import { isAgentClientId } from '../agent-clients/types.ts';
|
|
3
4
|
import type { AgentClientId } from '../agent-clients/types.ts';
|
|
4
5
|
import { normalizeAgentToken } from '../agent-clients/tokens.ts';
|
|
5
6
|
|
|
@@ -87,6 +88,261 @@ type ReceiptFailure = Readonly<{ ok: false; code: string; message: string; recei
|
|
|
87
88
|
type ReceiptSuccess = Readonly<{ ok: true; receipt: DelegationReceipt; code?: never; message?: never }>;
|
|
88
89
|
type ReceiptResult = ReceiptSuccess | ReceiptFailure;
|
|
89
90
|
|
|
91
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
92
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function hasExactKeys(
|
|
96
|
+
value: unknown,
|
|
97
|
+
required: readonly string[],
|
|
98
|
+
optional: readonly string[] = []
|
|
99
|
+
): value is Record<string, unknown> {
|
|
100
|
+
if (!isRecord(value)) return false;
|
|
101
|
+
const keys = Object.keys(value);
|
|
102
|
+
const allowed = new Set([...required, ...optional]);
|
|
103
|
+
return required.every((key) => Object.prototype.hasOwnProperty.call(value, key))
|
|
104
|
+
&& keys.every((key) => allowed.has(key));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function exactText(value: unknown): value is string {
|
|
108
|
+
return typeof value === 'string' && value.length > 0 && value.trim() === value;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function nullableText(value: unknown): value is string | null {
|
|
112
|
+
return value === null || exactText(value);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function nullableSafeInteger(value: unknown): value is number | null {
|
|
116
|
+
return value === null || (Number.isSafeInteger(value) && (value as number) >= 0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const LIFECYCLE_PROVENANCE_KEYS = [
|
|
120
|
+
'protocolVersion', 'packageVersion', 'internalExecutableBuildHash', 'lifecycleContractHash',
|
|
121
|
+
'hookDefinitionHash', 'hookSource', 'hookSourcePathDigest', 'hookSourceHash',
|
|
122
|
+
'capabilitySessionId', 'capabilityTurnId', 'capabilityToolUseId',
|
|
123
|
+
'controllerInstanceDigest', 'controlGeneration'
|
|
124
|
+
] as const;
|
|
125
|
+
|
|
126
|
+
function isDelegationLifecycleProvenance(value: unknown): value is DelegationLifecycleProvenance {
|
|
127
|
+
if (!hasExactKeys(value, LIFECYCLE_PROVENANCE_KEYS)) return false;
|
|
128
|
+
return Number.isSafeInteger(value.protocolVersion)
|
|
129
|
+
&& (value.protocolVersion as number) > 0
|
|
130
|
+
&& exactText(value.packageVersion)
|
|
131
|
+
&& exactText(value.internalExecutableBuildHash)
|
|
132
|
+
&& exactText(value.lifecycleContractHash)
|
|
133
|
+
&& exactText(value.hookDefinitionHash)
|
|
134
|
+
&& ['project', 'managed', 'isolated-user'].includes(value.hookSource as string)
|
|
135
|
+
&& exactText(value.hookSourcePathDigest)
|
|
136
|
+
&& exactText(value.hookSourceHash)
|
|
137
|
+
&& exactText(value.capabilitySessionId)
|
|
138
|
+
&& exactText(value.capabilityTurnId)
|
|
139
|
+
&& exactText(value.capabilityToolUseId)
|
|
140
|
+
&& nullableText(value.controllerInstanceDigest)
|
|
141
|
+
&& nullableText(value.controlGeneration);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const HOST_EVIDENCE_REQUIRED_KEYS = [
|
|
145
|
+
'kind', 'hookDefinitionHash', 'startRevision', 'stopRevision', 'consumer', 'consumedAt'
|
|
146
|
+
] as const;
|
|
147
|
+
const HOST_EVIDENCE_OPTIONAL_KEYS = [
|
|
148
|
+
'protocolVersion', 'packageVersion', 'internalExecutableBuildHash', 'lifecycleContractHash',
|
|
149
|
+
'hookSource', 'hookSourcePathDigest', 'hookSourceHash', 'capabilitySessionId',
|
|
150
|
+
'capabilityTurnId', 'capabilityToolUseId', 'spawnToolUseId', 'spawnObservedAt',
|
|
151
|
+
'controllerInstanceDigest', 'controlGeneration'
|
|
152
|
+
] as const;
|
|
153
|
+
|
|
154
|
+
function isDelegationHostEvidence(value: unknown): value is DelegationHostEvidence {
|
|
155
|
+
if (!hasExactKeys(value, HOST_EVIDENCE_REQUIRED_KEYS, HOST_EVIDENCE_OPTIONAL_KEYS)) return false;
|
|
156
|
+
if (!['codex-lifecycle-v1', 'codex-lifecycle-v2'].includes(value.kind as string)) return false;
|
|
157
|
+
if (!exactText(value.hookDefinitionHash)
|
|
158
|
+
|| !Number.isSafeInteger(value.startRevision) || (value.startRevision as number) < 1
|
|
159
|
+
|| !nullableSafeInteger(value.stopRevision)
|
|
160
|
+
|| !nullableText(value.consumer)
|
|
161
|
+
|| !nullableText(value.consumedAt)) return false;
|
|
162
|
+
if (value.kind === 'codex-lifecycle-v1') return true;
|
|
163
|
+
return Number.isSafeInteger(value.protocolVersion)
|
|
164
|
+
&& (value.protocolVersion as number) > 0
|
|
165
|
+
&& exactText(value.packageVersion)
|
|
166
|
+
&& exactText(value.internalExecutableBuildHash)
|
|
167
|
+
&& exactText(value.lifecycleContractHash)
|
|
168
|
+
&& ['project', 'managed', 'isolated-user'].includes(value.hookSource as string)
|
|
169
|
+
&& exactText(value.hookSourcePathDigest)
|
|
170
|
+
&& exactText(value.hookSourceHash)
|
|
171
|
+
&& exactText(value.capabilitySessionId)
|
|
172
|
+
&& exactText(value.capabilityTurnId)
|
|
173
|
+
&& exactText(value.spawnToolUseId)
|
|
174
|
+
&& exactText(value.spawnObservedAt)
|
|
175
|
+
&& nullableText(value.controllerInstanceDigest)
|
|
176
|
+
&& nullableText(value.controlGeneration);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function hasCurrentCodexEvidence(receipt: DelegationReceipt): boolean {
|
|
180
|
+
const provenance = receipt.lifecycleProvenance;
|
|
181
|
+
if (!provenance) return false;
|
|
182
|
+
const activated = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status);
|
|
183
|
+
if (!activated) return receipt.hostEvidence === null;
|
|
184
|
+
const host = receipt.hostEvidence;
|
|
185
|
+
if (
|
|
186
|
+
host?.kind !== 'codex-lifecycle-v2'
|
|
187
|
+
|| host.protocolVersion !== provenance.protocolVersion
|
|
188
|
+
|| host.packageVersion !== provenance.packageVersion
|
|
189
|
+
|| host.internalExecutableBuildHash !== provenance.internalExecutableBuildHash
|
|
190
|
+
|| host.lifecycleContractHash !== provenance.lifecycleContractHash
|
|
191
|
+
|| host.hookDefinitionHash !== provenance.hookDefinitionHash
|
|
192
|
+
|| host.hookSource !== provenance.hookSource
|
|
193
|
+
|| host.hookSourcePathDigest !== provenance.hookSourcePathDigest
|
|
194
|
+
|| host.hookSourceHash !== provenance.hookSourceHash
|
|
195
|
+
|| host.capabilitySessionId !== provenance.capabilitySessionId
|
|
196
|
+
|| host.capabilityTurnId !== provenance.capabilityTurnId
|
|
197
|
+
|| host.controllerInstanceDigest !== provenance.controllerInstanceDigest
|
|
198
|
+
|| host.controlGeneration !== provenance.controlGeneration
|
|
199
|
+
|| receipt.parentId !== provenance.capabilitySessionId
|
|
200
|
+
|| host.spawnToolUseId === provenance.capabilityToolUseId
|
|
201
|
+
) return false;
|
|
202
|
+
const spawnObservedAt = Date.parse(host.spawnObservedAt ?? '');
|
|
203
|
+
const spawnDispatchedAt = Date.parse(receipt.spawnDispatchedAt ?? '');
|
|
204
|
+
const activationDeadlineAt = Date.parse(receipt.activationDeadlineAt ?? '');
|
|
205
|
+
if (
|
|
206
|
+
!Number.isFinite(spawnObservedAt)
|
|
207
|
+
|| !Number.isFinite(spawnDispatchedAt)
|
|
208
|
+
|| !Number.isFinite(activationDeadlineAt)
|
|
209
|
+
|| spawnObservedAt < spawnDispatchedAt
|
|
210
|
+
|| spawnObservedAt > activationDeadlineAt
|
|
211
|
+
) return false;
|
|
212
|
+
if (['sealed', 'consumed'].includes(receipt.status)) {
|
|
213
|
+
return Number.isSafeInteger(host.stopRevision)
|
|
214
|
+
&& (host.stopRevision as number) > host.startRevision
|
|
215
|
+
&& host.consumer === receipt.id
|
|
216
|
+
&& exactText(host.consumedAt);
|
|
217
|
+
}
|
|
218
|
+
return host.stopRevision === null && host.consumer === null && host.consumedAt === null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const RECEIPT_KEYS = [
|
|
222
|
+
'id', 'taskId', 'runId', 'role', 'stage', 'round', 'artifact', 'client',
|
|
223
|
+
'requestedModel', 'requestedReasoningEffort', 'actualModel', 'actualReasoningEffort',
|
|
224
|
+
'modelFallbackReason', 'reasoningEffortFallbackReason', 'parentId', 'childId',
|
|
225
|
+
'spawnMode', 'agent', 'status', 'workspaceSnapshotScope', 'lifecycleProvenance',
|
|
226
|
+
'hostEvidence', 'beforeFingerprint', 'afterFingerprint', 'changedPaths', 'createdAt',
|
|
227
|
+
'preparedMonotonicMs', 'spawnDispatchMonotonicMs', 'activationDeadlineMonotonicMs',
|
|
228
|
+
'spawnDispatchedAt', 'activationDeadlineAt', 'startEvidenceMonotonicMs',
|
|
229
|
+
'activatedMonotonicMs', 'activatedAt', 'sealedAt', 'consumedAt'
|
|
230
|
+
] as const;
|
|
231
|
+
|
|
232
|
+
function hasStatusBoundEvidence(receipt: DelegationReceipt): boolean {
|
|
233
|
+
const dispatchFields = [
|
|
234
|
+
receipt.spawnDispatchMonotonicMs,
|
|
235
|
+
receipt.activationDeadlineMonotonicMs,
|
|
236
|
+
receipt.spawnDispatchedAt,
|
|
237
|
+
receipt.activationDeadlineAt
|
|
238
|
+
];
|
|
239
|
+
const dispatchEmpty = dispatchFields.every((field) => field === null);
|
|
240
|
+
const dispatchComplete = dispatchFields.every((field) => field !== null);
|
|
241
|
+
if (!dispatchEmpty && !dispatchComplete) return false;
|
|
242
|
+
|
|
243
|
+
const beforeActivation = ['prepared', 'aborted', 'expired'].includes(receipt.status);
|
|
244
|
+
if (beforeActivation) {
|
|
245
|
+
return receipt.parentId === null
|
|
246
|
+
&& receipt.childId === null
|
|
247
|
+
&& receipt.spawnMode === null
|
|
248
|
+
&& receipt.actualModel === null
|
|
249
|
+
&& receipt.actualReasoningEffort === null
|
|
250
|
+
&& receipt.modelFallbackReason === null
|
|
251
|
+
&& receipt.reasoningEffortFallbackReason === null
|
|
252
|
+
&& receipt.agent === null
|
|
253
|
+
&& receipt.hostEvidence === null
|
|
254
|
+
&& receipt.startEvidenceMonotonicMs === null
|
|
255
|
+
&& receipt.activatedMonotonicMs === null
|
|
256
|
+
&& receipt.activatedAt === null
|
|
257
|
+
&& receipt.afterFingerprint === null
|
|
258
|
+
&& receipt.changedPaths.length === 0
|
|
259
|
+
&& receipt.sealedAt === null
|
|
260
|
+
&& receipt.consumedAt === null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (
|
|
264
|
+
!dispatchComplete
|
|
265
|
+
|| !exactText(receipt.parentId)
|
|
266
|
+
|| !exactText(receipt.childId)
|
|
267
|
+
|| receipt.parentId === receipt.childId
|
|
268
|
+
|| receipt.startEvidenceMonotonicMs === null
|
|
269
|
+
|| receipt.activatedMonotonicMs === null
|
|
270
|
+
|| !exactText(receipt.activatedAt)
|
|
271
|
+
) return false;
|
|
272
|
+
|
|
273
|
+
if (receipt.status === 'activated') {
|
|
274
|
+
return receipt.agent === null
|
|
275
|
+
&& receipt.afterFingerprint === null
|
|
276
|
+
&& receipt.changedPaths.length === 0
|
|
277
|
+
&& receipt.sealedAt === null
|
|
278
|
+
&& receipt.consumedAt === null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (
|
|
282
|
+
!exactText(receipt.agent)
|
|
283
|
+
|| normalizeAgentToken(receipt.agent) !== normalizeAgentToken(receipt.client)
|
|
284
|
+
) return false;
|
|
285
|
+
|
|
286
|
+
if (receipt.status === 'stage-completed') {
|
|
287
|
+
return receipt.afterFingerprint === null
|
|
288
|
+
&& receipt.changedPaths.length === 0
|
|
289
|
+
&& receipt.sealedAt === null
|
|
290
|
+
&& receipt.consumedAt === null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!exactText(receipt.afterFingerprint) || !exactText(receipt.sealedAt)) return false;
|
|
294
|
+
return receipt.status === 'sealed'
|
|
295
|
+
? receipt.consumedAt === null
|
|
296
|
+
: receipt.status === 'consumed' && exactText(receipt.consumedAt);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function isDelegationReceipt(value: unknown): value is DelegationReceipt {
|
|
300
|
+
if (!hasExactKeys(value, RECEIPT_KEYS)) return false;
|
|
301
|
+
const structurallyValid = exactText(value.id)
|
|
302
|
+
&& exactText(value.taskId)
|
|
303
|
+
&& exactText(value.runId)
|
|
304
|
+
&& ['executor', 'reviewer'].includes(value.role as string)
|
|
305
|
+
&& ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code', 'commit'].includes(value.stage as string)
|
|
306
|
+
&& Number.isSafeInteger(value.round)
|
|
307
|
+
&& (value.round as number) > 0
|
|
308
|
+
&& exactText(value.artifact)
|
|
309
|
+
&& isAgentClientId(value.client)
|
|
310
|
+
&& exactText(value.requestedModel)
|
|
311
|
+
&& exactText(value.requestedReasoningEffort)
|
|
312
|
+
&& nullableText(value.actualModel)
|
|
313
|
+
&& nullableText(value.actualReasoningEffort)
|
|
314
|
+
&& nullableText(value.modelFallbackReason)
|
|
315
|
+
&& nullableText(value.reasoningEffortFallbackReason)
|
|
316
|
+
&& nullableText(value.parentId)
|
|
317
|
+
&& nullableText(value.childId)
|
|
318
|
+
&& nullableText(value.spawnMode)
|
|
319
|
+
&& nullableText(value.agent)
|
|
320
|
+
&& ['prepared', 'activated', 'stage-completed', 'sealed', 'consumed', 'aborted', 'expired'].includes(value.status as string)
|
|
321
|
+
&& value.workspaceSnapshotScope === 'task'
|
|
322
|
+
&& (value.lifecycleProvenance === null || isDelegationLifecycleProvenance(value.lifecycleProvenance))
|
|
323
|
+
&& (value.hostEvidence === null || isDelegationHostEvidence(value.hostEvidence))
|
|
324
|
+
&& exactText(value.beforeFingerprint)
|
|
325
|
+
&& nullableText(value.afterFingerprint)
|
|
326
|
+
&& Array.isArray(value.changedPaths)
|
|
327
|
+
&& value.changedPaths.every(exactText)
|
|
328
|
+
&& exactText(value.createdAt)
|
|
329
|
+
&& nullableSafeInteger(value.preparedMonotonicMs)
|
|
330
|
+
&& value.preparedMonotonicMs !== null
|
|
331
|
+
&& nullableSafeInteger(value.spawnDispatchMonotonicMs)
|
|
332
|
+
&& nullableSafeInteger(value.activationDeadlineMonotonicMs)
|
|
333
|
+
&& nullableText(value.spawnDispatchedAt)
|
|
334
|
+
&& nullableText(value.activationDeadlineAt)
|
|
335
|
+
&& nullableSafeInteger(value.startEvidenceMonotonicMs)
|
|
336
|
+
&& nullableSafeInteger(value.activatedMonotonicMs)
|
|
337
|
+
&& nullableText(value.activatedAt)
|
|
338
|
+
&& nullableText(value.sealedAt)
|
|
339
|
+
&& nullableText(value.consumedAt);
|
|
340
|
+
if (!structurallyValid) return false;
|
|
341
|
+
const receipt = value as unknown as DelegationReceipt;
|
|
342
|
+
return hasStatusBoundEvidence(receipt)
|
|
343
|
+
&& (receipt.client !== 'codex' || hasCurrentCodexEvidence(receipt));
|
|
344
|
+
}
|
|
345
|
+
|
|
90
346
|
const MANAGED_AGENTS = {
|
|
91
347
|
'agent-infra-lifecycle-executor': 'executor',
|
|
92
348
|
'agent-infra-lifecycle-reviewer': 'reviewer'
|
|
@@ -100,6 +356,11 @@ function fail(code: string, message: string): ReceiptFailure {
|
|
|
100
356
|
return { ok: false, code, message };
|
|
101
357
|
}
|
|
102
358
|
|
|
359
|
+
// 空白串折叠为 null,保持"缺失即 null"的单一表示(Start/Stop 两处共用,避免任一入口把 " " 当成已观察到的值入账)
|
|
360
|
+
function foldBlankToNull(value: string | undefined): string | null {
|
|
361
|
+
return value?.trim() ? value : null;
|
|
362
|
+
}
|
|
363
|
+
|
|
103
364
|
function prepareDelegation(
|
|
104
365
|
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 }>,
|
|
105
366
|
options: { id?: () => string; now?: () => string; monotonicNow?: () => number } = {}
|
|
@@ -146,7 +407,7 @@ function dispatchDelegation(
|
|
|
146
407
|
}
|
|
147
408
|
const monotonic = (options.monotonicNow ?? (() => Number(process.hrtime.bigint() / 1_000_000n)))();
|
|
148
409
|
const dispatchedAt = (options.now ?? (() => new Date().toISOString()))();
|
|
149
|
-
const window = options.activationWindowMs ??
|
|
410
|
+
const window = options.activationWindowMs ?? 60_000;
|
|
150
411
|
return { ok: true, receipt: Object.freeze({
|
|
151
412
|
...receipt,
|
|
152
413
|
spawnDispatchMonotonicMs: monotonic,
|
|
@@ -162,7 +423,7 @@ function activateDelegation(
|
|
|
162
423
|
nativeAgent: string;
|
|
163
424
|
childId: string;
|
|
164
425
|
parentId: string;
|
|
165
|
-
spawnMode
|
|
426
|
+
spawnMode?: string;
|
|
166
427
|
actualModel?: string;
|
|
167
428
|
actualReasoningEffort?: string;
|
|
168
429
|
modelFallbackReason?: string;
|
|
@@ -211,32 +472,46 @@ function activateDelegation(
|
|
|
211
472
|
if (!event.parentId || (receipt.parentId !== null && event.parentId !== receipt.parentId) || !event.childId || event.childId === event.parentId) {
|
|
212
473
|
return fail('DELEGATION_IDENTITY_INVALID', 'native parent/child identity does not match the prepared delegation');
|
|
213
474
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
return fail('DELEGATION_MODEL_IDENTITY_MISSING', 'native start event must provide a non-empty actual model identity');
|
|
475
|
+
// spawnMode 的必需性按 client 判断:claude-code 宿主结构上不提供该字段,跳过;其余 client 维持硬校验
|
|
476
|
+
if (receipt.client !== 'claude-code' && event.spawnMode !== 'fresh') {
|
|
477
|
+
return fail('DELEGATION_FORK_FORBIDDEN', `spawn mode '${event.spawnMode}' is not fresh`);
|
|
218
478
|
}
|
|
219
|
-
|
|
220
|
-
|
|
479
|
+
// model/effort 维度:claude-code 如实记录、不做门禁;其余 client 维持现状硬校验(HDR-2)
|
|
480
|
+
const actualModel = foldBlankToNull(event.actualModel);
|
|
481
|
+
const actualReasoningEffort = foldBlankToNull(event.actualReasoningEffort);
|
|
482
|
+
if (receipt.client !== 'claude-code') {
|
|
483
|
+
if (!actualModel || actualModel.trim() !== actualModel) {
|
|
484
|
+
return fail('DELEGATION_MODEL_IDENTITY_MISSING', 'native start event must provide a non-empty actual model identity');
|
|
485
|
+
}
|
|
221
486
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
487
|
+
if (actualModel !== null && actualModel !== receipt.requestedModel) {
|
|
488
|
+
if (receipt.client !== 'claude-code' && (!event.modelFallbackReason || event.modelFallbackReason.trim() === '')) {
|
|
489
|
+
return fail('DELEGATION_MODEL_FALLBACK_UNRECORDED', 'actual model differs from requested model without a fallback reason');
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (receipt.client !== 'claude-code') {
|
|
493
|
+
if (!actualReasoningEffort || actualReasoningEffort.trim() !== actualReasoningEffort) {
|
|
494
|
+
return fail('DELEGATION_REASONING_EFFORT_MISSING', 'native start event must provide a non-empty actual reasoning effort');
|
|
495
|
+
}
|
|
225
496
|
}
|
|
226
497
|
if (
|
|
227
498
|
receipt.client === 'codex'
|
|
228
499
|
&& event.hostEvidence?.kind !== 'codex-lifecycle-v2'
|
|
229
500
|
) return fail('DELEGATION_HOST_EVIDENCE_REQUIRED', 'Codex activation requires trusted lifecycle-v2 host evidence');
|
|
230
|
-
if (
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
return fail('DELEGATION_REASONING_EFFORT_FALLBACK_UNRECORDED', 'actual reasoning effort differs from requested effort without a fallback reason');
|
|
501
|
+
if (actualReasoningEffort !== null && actualReasoningEffort !== receipt.requestedReasoningEffort) {
|
|
502
|
+
if (receipt.client !== 'claude-code' && (!event.reasoningEffortFallbackReason || event.reasoningEffortFallbackReason.trim() === '')) {
|
|
503
|
+
return fail('DELEGATION_REASONING_EFFORT_FALLBACK_UNRECORDED', 'actual reasoning effort differs from requested effort without a fallback reason');
|
|
504
|
+
}
|
|
235
505
|
}
|
|
236
|
-
|
|
506
|
+
// "无关 fallback 理由"检查对所有 client 保持不变——防止编造理由的红线,不属于 HDR-2 放宽范围
|
|
507
|
+
// actualModel/actualReasoningEffort 为 null(未观察到)时同样没有"实际不同"这回事,理由一样是无关的
|
|
508
|
+
if ((actualModel === null || actualModel === receipt.requestedModel) && event.modelFallbackReason) {
|
|
237
509
|
return fail('DELEGATION_MODEL_FALLBACK_INVALID', 'model fallback reason is only valid when actual model differs');
|
|
238
510
|
}
|
|
239
|
-
if (
|
|
511
|
+
if (
|
|
512
|
+
(actualReasoningEffort === null || actualReasoningEffort === receipt.requestedReasoningEffort)
|
|
513
|
+
&& event.reasoningEffortFallbackReason
|
|
514
|
+
) {
|
|
240
515
|
return fail('DELEGATION_REASONING_EFFORT_FALLBACK_INVALID', 'reasoning-effort fallback reason is only valid when actual effort differs');
|
|
241
516
|
}
|
|
242
517
|
if (
|
|
@@ -280,7 +555,7 @@ function activateDelegation(
|
|
|
280
555
|
status: 'activated',
|
|
281
556
|
parentId: event.parentId,
|
|
282
557
|
childId: event.childId,
|
|
283
|
-
spawnMode: event.spawnMode,
|
|
558
|
+
spawnMode: event.spawnMode ?? null,
|
|
284
559
|
actualModel,
|
|
285
560
|
actualReasoningEffort,
|
|
286
561
|
modelFallbackReason: event.modelFallbackReason ?? null,
|
|
@@ -328,6 +603,10 @@ function sealDelegation(
|
|
|
328
603
|
exitCode: number;
|
|
329
604
|
afterFingerprint: string;
|
|
330
605
|
changedPaths: readonly string[];
|
|
606
|
+
actualModel?: string;
|
|
607
|
+
actualReasoningEffort?: string;
|
|
608
|
+
modelFallbackReason?: string;
|
|
609
|
+
reasoningEffortFallbackReason?: string;
|
|
331
610
|
hostEvidence?: Readonly<{ stopRevision: number; consumer: string; consumedAt: string }>;
|
|
332
611
|
}>,
|
|
333
612
|
options: { now?: () => string; requireHostEvidence?: boolean } = {}
|
|
@@ -361,6 +640,10 @@ function sealDelegation(
|
|
|
361
640
|
status: 'sealed',
|
|
362
641
|
afterFingerprint: event.afterFingerprint,
|
|
363
642
|
changedPaths: Object.freeze([...event.changedPaths]),
|
|
643
|
+
actualModel: receipt.actualModel ?? foldBlankToNull(event.actualModel),
|
|
644
|
+
actualReasoningEffort: receipt.actualReasoningEffort ?? foldBlankToNull(event.actualReasoningEffort),
|
|
645
|
+
modelFallbackReason: receipt.modelFallbackReason ?? event.modelFallbackReason ?? null,
|
|
646
|
+
reasoningEffortFallbackReason: receipt.reasoningEffortFallbackReason ?? event.reasoningEffortFallbackReason ?? null,
|
|
364
647
|
hostEvidence: event.hostEvidence && receipt.hostEvidence ? Object.freeze({
|
|
365
648
|
...receipt.hostEvidence,
|
|
366
649
|
...event.hostEvidence
|
|
@@ -385,6 +668,8 @@ export {
|
|
|
385
668
|
completeDelegationStage,
|
|
386
669
|
consumeDelegation,
|
|
387
670
|
dispatchDelegation,
|
|
671
|
+
foldBlankToNull,
|
|
672
|
+
isDelegationReceipt,
|
|
388
673
|
managedDelegationRole,
|
|
389
674
|
prepareDelegation,
|
|
390
675
|
sealDelegation
|
package/lib/task/events.ts
CHANGED
|
@@ -32,6 +32,7 @@ import type { OrchestrationStageCompletion } from './orchestration.ts';
|
|
|
32
32
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
33
33
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
34
34
|
import type { TaskOperationSummary, TaskWriteErrorCode, TaskWriteOptions } from './write.ts';
|
|
35
|
+
import { allowsManualOverride } from './guard-override.ts';
|
|
35
36
|
|
|
36
37
|
const eventCatalog = [
|
|
37
38
|
'analyze.started', 'analyze.awaiting-input', 'analyze.completed',
|
|
@@ -53,6 +54,7 @@ type TaskEventErrorCode =
|
|
|
53
54
|
| ArtifactErrorCode | TaskWriteErrorCode;
|
|
54
55
|
type TaskEventRequest = {
|
|
55
56
|
taskRef: string; event: TaskEventName | string; agent: string; dryRun?: boolean; orchestrated?: boolean;
|
|
57
|
+
overrideTicket?: string; overrideTarget?: string; overrideScope?: string;
|
|
56
58
|
round?: number; question?: number; artifact?: string; fixFor?: string; implementationInput?: string;
|
|
57
59
|
verdict?: Verdict; blockers?: number; major?: number; minor?: number;
|
|
58
60
|
manualValidation?: number; filesModified?: number; testsPassed?: number;
|
|
@@ -61,6 +63,7 @@ type TaskEventRequest = {
|
|
|
61
63
|
type TaskEventError = { code: TaskEventErrorCode; message: string };
|
|
62
64
|
type TaskEventOptions = TaskWriteOptions & {
|
|
63
65
|
commitOrchestrationCompletion?: (plan: OrchestrationStageCompletion) => void;
|
|
66
|
+
lockAlreadyHeld?: boolean;
|
|
64
67
|
};
|
|
65
68
|
type TaskEventResult = {
|
|
66
69
|
status: 'planned' | 'applied' | 'no-op' | 'failed'; changed: boolean;
|
|
@@ -73,7 +76,7 @@ type TaskEventResult = {
|
|
|
73
76
|
operations: readonly TaskOperationSummary[]; error: TaskEventError | null;
|
|
74
77
|
};
|
|
75
78
|
|
|
76
|
-
const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun']);
|
|
79
|
+
const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope']);
|
|
77
80
|
const SCHEMAS: Record<TaskEventName, { required?: string[]; optional?: string[] }> = {
|
|
78
81
|
'analyze.started': { optional: ['round'] },
|
|
79
82
|
'analyze.awaiting-input': { required: ['question'] },
|
|
@@ -357,7 +360,8 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
357
360
|
if (invalid) return failed(request, invalid);
|
|
358
361
|
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
359
362
|
if (!resolved.ok) return failed(request, { code: resolved.code, message: resolved.message }, { taskId: resolved.taskId });
|
|
360
|
-
|
|
363
|
+
const stateOverride = allowsManualOverride(options.manualOverride, 'task-event', 'TASK_STATE_MISMATCH');
|
|
364
|
+
if (resolved.state !== 'active' && !stateOverride) return failed(request, { code: 'TASK_STATE_MISMATCH', message: `task ${resolved.taskId} is ${resolved.state}, expected active` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
361
365
|
let content: string;
|
|
362
366
|
try { content = fs.readFileSync(resolved.taskMdPath, 'utf8'); }
|
|
363
367
|
catch (error) { return failed(request, { code: 'TASK_READ_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath }); }
|
|
@@ -406,7 +410,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
406
410
|
const done = completedRows.find((item) => item.note === eventIdentity.note);
|
|
407
411
|
if (eventIdentity.phase === 'completed' && done) return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, done.done, frontmatter, artifactContext);
|
|
408
412
|
if (eventIdentity.phase === 'completed' && completedRows.length > 0 && openRows.length === 0) return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'event identity is already completed with a different payload' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
409
|
-
if (eventIdentity.phase === 'completed' && !row?.started) return failed(normalized, { code: 'EVENT_START_MISSING', message: 'completion requires one open matching started event' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
413
|
+
if (eventIdentity.phase === 'completed' && !row?.started && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_START_MISSING')) return failed(normalized, { code: 'EVENT_START_MISSING', message: 'completion requires one open matching started event' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
410
414
|
if (eventIdentity.phase === 'completed') {
|
|
411
415
|
const validated = validateCompletedArtifact(resolved.taskDir, FAMILY[eventIdentity.family].artifact, normalized.artifact!, normalized.round);
|
|
412
416
|
if (!validated.ok) return failed(normalized, validated.error, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
@@ -417,7 +421,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
417
421
|
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, existing.time, frontmatter, artifactContext);
|
|
418
422
|
}
|
|
419
423
|
const allowed = FAMILY[eventIdentity.family][eventIdentity.phase === 'started' ? 'started' : 'completed'];
|
|
420
|
-
if (!(allowed as readonly string[]).includes(currentStep)) return failed(normalized, { code: 'EVENT_TRANSITION_INVALID', message: `${normalized.event} is not allowed from '${currentStep}'` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
424
|
+
if (!(allowed as readonly string[]).includes(currentStep) && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_TRANSITION_INVALID')) return failed(normalized, { code: 'EVENT_TRANSITION_INVALID', message: `${normalized.event} is not allowed from '${currentStep}'` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
421
425
|
const findingCountError = validateReviewFindingCounts(
|
|
422
426
|
normalized,
|
|
423
427
|
content,
|
|
@@ -522,7 +526,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
522
526
|
});
|
|
523
527
|
}
|
|
524
528
|
mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
|
|
525
|
-
const result = writeTask({ taskRef: normalized.taskRef, expectedState: 'active', dryRun: normalized.dryRun, mutations }, { ...options, metadataProvider: () => metadata });
|
|
529
|
+
const result = writeTask({ taskRef: normalized.taskRef, expectedState: stateOverride ? resolved.state : 'active', dryRun: normalized.dryRun, mutations }, { ...options, metadataProvider: () => metadata });
|
|
526
530
|
if (result.status === 'failed') return failed(normalized, result.error, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
|
|
527
531
|
if (!normalized.dryRun && orchestrationCompletion) {
|
|
528
532
|
try {
|
|
@@ -560,6 +564,7 @@ function applyTaskEvent(request: TaskEventRequest, options: TaskEventOptions = {
|
|
|
560
564
|
const invalid = validateTaskEventRequest(request);
|
|
561
565
|
if (invalid) return failed(request, invalid);
|
|
562
566
|
const parts = eventParts(request.event);
|
|
567
|
+
if (options.lockAlreadyHeld) return applyTaskEventUnlocked(request, options);
|
|
563
568
|
if (request.dryRun || parts.phase !== 'completed' || parts.family === 'manual-validation') {
|
|
564
569
|
return applyTaskEventUnlocked(request, options);
|
|
565
570
|
}
|