@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
|
@@ -12,6 +12,7 @@ import type { LedgerRow, ReviewStage } from './ledger.ts';
|
|
|
12
12
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
13
13
|
import { writeTask } from './write.ts';
|
|
14
14
|
import type { TaskMutation, TaskOperationSummary, TaskWriteOptions } from './write.ts';
|
|
15
|
+
import { allowsManualOverride } from './guard-override.ts';
|
|
15
16
|
|
|
16
17
|
type ReviewSeverity = 'blocker' | 'major' | 'minor';
|
|
17
18
|
type ExecutorResponse = 'accepted' | 'adjusted' | 'refuted' | 'cannot-judge';
|
|
@@ -120,7 +121,8 @@ function mapWrite(intent: LedgerIntent, entityId: string, before: LedgerRow | nu
|
|
|
120
121
|
function applyLedgerIntent(intent: LedgerIntent, options: TaskWriteOptions = {}): LedgerIntentResult {
|
|
121
122
|
const resolved = resolveTaskRef(intent.taskRef, { repoRoot: options.repoRoot });
|
|
122
123
|
if (!resolved.ok) return failed(intent, resolved.code, resolved.message, resolved.taskId);
|
|
123
|
-
|
|
124
|
+
const stateOverride = allowsManualOverride(options.manualOverride, 'ledger-intent', 'TASK_STATE_MISMATCH');
|
|
125
|
+
if (resolved.state !== 'active' && !stateOverride) return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
124
126
|
let content: string;
|
|
125
127
|
let rows: LedgerRow[];
|
|
126
128
|
try {
|
|
@@ -196,7 +198,8 @@ function applyLedgerIntent(intent: LedgerIntent, options: TaskWriteOptions = {})
|
|
|
196
198
|
? new Set(['confirmed', 'open', 'needs-human-decision'])
|
|
197
199
|
: before.status === 'cannot-judge' ? new Set(['open', 'needs-human-decision']) : new Set<string>();
|
|
198
200
|
const sameRoundMinorClose = before.status === 'open' && before.severity === 'minor' && intent.status === 'closed';
|
|
199
|
-
if ((!sameRoundMinorClose && !allowed.has(intent.status)) || (intent.status === 'open' && Number(before.round) >= maxHandshakeRounds(resolved.repoRoot)))
|
|
201
|
+
if (((!sameRoundMinorClose && !allowed.has(intent.status)) || (intent.status === 'open' && Number(before.round) >= maxHandshakeRounds(resolved.repoRoot)))
|
|
202
|
+
&& !allowsManualOverride(options.manualOverride, 'ledger-intent', 'LEDGER_TRANSITION_INVALID')) {
|
|
200
203
|
return failed(intent, 'LEDGER_TRANSITION_INVALID', `finding '${intent.id}' cannot transition from ${before.status} to ${intent.status}`, resolved.taskId, intent.id);
|
|
201
204
|
}
|
|
202
205
|
after = { ...before, status: intent.status, evidence };
|
|
@@ -230,7 +233,7 @@ function applyLedgerIntent(intent: LedgerIntent, options: TaskWriteOptions = {})
|
|
|
230
233
|
}
|
|
231
234
|
|
|
232
235
|
const result = writeTask({
|
|
233
|
-
taskRef: intent.taskRef, expectedState: 'active',
|
|
236
|
+
taskRef: intent.taskRef, expectedState: stateOverride ? resolved.state : 'active',
|
|
234
237
|
mutations: [...ledgerSectionMutation(content), rowMutation(after), ...(implementationMutation ? [implementationMutation] : [])],
|
|
235
238
|
dryRun: 'dryRun' in intent ? intent.dryRun : false
|
|
236
239
|
}, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state } });
|
package/lib/task/ledger.ts
CHANGED
|
@@ -61,7 +61,22 @@ function validateLedgerRows(rows: readonly LedgerRow[]): LedgerValidationError |
|
|
|
61
61
|
for (const row of rows) {
|
|
62
62
|
if (seen.has(row.id)) return { code: 'LEDGER_DUPLICATE_ID', message: `duplicate ledger id '${row.id}'` };
|
|
63
63
|
seen.add(row.id);
|
|
64
|
-
if (/^PRC-[1-9]\d*$/.test(row.id))
|
|
64
|
+
if (/^PRC-[1-9]\d*$/.test(row.id)) {
|
|
65
|
+
if (
|
|
66
|
+
row.stage !== 'post-review-commit' ||
|
|
67
|
+
row.round !== '-' ||
|
|
68
|
+
row.severity !== '-' ||
|
|
69
|
+
row.status !== 'human-decided' ||
|
|
70
|
+
row.evidence === '' ||
|
|
71
|
+
/[\r\n]/.test(row.evidence)
|
|
72
|
+
) {
|
|
73
|
+
return { code: 'LEDGER_DOCUMENT_INVALID', message: `post-review exemption row '${row.id}' is invalid` };
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (row.stage === 'post-review-commit') {
|
|
78
|
+
return { code: 'LEDGER_DOCUMENT_INVALID', message: `post-review exemption row '${row.id}' must use a PRC id` };
|
|
79
|
+
}
|
|
65
80
|
if (!/^(AN|PL|CD|HD)-[1-9]\d*$/.test(row.id)) return { code: 'LEDGER_ID_INVALID', message: `ledger id '${row.id}' is invalid` };
|
|
66
81
|
if (!['analysis', 'plan', 'code', 'post-review-commit'].includes(row.stage)) return { code: 'LEDGER_STAGE_INVALID', message: `ledger stage '${row.stage}' is invalid` };
|
|
67
82
|
if (!FINDING_STATUSES.has(row.status)) return { code: 'LEDGER_STATUS_INVALID', message: `ledger status '${row.status}' is invalid` };
|
package/lib/task/lifecycle.ts
CHANGED
|
@@ -16,6 +16,33 @@ import type { TaskFileSystem, TaskMutation, TaskOperationSummary, TaskWriteMetad
|
|
|
16
16
|
const lifecycleIntentCatalog = [
|
|
17
17
|
'block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore'
|
|
18
18
|
] as const;
|
|
19
|
+
const lifecycleFailureCatalog = [
|
|
20
|
+
'LIFECYCLE_DIRECTORY_RENAME_FAILED',
|
|
21
|
+
'LIFECYCLE_DOCUMENT_INVALID',
|
|
22
|
+
'LIFECYCLE_FINAL_STATE_INVALID',
|
|
23
|
+
'LIFECYCLE_IDENTITY_CONFLICT',
|
|
24
|
+
'LIFECYCLE_IDENTITY_INVALID',
|
|
25
|
+
'LIFECYCLE_INTENT_CONFLICT',
|
|
26
|
+
'LIFECYCLE_JOURNAL_CLEANUP_FAILED',
|
|
27
|
+
'LIFECYCLE_JOURNAL_INVALID',
|
|
28
|
+
'LIFECYCLE_JOURNAL_WRITE_FAILED',
|
|
29
|
+
'LIFECYCLE_LOG_MISSING',
|
|
30
|
+
'LIFECYCLE_METADATA_FAILED',
|
|
31
|
+
'LIFECYCLE_PAYLOAD_INVALID',
|
|
32
|
+
'LIFECYCLE_SHORT_ID_FAILED',
|
|
33
|
+
'LIFECYCLE_SHORT_ID_PRECONDITION',
|
|
34
|
+
'LIFECYCLE_SOURCE_INVALID',
|
|
35
|
+
'LIFECYCLE_STAGING_IDENTITY_INVALID',
|
|
36
|
+
'LIFECYCLE_STAGING_INVALID',
|
|
37
|
+
'LIFECYCLE_TARGET_CONFLICT',
|
|
38
|
+
'LIFECYCLE_TASK_NOT_FOUND',
|
|
39
|
+
'SHORT_ID_CAPACITY_EXCEEDED'
|
|
40
|
+
] as const;
|
|
41
|
+
const lifecycleProducerCatalog = lifecycleFailureCatalog.map((code) => ({
|
|
42
|
+
producerId: 'lifecycle.apply' as const,
|
|
43
|
+
guardId: 'G-03' as const,
|
|
44
|
+
code
|
|
45
|
+
}));
|
|
19
46
|
type TaskLifecycleIntent = (typeof lifecycleIntentCatalog)[number];
|
|
20
47
|
type TaskLifecycleRequest =
|
|
21
48
|
| { taskRef: string; intent: 'block'; agent: string; reason: string; unblockCondition: string; dryRun?: boolean }
|
|
@@ -39,6 +66,7 @@ type LifecycleJournal = {
|
|
|
39
66
|
targetState: HotState;
|
|
40
67
|
metadata: TaskWriteMetadata;
|
|
41
68
|
completedSteps: LifecycleStep[];
|
|
69
|
+
failure?: LifecycleError;
|
|
42
70
|
};
|
|
43
71
|
type TaskLifecycleResult = {
|
|
44
72
|
status: 'planned' | 'applied' | 'no-op' | 'failed';
|
|
@@ -72,8 +100,13 @@ type TaskLifecycleOptions = {
|
|
|
72
100
|
fileSystem?: Partial<LifecycleFileSystem>;
|
|
73
101
|
taskFileSystem?: Partial<TaskFileSystem>;
|
|
74
102
|
directoryRenameSync?: (source: string, target: string) => void;
|
|
103
|
+
manualOverride?: Readonly<{ failureId: string; operator: string; reason: string }>;
|
|
75
104
|
};
|
|
76
105
|
|
|
106
|
+
function allowsManualOverride(options: TaskLifecycleOptions, code: string): boolean {
|
|
107
|
+
return options.manualOverride?.failureId === `lifecycle.apply:${code}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
77
110
|
const STEPS: readonly LifecycleStep[] = ['task-written', 'directory-moved', 'registry-committed'];
|
|
78
111
|
const DEFAULT_IO: LifecycleFileSystem = {
|
|
79
112
|
renameSync: (source, target) => fs.renameSync(source, target),
|
|
@@ -172,10 +205,16 @@ function matchingCompletion(content: string, request: TaskLifecycleRequest, rest
|
|
|
172
205
|
}
|
|
173
206
|
|
|
174
207
|
function validateRestoreStaging(request: Extract<TaskLifecycleRequest, { intent: 'restore' }>, repoRoot: string, taskId: string): LifecycleError | { stagingDir: string; fileCount: number } {
|
|
175
|
-
|
|
176
|
-
|
|
208
|
+
let workspace: string;
|
|
209
|
+
let stagingDir: string;
|
|
210
|
+
try {
|
|
211
|
+
workspace = fs.realpathSync.native(path.resolve(repoRoot, '.agents', 'workspace'));
|
|
212
|
+
stagingDir = fs.realpathSync.native(path.resolve(request.stagingDir));
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return { code: 'LIFECYCLE_STAGING_INVALID', message: String(error) };
|
|
215
|
+
}
|
|
177
216
|
const relative = path.relative(workspace, stagingDir);
|
|
178
|
-
if (!relative || relative.startsWith(
|
|
217
|
+
if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative) || path.basename(stagingDir) === taskId) {
|
|
179
218
|
return { code: 'LIFECYCLE_STAGING_INVALID', message: 'restore staging must be a non-final directory inside .agents/workspace' };
|
|
180
219
|
}
|
|
181
220
|
let entries: fs.Dirent[];
|
|
@@ -213,8 +252,8 @@ function validateRestoreStaging(request: Extract<TaskLifecycleRequest, { intent:
|
|
|
213
252
|
return { stagingDir, fileCount: entries.filter((entry) => !entry.name.startsWith('.')).length };
|
|
214
253
|
}
|
|
215
254
|
|
|
216
|
-
function mutationsFor(request: TaskLifecycleRequest, content: string, metadata: TaskWriteMetadata, restoredFiles: number): TaskMutation[] | LifecycleError {
|
|
217
|
-
const section = locateActivityLog(content);
|
|
255
|
+
function mutationsFor(request: TaskLifecycleRequest, content: string, metadata: TaskWriteMetadata, restoredFiles: number, allowMissingActivityLog = false): TaskMutation[] | LifecycleError {
|
|
256
|
+
const section = locateActivityLog(content) ?? (allowMissingActivityLog ? { heading: 'Activity Log', body: '', entries: [] } : null);
|
|
218
257
|
if (!section) return { code: 'LIFECYCLE_LOG_MISSING', message: 'task has no unique Activity Log section' };
|
|
219
258
|
const identity = actionAndNote(request, restoredFiles);
|
|
220
259
|
let body = appendActivityEntry(section, { time: metadata.timestamp, step: `${identity.action} [started]`, agent: request.agent, note: 'started' });
|
|
@@ -261,6 +300,14 @@ function writeJournal(file: string, journal: LifecycleJournal, io: LifecycleFile
|
|
|
261
300
|
}
|
|
262
301
|
}
|
|
263
302
|
|
|
303
|
+
function rememberJournalFailure(file: string, journal: LifecycleJournal, io: LifecycleFileSystem, failure: LifecycleError): void {
|
|
304
|
+
try {
|
|
305
|
+
writeJournal(file, { ...journal, failure }, io);
|
|
306
|
+
} catch {
|
|
307
|
+
// Preserve the original lifecycle failure when the diagnostic receipt cannot be updated.
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
264
311
|
function parseJournal(content: string): LifecycleJournal {
|
|
265
312
|
const value = JSON.parse(content) as Partial<LifecycleJournal>;
|
|
266
313
|
if (
|
|
@@ -326,7 +373,7 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
|
|
|
326
373
|
}
|
|
327
374
|
return failed(request, { code: 'LIFECYCLE_INTENT_CONFLICT', message: `task ${taskId} is already ${spec.target} with a different lifecycle intent` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
328
375
|
}
|
|
329
|
-
if (!spec.sources.includes(sourceState) && !existingJournal) {
|
|
376
|
+
if (!spec.sources.includes(sourceState) && !existingJournal && !allowsManualOverride(options, 'LIFECYCLE_SOURCE_INVALID')) {
|
|
330
377
|
return failed(request, { code: 'LIFECYCLE_SOURCE_INVALID', message: `${request.intent} is not allowed from ${sourceState}` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
331
378
|
}
|
|
332
379
|
if (sourcePath !== targetPath && fs.existsSync(targetPath)) {
|
|
@@ -337,7 +384,7 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
|
|
|
337
384
|
try { frontmatter = parseTypedTaskFrontmatter(content); }
|
|
338
385
|
catch (error) { return failed(request, { code: 'LIFECYCLE_DOCUMENT_INVALID', message: error instanceof Error ? error.message : String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath }); }
|
|
339
386
|
if (frontmatter.id !== taskId) return failed(request, { code: 'LIFECYCLE_IDENTITY_INVALID', message: 'task.md id does not match its lifecycle task id' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
340
|
-
if (!locateActivityLog(content)) return failed(request, { code: 'LIFECYCLE_LOG_MISSING', message: 'task has no unique Activity Log section' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
387
|
+
if (!locateActivityLog(content) && !allowsManualOverride(options, 'LIFECYCLE_LOG_MISSING')) return failed(request, { code: 'LIFECYCLE_LOG_MISSING', message: 'task has no unique Activity Log section' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
341
388
|
const registryRequest = executeShortIdCommand({
|
|
342
389
|
operation: 'list', activeDir: path.join(repoRoot, '.agents', 'workspace', 'active'),
|
|
343
390
|
shortIdLength: configuredShortIdLength(repoRoot)
|
|
@@ -347,10 +394,10 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
|
|
|
347
394
|
}
|
|
348
395
|
const registry = JSON.parse(registryRequest.output || '{"version":1,"ids":{}}') as { ids: Record<string, string> };
|
|
349
396
|
const registeredKey = Object.entries(registry.ids).find(([, candidate]) => candidate === taskId)?.[0];
|
|
350
|
-
if (spec.registry === 'release' && sourceState === 'active' && !registeredKey) {
|
|
397
|
+
if (spec.registry === 'release' && sourceState === 'active' && !registeredKey && !allowsManualOverride(options, 'LIFECYCLE_SHORT_ID_PRECONDITION')) {
|
|
351
398
|
return failed(request, { code: 'LIFECYCLE_SHORT_ID_PRECONDITION', message: 'active source has no short-id registry entry' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
352
399
|
}
|
|
353
|
-
if (spec.registry === 'alloc' && !registeredKey && Object.keys(registry.ids).length >= 10 ** configuredShortIdLength(repoRoot) - 1) {
|
|
400
|
+
if (spec.registry === 'alloc' && !registeredKey && Object.keys(registry.ids).length >= 10 ** configuredShortIdLength(repoRoot) - 1 && !allowsManualOverride(options, 'SHORT_ID_CAPACITY_EXCEEDED')) {
|
|
354
401
|
return failed(request, { code: 'SHORT_ID_CAPACITY_EXCEEDED', message: 'short-id registry has no free slot' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
355
402
|
}
|
|
356
403
|
const digest = intentIdentity(request, taskId);
|
|
@@ -360,14 +407,20 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
|
|
|
360
407
|
if (fs.existsSync(initialJournalPath)) {
|
|
361
408
|
try { journal = parseJournal(io.readFileSync(initialJournalPath)); }
|
|
362
409
|
catch (error) { return failed(request, { code: 'LIFECYCLE_JOURNAL_INVALID', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath }); }
|
|
363
|
-
if (journal.
|
|
410
|
+
if (journal.taskId !== taskId || journal.intent !== request.intent) return failed(request, { code: 'LIFECYCLE_INTENT_CONFLICT', message: 'an in-progress lifecycle journal belongs to a different request' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath });
|
|
411
|
+
if (request.dryRun) {
|
|
412
|
+
if (!journal.failure) return failed(request, { code: 'LIFECYCLE_JOURNAL_INVALID', message: 'existing lifecycle journal has no durable failure evidence for a read-only probe' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath });
|
|
413
|
+
return failed(request, journal.failure, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath, completedSteps: journal.completedSteps, pendingSteps: STEPS.filter((step) => !journal.completedSteps.includes(step)) });
|
|
414
|
+
}
|
|
415
|
+
const overrideResumesRecordedFailure = journal.failure && allowsManualOverride(options, journal.failure.code);
|
|
416
|
+
if (journal.intentDigest !== digest && !overrideResumesRecordedFailure) return failed(request, { code: 'LIFECYCLE_INTENT_CONFLICT', message: 'an in-progress lifecycle journal belongs to a different request' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath });
|
|
364
417
|
} else {
|
|
365
418
|
let metadata: TaskWriteMetadata;
|
|
366
419
|
try { metadata = (options.metadataProvider ?? captureTaskWriteMetadata)(); }
|
|
367
420
|
catch (error) { return failed(request, { code: 'LIFECYCLE_METADATA_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath }); }
|
|
368
421
|
journal = { version: 1, taskId, intent: request.intent, intentDigest: digest, sourceState, targetState: spec.target, metadata, completedSteps: [] };
|
|
369
422
|
if (request.dryRun) {
|
|
370
|
-
const plannedMutations = mutationsFor(request, content, metadata, restoredFiles);
|
|
423
|
+
const plannedMutations = mutationsFor(request, content, metadata, restoredFiles, allowsManualOverride(options, 'LIFECYCLE_LOG_MISSING'));
|
|
371
424
|
if ('code' in plannedMutations) return failed(request, plannedMutations, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
372
425
|
const taskPlan = writeTask(
|
|
373
426
|
{ taskRef: taskId, expectedState: sourceState === 'blocked' ? 'blocked' : 'active', mutations: plannedMutations, dryRun: true },
|
|
@@ -403,7 +456,7 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
|
|
|
403
456
|
if (matchingCompletion(io.readFileSync(path.join(sourcePath, 'task.md')), request, restoredFiles)) completed.add('task-written');
|
|
404
457
|
if (!completed.has('task-written')) {
|
|
405
458
|
const current = io.readFileSync(path.join(sourcePath, 'task.md'));
|
|
406
|
-
const mutations = mutationsFor(request, current, journal.metadata, restoredFiles);
|
|
459
|
+
const mutations = mutationsFor(request, current, journal.metadata, restoredFiles, allowsManualOverride(options, 'LIFECYCLE_LOG_MISSING'));
|
|
407
460
|
if ('code' in mutations) return failed(request, mutations, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, completedSteps: [...completed], changed: completed.size > 0 });
|
|
408
461
|
const writeResult = writeTask(
|
|
409
462
|
{ taskRef: taskId, expectedState: sourceState === 'blocked' ? 'blocked' : 'active', mutations },
|
|
@@ -424,7 +477,11 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
|
|
|
424
477
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
425
478
|
if (fs.existsSync(targetPath)) return failed(request, { code: 'LIFECYCLE_TARGET_CONFLICT', message: `target directory already exists: ${targetPath}` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
|
|
426
479
|
try { (options.directoryRenameSync ?? io.renameSync)(sourcePath, targetPath); }
|
|
427
|
-
catch (error) {
|
|
480
|
+
catch (error) {
|
|
481
|
+
const failure = { code: 'LIFECYCLE_DIRECTORY_RENAME_FAILED', message: String(error) } as const;
|
|
482
|
+
rememberJournalFailure(journalPath, journal, io, failure);
|
|
483
|
+
return failed(request, failure, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
|
|
484
|
+
}
|
|
428
485
|
completed.add('directory-moved'); journal.completedSteps = [...completed]; journalPath = path.join(targetPath, '.task-lifecycle.json');
|
|
429
486
|
try { writeJournal(journalPath, journal, io); }
|
|
430
487
|
catch (error) { return failed(request, { code: 'LIFECYCLE_JOURNAL_WRITE_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, directory: { effect: 'move', changed: true }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion }); }
|
|
@@ -451,7 +508,7 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
|
|
|
451
508
|
};
|
|
452
509
|
}
|
|
453
510
|
|
|
454
|
-
export { lifecycleIntentCatalog, applyTaskLifecycle };
|
|
511
|
+
export { lifecycleIntentCatalog, lifecycleFailureCatalog, lifecycleProducerCatalog, applyTaskLifecycle };
|
|
455
512
|
export type {
|
|
456
513
|
TaskLifecycleIntent, TaskLifecycleRequest, TaskLifecycleResult, TaskLifecycleOptions,
|
|
457
514
|
LifecycleError, LifecycleJournal, LifecycleStep
|