@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
package/lib/task/events.ts
CHANGED
|
@@ -4,11 +4,16 @@ import { appendActivityEntry, locateActivityLog, pairEntries } from './activity-
|
|
|
4
4
|
import {
|
|
5
5
|
buildArtifactLinkSection,
|
|
6
6
|
artifactName,
|
|
7
|
+
inspectArtifactDirectory,
|
|
8
|
+
parseReviewedInputReference,
|
|
9
|
+
parseCodePlanInputReference,
|
|
7
10
|
parseArtifactName,
|
|
8
11
|
resolveArtifactContext,
|
|
9
12
|
validateCompletedArtifact
|
|
10
13
|
} from './artifact-lifecycle.ts';
|
|
11
14
|
import type { ArtifactContextResult, ArtifactErrorCode, ArtifactFamily, ArtifactIdentity } from './artifact-lifecycle.ts';
|
|
15
|
+
import { ArtifactReceiptError, sha256File, upsertArtifactReceipt } from './artifact-receipts.ts';
|
|
16
|
+
import type { ArtifactReceipt } from './artifact-receipts.ts';
|
|
12
17
|
import { parseTypedTaskFrontmatter } from './frontmatter.ts';
|
|
13
18
|
import {
|
|
14
19
|
consumeImplementationInput,
|
|
@@ -27,6 +32,7 @@ import type { OrchestrationStageCompletion } from './orchestration.ts';
|
|
|
27
32
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
28
33
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
29
34
|
import type { TaskOperationSummary, TaskWriteErrorCode, TaskWriteOptions } from './write.ts';
|
|
35
|
+
import { allowsManualOverride } from './guard-override.ts';
|
|
30
36
|
|
|
31
37
|
const eventCatalog = [
|
|
32
38
|
'analyze.started', 'analyze.awaiting-input', 'analyze.completed',
|
|
@@ -48,6 +54,7 @@ type TaskEventErrorCode =
|
|
|
48
54
|
| ArtifactErrorCode | TaskWriteErrorCode;
|
|
49
55
|
type TaskEventRequest = {
|
|
50
56
|
taskRef: string; event: TaskEventName | string; agent: string; dryRun?: boolean; orchestrated?: boolean;
|
|
57
|
+
overrideTicket?: string; overrideTarget?: string; overrideScope?: string;
|
|
51
58
|
round?: number; question?: number; artifact?: string; fixFor?: string; implementationInput?: string;
|
|
52
59
|
verdict?: Verdict; blockers?: number; major?: number; minor?: number;
|
|
53
60
|
manualValidation?: number; filesModified?: number; testsPassed?: number;
|
|
@@ -56,6 +63,7 @@ type TaskEventRequest = {
|
|
|
56
63
|
type TaskEventError = { code: TaskEventErrorCode; message: string };
|
|
57
64
|
type TaskEventOptions = TaskWriteOptions & {
|
|
58
65
|
commitOrchestrationCompletion?: (plan: OrchestrationStageCompletion) => void;
|
|
66
|
+
lockAlreadyHeld?: boolean;
|
|
59
67
|
};
|
|
60
68
|
type TaskEventResult = {
|
|
61
69
|
status: 'planned' | 'applied' | 'no-op' | 'failed'; changed: boolean;
|
|
@@ -68,7 +76,7 @@ type TaskEventResult = {
|
|
|
68
76
|
operations: readonly TaskOperationSummary[]; error: TaskEventError | null;
|
|
69
77
|
};
|
|
70
78
|
|
|
71
|
-
const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun']);
|
|
79
|
+
const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope']);
|
|
72
80
|
const SCHEMAS: Record<TaskEventName, { required?: string[]; optional?: string[] }> = {
|
|
73
81
|
'analyze.started': { optional: ['round'] },
|
|
74
82
|
'analyze.awaiting-input': { required: ['question'] },
|
|
@@ -273,12 +281,87 @@ function openStartedIdentity(rows: ReturnType<typeof pairEntries>, family: Event
|
|
|
273
281
|
return matches.length === 1 ? matches[0] : matches.length > 1 ? { conflict: true as const } : null;
|
|
274
282
|
}
|
|
275
283
|
|
|
284
|
+
function reviewInputFamily(family: EventFamily): ArtifactFamily {
|
|
285
|
+
return family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function buildCompletionReceipt(
|
|
289
|
+
taskDir: string,
|
|
290
|
+
family: EventFamily,
|
|
291
|
+
artifact: ArtifactIdentity,
|
|
292
|
+
completedAt: string,
|
|
293
|
+
frontmatter: Record<string, unknown>
|
|
294
|
+
): { ok: true; receipt: ArtifactReceipt } | { ok: false; message: string } | null {
|
|
295
|
+
if (family === 'code') {
|
|
296
|
+
let content: string;
|
|
297
|
+
try { content = fs.readFileSync(artifact.path, 'utf8'); }
|
|
298
|
+
catch (error) { return { ok: false, message: `cannot read ${artifact.name}: ${String(error)}` }; }
|
|
299
|
+
const input = parseCodePlanInputReference(content);
|
|
300
|
+
if (!input) return { ok: false, message: `${artifact.name} does not reference a canonical plan artifact` };
|
|
301
|
+
const startedInput = typeof frontmatter.code_input_artifact === 'string' ? frontmatter.code_input_artifact : '';
|
|
302
|
+
const startedSha256 = typeof frontmatter.code_input_sha256 === 'string' ? frontmatter.code_input_sha256 : '';
|
|
303
|
+
if (!startedInput || !startedSha256) return { ok: false, message: 'code.started plan input context is missing' };
|
|
304
|
+
if (input.name !== startedInput) return { ok: false, message: `${artifact.name} plan input '${input.name}' does not match code.started input '${startedInput}'` };
|
|
305
|
+
const plan = inspectArtifactDirectory(taskDir, 'plan');
|
|
306
|
+
if (plan.status !== 'ready' || !plan.latest || plan.latest.name !== input.name) {
|
|
307
|
+
return { ok: false, message: `code input '${input.name}' is not the latest plan artifact` };
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
const inputSha256 = sha256File(plan.latest.path);
|
|
311
|
+
if (inputSha256 !== startedSha256) return { ok: false, message: `code input ${input.name} changed after code.started` };
|
|
312
|
+
return {
|
|
313
|
+
ok: true,
|
|
314
|
+
receipt: {
|
|
315
|
+
event: 'code.completed', output: artifact.name, input: input.name,
|
|
316
|
+
inputSha256, completedAt
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
} catch (error) {
|
|
320
|
+
return { ok: false, message: `cannot hash code input plan: ${error instanceof Error ? error.message : String(error)}` };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (!family.startsWith('review-')) return null;
|
|
324
|
+
const expectedFamily = reviewInputFamily(family);
|
|
325
|
+
let content: string;
|
|
326
|
+
try { content = fs.readFileSync(artifact.path, 'utf8'); }
|
|
327
|
+
catch (error) { return { ok: false, message: `cannot read ${artifact.name}: ${String(error)}` }; }
|
|
328
|
+
const input = parseReviewedInputReference(content, expectedFamily);
|
|
329
|
+
if (!input) return { ok: false, message: `${artifact.name} does not reference a canonical ${expectedFamily} artifact` };
|
|
330
|
+
const startedInput = typeof frontmatter.review_input_artifact === 'string' ? frontmatter.review_input_artifact : '';
|
|
331
|
+
const startedSha256 = typeof frontmatter.review_input_sha256 === 'string' ? frontmatter.review_input_sha256 : '';
|
|
332
|
+
if (!startedInput || !startedSha256) return { ok: false, message: 'review started input context is missing' };
|
|
333
|
+
if (input.name !== startedInput) return { ok: false, message: `${artifact.name} input '${input.name}' does not match review.started input '${startedInput}'` };
|
|
334
|
+
const current = inspectArtifactDirectory(taskDir, expectedFamily);
|
|
335
|
+
if (current.status !== 'ready' || !current.latest || current.latest.name !== input.name) {
|
|
336
|
+
return { ok: false, message: `review input '${input.name}' is not the latest ${expectedFamily} artifact` };
|
|
337
|
+
}
|
|
338
|
+
const event = family === 'review-analysis'
|
|
339
|
+
? 'review-analysis.completed' as const
|
|
340
|
+
: family === 'review-plan' ? 'review-plan.completed' as const : 'review-code.completed' as const;
|
|
341
|
+
try {
|
|
342
|
+
const stat = fs.lstatSync(current.latest.path);
|
|
343
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('review input is not a regular file');
|
|
344
|
+
const inputSha256 = sha256File(current.latest.path);
|
|
345
|
+
if (inputSha256 !== startedSha256) return { ok: false, message: `review input ${input.name} changed after review.started` };
|
|
346
|
+
return {
|
|
347
|
+
ok: true,
|
|
348
|
+
receipt: {
|
|
349
|
+
event, output: artifact.name, input: input.name,
|
|
350
|
+
inputSha256, completedAt
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
} catch (error) {
|
|
354
|
+
return { ok: false, message: `cannot hash review input ${input.name}: ${error instanceof Error ? error.message : String(error)}` };
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
276
358
|
function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOptions = {}): TaskEventResult {
|
|
277
359
|
const invalid = validateTaskEventRequest(request);
|
|
278
360
|
if (invalid) return failed(request, invalid);
|
|
279
361
|
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
280
362
|
if (!resolved.ok) return failed(request, { code: resolved.code, message: resolved.message }, { taskId: resolved.taskId });
|
|
281
|
-
|
|
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 });
|
|
282
365
|
let content: string;
|
|
283
366
|
try { content = fs.readFileSync(resolved.taskMdPath, 'utf8'); }
|
|
284
367
|
catch (error) { return failed(request, { code: 'TASK_READ_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath }); }
|
|
@@ -327,7 +410,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
327
410
|
const done = completedRows.find((item) => item.note === eventIdentity.note);
|
|
328
411
|
if (eventIdentity.phase === 'completed' && done) return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, done.done, frontmatter, artifactContext);
|
|
329
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 });
|
|
330
|
-
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 });
|
|
331
414
|
if (eventIdentity.phase === 'completed') {
|
|
332
415
|
const validated = validateCompletedArtifact(resolved.taskDir, FAMILY[eventIdentity.family].artifact, normalized.artifact!, normalized.round);
|
|
333
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 });
|
|
@@ -338,7 +421,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
338
421
|
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, existing.time, frontmatter, artifactContext);
|
|
339
422
|
}
|
|
340
423
|
const allowed = FAMILY[eventIdentity.family][eventIdentity.phase === 'started' ? 'started' : 'completed'];
|
|
341
|
-
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 });
|
|
342
425
|
const findingCountError = validateReviewFindingCounts(
|
|
343
426
|
normalized,
|
|
344
427
|
content,
|
|
@@ -371,18 +454,60 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
371
454
|
let metadata;
|
|
372
455
|
try { metadata = (options.metadataProvider ?? captureTaskWriteMetadata)(); }
|
|
373
456
|
catch (error) { return failed(normalized, { code: 'METADATA_CAPTURE_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath }); }
|
|
457
|
+
let completionReceipt: ArtifactReceipt | null = null;
|
|
458
|
+
if (eventIdentity.phase === 'completed' && completedArtifact) {
|
|
459
|
+
const receipt = buildCompletionReceipt(resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter);
|
|
460
|
+
if (receipt && !receipt.ok) {
|
|
461
|
+
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: receipt.message }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
462
|
+
}
|
|
463
|
+
completionReceipt = receipt?.receipt ?? null;
|
|
464
|
+
}
|
|
374
465
|
const step = eventIdentity.phase === 'started' || eventIdentity.target === null ? currentStep : eventIdentity.target;
|
|
375
466
|
const logStep = eventIdentity.phase === 'started' ? `${eventIdentity.action} [started]` : eventIdentity.action;
|
|
376
467
|
const body = appendActivityEntry(section, { time: metadata.timestamp, step: logStep, agent: normalized.agent, note: eventIdentity.note });
|
|
377
468
|
const frontmatterSet: Record<string, string> = { current_step: step, assigned_to: normalized.agent };
|
|
469
|
+
let frontmatterRemove: string[] | undefined;
|
|
470
|
+
if (eventIdentity.phase === 'started' && eventIdentity.family === 'code') {
|
|
471
|
+
const planInput = artifactContext?.inputs.find((input) => input.family === 'plan');
|
|
472
|
+
if (!planInput) return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: 'code.started plan input context is unavailable' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
|
|
473
|
+
try {
|
|
474
|
+
frontmatterSet.code_input_artifact = planInput.name;
|
|
475
|
+
frontmatterSet.code_input_sha256 = sha256File(planInput.path);
|
|
476
|
+
} catch (error) {
|
|
477
|
+
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot hash code.started plan input: ${error instanceof Error ? error.message : String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
|
|
478
|
+
}
|
|
479
|
+
} else if (eventIdentity.phase === 'completed' && eventIdentity.family === 'code') {
|
|
480
|
+
frontmatterRemove = ['code_input_artifact', 'code_input_sha256'];
|
|
481
|
+
} else if (eventIdentity.phase === 'started' && eventIdentity.family.startsWith('review-')) {
|
|
482
|
+
const expectedFamily = reviewInputFamily(eventIdentity.family);
|
|
483
|
+
const input = artifactContext?.inputs.find((candidate) => candidate.family === expectedFamily);
|
|
484
|
+
if (!input) return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `review.started ${expectedFamily} input context is unavailable` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
|
|
485
|
+
try {
|
|
486
|
+
frontmatterSet.review_input_artifact = input.name;
|
|
487
|
+
frontmatterSet.review_input_sha256 = sha256File(input.path);
|
|
488
|
+
} catch (error) {
|
|
489
|
+
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot hash review.started input: ${error instanceof Error ? error.message : String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
|
|
490
|
+
}
|
|
491
|
+
} else if (eventIdentity.phase === 'completed' && eventIdentity.family.startsWith('review-')) {
|
|
492
|
+
frontmatterRemove = ['review_input_artifact', 'review_input_sha256'];
|
|
493
|
+
}
|
|
378
494
|
if (eventIdentity.phase === 'started' && normalized.implementationInput) frontmatterSet.last_reviewed_commit = '';
|
|
379
495
|
const mutations: Parameters<typeof writeTask>[0]['mutations'][number][] = [
|
|
380
|
-
{ kind: 'frontmatter', set: frontmatterSet }
|
|
496
|
+
{ kind: 'frontmatter', set: frontmatterSet, remove: frontmatterRemove }
|
|
381
497
|
];
|
|
382
498
|
if (completedArtifact) {
|
|
383
499
|
const link = buildArtifactLinkSection(content, completedArtifact);
|
|
384
500
|
mutations.push({ kind: 'section', aliases: link.aliases, heading: link.heading, body: link.body });
|
|
385
501
|
}
|
|
502
|
+
if (completionReceipt) {
|
|
503
|
+
try {
|
|
504
|
+
const receiptSection = upsertArtifactReceipt(content, completionReceipt);
|
|
505
|
+
mutations.push({ kind: 'section', aliases: receiptSection.aliases, heading: receiptSection.heading, body: receiptSection.body });
|
|
506
|
+
} catch (error) {
|
|
507
|
+
const message = error instanceof ArtifactReceiptError ? error.message : String(error);
|
|
508
|
+
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
509
|
+
}
|
|
510
|
+
}
|
|
386
511
|
if (eventIdentity.phase === 'completed' && normalized.implementationInput) {
|
|
387
512
|
let implementationRows;
|
|
388
513
|
try {
|
|
@@ -401,7 +526,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
401
526
|
});
|
|
402
527
|
}
|
|
403
528
|
mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
|
|
404
|
-
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 });
|
|
405
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 });
|
|
406
531
|
if (!normalized.dryRun && orchestrationCompletion) {
|
|
407
532
|
try {
|
|
@@ -439,6 +564,7 @@ function applyTaskEvent(request: TaskEventRequest, options: TaskEventOptions = {
|
|
|
439
564
|
const invalid = validateTaskEventRequest(request);
|
|
440
565
|
if (invalid) return failed(request, invalid);
|
|
441
566
|
const parts = eventParts(request.event);
|
|
567
|
+
if (options.lockAlreadyHeld) return applyTaskEventUnlocked(request, options);
|
|
442
568
|
if (request.dryRun || parts.phase !== 'completed' || parts.family === 'manual-validation') {
|
|
443
569
|
return applyTaskEventUnlocked(request, options);
|
|
444
570
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
type ManualOverrideCapability = Readonly<{
|
|
2
|
+
failureId: string;
|
|
3
|
+
operator: string;
|
|
4
|
+
reason: string;
|
|
5
|
+
}>;
|
|
6
|
+
|
|
7
|
+
type GuardProducer = Readonly<{
|
|
8
|
+
producerId: string;
|
|
9
|
+
guardId: `G-${string}`;
|
|
10
|
+
code: string;
|
|
11
|
+
}>;
|
|
12
|
+
|
|
13
|
+
const guardProducerCatalog: readonly GuardProducer[] = [
|
|
14
|
+
{ producerId: 'task.resolve', guardId: 'G-01', code: 'TASK_NOT_FOUND' },
|
|
15
|
+
{ producerId: 'task.resolve', guardId: 'G-01', code: 'TASK_IDENTITY_CONFLICT' },
|
|
16
|
+
{ producerId: 'task.write', guardId: 'G-02', code: 'TASK_STATE_MISMATCH' },
|
|
17
|
+
{ producerId: 'task-event', guardId: 'G-04', code: 'TASK_STATE_MISMATCH' },
|
|
18
|
+
{ producerId: 'task-event', guardId: 'G-04', code: 'EVENT_TRANSITION_INVALID' },
|
|
19
|
+
{ producerId: 'task-event', guardId: 'G-04', code: 'EVENT_START_MISSING' },
|
|
20
|
+
{ producerId: 'task-verify', guardId: 'G-05', code: 'VERIFY_TASK_STATE_MISMATCH' },
|
|
21
|
+
{ producerId: 'task-verify', guardId: 'G-05', code: 'VERIFY_ARTIFACT_INVALID' },
|
|
22
|
+
{ producerId: 'verification-engine', guardId: 'G-06', code: 'CHECK_FAILED' },
|
|
23
|
+
{ producerId: 'verification-engine', guardId: 'G-06', code: 'CHECK_BLOCKED' },
|
|
24
|
+
{ producerId: 'activity-intent', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
|
|
25
|
+
{ producerId: 'ledger-intent', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
|
|
26
|
+
{ producerId: 'decision-intent', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
|
|
27
|
+
{ producerId: 'workflow-warning', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
|
|
28
|
+
{ producerId: 'review-finalization', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
|
|
29
|
+
{ producerId: 'ledger-intent', guardId: 'G-08', code: 'LEDGER_TRANSITION_INVALID' },
|
|
30
|
+
{ producerId: 'lifecycle-execution', guardId: 'G-09', code: 'ORCHESTRATION_STANDALONE_BUSY' },
|
|
31
|
+
{ producerId: 'lifecycle-execution', guardId: 'G-09', code: 'ORCHESTRATION_PROVENANCE_MISMATCH' },
|
|
32
|
+
{ producerId: 'lifecycle-execution', guardId: 'G-09', code: 'ORCHESTRATION_STATE_INVALID' },
|
|
33
|
+
{ producerId: 'platform.issue', guardId: 'G-10', code: 'PLATFORM_BIND_FAILED' },
|
|
34
|
+
{ producerId: 'platform.pull-request', guardId: 'G-10', code: 'PLATFORM_BIND_FAILED' }
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
function guardFailureId(producerId: string, code: string): string {
|
|
38
|
+
return `${producerId}:${code}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function allowsManualOverride(
|
|
42
|
+
capability: ManualOverrideCapability | undefined,
|
|
43
|
+
producerId: string,
|
|
44
|
+
code: string
|
|
45
|
+
): boolean {
|
|
46
|
+
return capability?.failureId === guardFailureId(producerId, code);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { allowsManualOverride, guardFailureId, guardProducerCatalog };
|
|
50
|
+
export type { GuardProducer, ManualOverrideCapability };
|