@fitlab-ai/agent-infra 0.9.15 → 0.11.0
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 +2 -6
- package/README.zh-CN.md +2 -6
- package/bin/internal-cli.sh +0 -13
- package/dist/bin/internal-cli.js +11 -49
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +6 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
- package/dist/lib/fs/durable-write.js +2 -0
- package/dist/lib/internal/cli-route-inventory.js +3 -4
- package/dist/lib/internal/manual-validation.js +17 -8
- package/dist/lib/internal/platform-comment.js +60 -2
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-lifecycle.js +4 -4
- package/dist/lib/internal/task-operation-registry.js +22 -13
- package/dist/lib/internal/task-orchestration.js +2 -2
- package/dist/lib/internal/task-review.js +12 -7
- package/dist/lib/platform/activity-recovery.js +159 -0
- package/dist/lib/platform/issue-comments.js +89 -31
- package/dist/lib/platform/issues.js +1 -1
- package/dist/lib/platform/pr-change-report.js +5 -4
- package/dist/lib/platform/pr-summary.js +64 -32
- package/dist/lib/platform/pull-requests.js +3 -3
- package/dist/lib/platform/task-comment-projection.js +70 -0
- package/dist/lib/platform/task-identities.js +2 -9
- package/dist/lib/platform/task-recovery.js +55 -0
- package/dist/lib/platform/verification-sync.js +45 -7
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/sandbox/commands/rm.js +21 -2
- package/dist/lib/sandbox/control/executor.js +4 -2
- package/dist/lib/sandbox/control/server.js +29 -9
- package/dist/lib/sandbox/control/state.js +7 -1
- package/dist/lib/sandbox/control/task-workflow.js +1 -1
- package/dist/lib/sandbox/control/workflow-executor.js +87 -35
- package/dist/lib/sandbox/index.js +1 -2
- package/dist/lib/sandbox/removal.js +116 -1011
- package/dist/lib/sandbox/workspace-identity.js +17 -1
- package/dist/lib/server/process-state.js +17 -5
- package/dist/lib/task/artifact-command.js +11 -30
- package/dist/lib/task/artifact-operations.js +17 -231
- package/dist/lib/task/artifact-recovery.js +476 -0
- package/dist/lib/task/artifact-repair-intent.js +90 -39
- package/dist/lib/task/commit-operation.js +31 -4
- package/dist/lib/task/control-authority.js +39 -8
- package/dist/lib/task/control-recovery.js +14 -2
- package/dist/lib/task/create-service.js +1 -8
- package/dist/lib/task/delegation-receipts.js +64 -3
- package/dist/lib/task/events.js +165 -23
- package/dist/lib/task/human-override.js +5 -8
- package/dist/lib/task/intermediate-cleanup.js +91 -8
- package/dist/lib/task/lifecycle-recovery.js +549 -0
- package/dist/lib/task/lifecycle.js +12 -3
- package/dist/lib/task/local-artifact-finalization.js +212 -123
- package/dist/lib/task/manual-validation-receipt.js +5 -1
- package/dist/lib/task/orchestration.js +52 -2
- package/dist/lib/task/pr-delivery-fact.js +12 -56
- package/dist/lib/task/recovery-warning.js +19 -0
- package/dist/lib/task/review-command.js +3 -2
- package/dist/lib/task/review-finalization.js +294 -74
- package/dist/lib/{host-control → task}/secure-fs.js +8 -19
- package/dist/lib/task/short-id.js +15 -7
- package/dist/lib/task/task-execution-lock.js +1 -1
- package/dist/lib/task/verification-engine.js +3 -14
- package/dist/lib/task/workflow-command.js +2 -1
- package/dist/lib/task/workflow-dispatch.js +161 -0
- package/dist/lib/version.js +1 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +7 -2
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
- package/lib/fs/durable-write.ts +1 -0
- package/lib/internal/cli-route-inventory.ts +3 -4
- package/lib/internal/manual-validation.ts +17 -8
- package/lib/internal/platform-comment.ts +53 -2
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +1 -1
- package/lib/internal/task-lifecycle.ts +5 -4
- package/lib/internal/task-operation-registry.ts +22 -13
- package/lib/internal/task-orchestration.ts +1 -1
- package/lib/internal/task-review.ts +11 -7
- package/lib/platform/activity-recovery.ts +179 -0
- package/lib/platform/issue-comments.ts +105 -30
- package/lib/platform/issues.ts +1 -1
- package/lib/platform/pr-change-report.ts +7 -5
- package/lib/platform/pr-summary.ts +64 -32
- package/lib/platform/pull-requests.ts +3 -3
- package/lib/platform/task-comment-projection.ts +79 -0
- package/lib/platform/task-identities.ts +3 -14
- package/lib/platform/task-recovery.ts +54 -0
- package/lib/platform/verification-sync.ts +48 -11
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/sandbox/commands/rm.ts +34 -2
- package/lib/sandbox/control/executor.ts +4 -2
- package/lib/sandbox/control/protocol.ts +6 -1
- package/lib/sandbox/control/server.ts +30 -10
- package/lib/sandbox/control/state.ts +9 -1
- package/lib/sandbox/control/task-workflow.ts +1 -1
- package/lib/sandbox/control/workflow-executor.ts +106 -35
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/removal.ts +126 -1128
- package/lib/sandbox/workspace-identity.ts +19 -3
- package/lib/server/process-state.ts +26 -5
- package/lib/task/artifact-command.ts +12 -29
- package/lib/task/artifact-operations.ts +22 -252
- package/lib/task/artifact-recovery.ts +572 -0
- package/lib/task/artifact-repair-intent.ts +126 -51
- package/lib/task/commit-operation.ts +35 -4
- package/lib/task/control-authority.ts +48 -15
- package/lib/task/control-recovery.ts +13 -1
- package/lib/task/create-service.ts +1 -10
- package/lib/task/delegation-receipts.ts +73 -2
- package/lib/task/events.ts +188 -24
- package/lib/task/human-override.ts +5 -8
- package/lib/task/intermediate-cleanup.ts +66 -12
- package/lib/task/lifecycle-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +13 -4
- package/lib/task/local-artifact-finalization.ts +249 -131
- package/lib/task/manual-validation-receipt.ts +5 -1
- package/lib/task/orchestration.ts +65 -0
- package/lib/task/pr-delivery-fact.ts +15 -62
- package/lib/task/recovery-warning.ts +21 -0
- package/lib/task/review-command.ts +2 -1
- package/lib/task/review-finalization.ts +298 -84
- package/lib/{host-control → task}/secure-fs.ts +10 -32
- package/lib/task/short-id.ts +27 -5
- package/lib/task/task-execution-lock.ts +1 -1
- package/lib/task/verification-engine.ts +3 -17
- package/lib/task/verification-types.ts +0 -2
- package/lib/task/workflow-command.ts +2 -1
- package/lib/task/workflow-dispatch.ts +171 -0
- package/lib/version.ts +1 -15
- package/package.json +1 -1
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +12 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +13 -5
- package/templates/.agents/rules/local-artifact-repair.en.md +47 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +45 -36
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- 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/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- 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 +2 -2
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/config/verify.json +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-issue/config/verify.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/restore-task/SKILL.en.md +11 -6
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +11 -6
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -4
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/import-issue.en.md +1 -1
- package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
- package/templates/.claude/commands/restore-task.en.md +1 -1
- package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
- package/dist/lib/host-control/audit.js +0 -19
- package/dist/lib/host-control/client.js +0 -142
- package/dist/lib/host-control/command.js +0 -49
- package/dist/lib/host-control/path.js +0 -160
- package/dist/lib/host-control/server.js +0 -159
- package/dist/lib/host-control/service.js +0 -112
- package/dist/lib/internal/host-control.js +0 -28
- package/lib/host-control/audit.ts +0 -22
- package/lib/host-control/client.ts +0 -172
- package/lib/host-control/command.ts +0 -60
- package/lib/host-control/path.ts +0 -167
- package/lib/host-control/server.ts +0 -180
- package/lib/host-control/service.ts +0 -133
- package/lib/internal/host-control.ts +0 -28
package/lib/task/short-id.ts
CHANGED
|
@@ -329,14 +329,18 @@ function mutateShortIdRegistryAt(
|
|
|
329
329
|
activeDir: string,
|
|
330
330
|
width: number,
|
|
331
331
|
taskId: string,
|
|
332
|
-
requested: ShortIdMutationEffect
|
|
332
|
+
requested: ShortIdMutationEffect,
|
|
333
|
+
onlyReleaseWhenInactive = false,
|
|
334
|
+
pruneOrphans = true
|
|
333
335
|
): ShortIdMutationResult {
|
|
334
336
|
const registryPath = path.join(activeDir, REGISTRY_NAME);
|
|
335
337
|
return withRegistryLock(activeDir, () => {
|
|
336
338
|
const registry = readRegistryStrict(registryPath, width);
|
|
337
339
|
const original = JSON.stringify(registry.ids);
|
|
338
|
-
|
|
339
|
-
|
|
340
|
+
if (pruneOrphans) {
|
|
341
|
+
for (const [key, candidate] of Object.entries(registry.ids)) {
|
|
342
|
+
if (candidate !== taskId && !fs.existsSync(path.join(activeDir, candidate, 'task.md'))) delete registry.ids[key];
|
|
343
|
+
}
|
|
340
344
|
}
|
|
341
345
|
const existing = Object.entries(registry.ids).find(([, candidate]) => candidate === taskId);
|
|
342
346
|
let result: ShortIdMutationResult;
|
|
@@ -356,8 +360,13 @@ function mutateShortIdRegistryAt(
|
|
|
356
360
|
result = { effect: 'allocated', shortId: key, changed: true };
|
|
357
361
|
}
|
|
358
362
|
} else if (requested === 'release') {
|
|
359
|
-
|
|
360
|
-
|
|
363
|
+
const taskIsActive = fs.existsSync(path.join(activeDir, taskId, 'task.md'));
|
|
364
|
+
if (!existing || (onlyReleaseWhenInactive && taskIsActive)) {
|
|
365
|
+
result = { effect: 'unchanged', shortId: existing?.[0] ?? null, changed: false };
|
|
366
|
+
} else {
|
|
367
|
+
delete registry.ids[existing[0]];
|
|
368
|
+
result = { effect: 'released', shortId: existing[0], changed: true };
|
|
369
|
+
}
|
|
361
370
|
} else result = { effect: 'unchanged', shortId: existing ? existing[0] : null, changed: false };
|
|
362
371
|
const changed = original !== JSON.stringify(registry.ids);
|
|
363
372
|
if (changed) writeRegistry(registryPath, registry);
|
|
@@ -365,6 +374,18 @@ function mutateShortIdRegistryAt(
|
|
|
365
374
|
});
|
|
366
375
|
}
|
|
367
376
|
|
|
377
|
+
function releaseStaleShortIdRegistry(repoRoot: string, taskId: string): ShortIdMutationResult {
|
|
378
|
+
const activeDir = path.join(repoRoot, '.agents', 'workspace', 'active');
|
|
379
|
+
return mutateShortIdRegistryAt(
|
|
380
|
+
activeDir,
|
|
381
|
+
configuredShortIdLength(repoRoot),
|
|
382
|
+
taskId,
|
|
383
|
+
'release',
|
|
384
|
+
true,
|
|
385
|
+
false
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
368
389
|
function executeShortIdCommand(request: ShortIdCommandRequest): ShortIdCommandResult {
|
|
369
390
|
try {
|
|
370
391
|
if (!Number.isInteger(request.shortIdLength) || request.shortIdLength < 1) {
|
|
@@ -550,6 +571,7 @@ export {
|
|
|
550
571
|
inspectShortIdRegistry,
|
|
551
572
|
configuredShortIdLength,
|
|
552
573
|
mutateShortIdRegistry,
|
|
574
|
+
releaseStaleShortIdRegistry,
|
|
553
575
|
executeShortIdCommand
|
|
554
576
|
};
|
|
555
577
|
export type {
|
|
@@ -293,7 +293,7 @@ function withTaskExecutionLock<T>(
|
|
|
293
293
|
return value;
|
|
294
294
|
} catch (error) {
|
|
295
295
|
if (error instanceof TaskExecutionLockError) throw error;
|
|
296
|
-
if (error instanceof Error && error.name
|
|
296
|
+
if (error instanceof Error && ['OrchestrationStateError', 'ArtifactRecoveryError'].includes(error.name)) throw error;
|
|
297
297
|
throw new TaskExecutionLockError(
|
|
298
298
|
'ORCHESTRATION_LOCK_FAILED',
|
|
299
299
|
'lifecycle task lock operation failed',
|
|
@@ -48,6 +48,7 @@ import { manualValidationFinalSummaryProjectionMatches } from "./manual-validati
|
|
|
48
48
|
import { readManualValidationCompletion } from "./manual-validation-completion.ts";
|
|
49
49
|
import { sha256File } from "./artifact-receipts.ts";
|
|
50
50
|
import { summaryCommentState } from "../platform/pr-summary.ts";
|
|
51
|
+
import { taskIssueIdentity } from "../platform/task-identities.ts";
|
|
51
52
|
|
|
52
53
|
const TASK_ENUMS = {
|
|
53
54
|
type: ["feature", "bugfix", "refactor", "docs", "chore"],
|
|
@@ -100,8 +101,6 @@ const sharedUtils = {
|
|
|
100
101
|
failResult,
|
|
101
102
|
blockedResult,
|
|
102
103
|
safeStat,
|
|
103
|
-
parseIssueNumber,
|
|
104
|
-
parsePrNumber
|
|
105
104
|
};
|
|
106
105
|
|
|
107
106
|
async function runCheck(type: any, context: any, shared: any): Promise<any> {
|
|
@@ -361,8 +360,8 @@ function checkTaskMeta({ taskDir, config, repositoryRoot }: any): any {
|
|
|
361
360
|
);
|
|
362
361
|
}
|
|
363
362
|
|
|
364
|
-
if (config.
|
|
365
|
-
return failResult("task-meta", "Expected a valid
|
|
363
|
+
if (config.require_platform_issue_identity && !taskIssueIdentity(metadata)) {
|
|
364
|
+
return failResult("task-meta", "Expected a valid platform_issue_identity in task metadata");
|
|
366
365
|
}
|
|
367
366
|
|
|
368
367
|
if (config.require_completed_at && isBlank(metadata.completed_at)) {
|
|
@@ -1376,19 +1375,6 @@ function getCheckedRequirements(content: any): any {
|
|
|
1376
1375
|
.map((match: any) => match[1].trim());
|
|
1377
1376
|
}
|
|
1378
1377
|
|
|
1379
|
-
function parseIssueNumber(value: any): any {
|
|
1380
|
-
if (isBlank(value) || value === "N/A") {
|
|
1381
|
-
return null;
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
const match = String(value).match(/\d+/);
|
|
1385
|
-
return match ? Number(match[0]) : null;
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
function parsePrNumber(value: any): any {
|
|
1389
|
-
return parseIssueNumber(value);
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
1378
|
// === Utilities ===
|
|
1393
1379
|
|
|
1394
1380
|
function normalizeContent(text: any): any {
|
|
@@ -11,8 +11,6 @@ export type VerificationShared = {
|
|
|
11
11
|
failResult: (type: string, message: string, failType?: string) => VerificationCheckResult;
|
|
12
12
|
blockedResult: (type: string, message: string, failType?: string) => VerificationCheckResult;
|
|
13
13
|
safeStat: (filePath: string) => fs.Stats | null;
|
|
14
|
-
parseIssueNumber: (value: unknown) => number | null;
|
|
15
|
-
parsePrNumber: (value: unknown) => number | null;
|
|
16
14
|
};
|
|
17
15
|
|
|
18
16
|
type VerificationStatus = 'pass' | 'fail' | 'blocked';
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export const TASK_WORKFLOW_COMMANDS = Object.freeze({
|
|
2
2
|
'artifact-inspect': ['task-artifact', 'inspect'],
|
|
3
3
|
'artifact-init': ['task-artifact', 'init'],
|
|
4
|
-
'artifact-
|
|
4
|
+
'artifact-preflight': ['task-artifact', 'preflight'],
|
|
5
5
|
'artifact-finalize-local': ['task-artifact', 'finalize-local'],
|
|
6
|
+
'review-preflight': ['task-review', 'preflight'],
|
|
6
7
|
'review-finalize-summary': ['task-review', 'finalize-summary'],
|
|
7
8
|
event: ['task-event', null],
|
|
8
9
|
'ledger-finding-response': ['task-ledger', 'finding-respond'],
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { reconcileTaskInvalidation } from './invalidation-command.ts';
|
|
2
|
+
import { applyLedgerIntent } from './ledger-intents.ts';
|
|
3
|
+
import type { LedgerIntent } from './ledger-intents.ts';
|
|
4
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
5
|
+
import type { TaskWorkflowOperation } from './workflow-command.ts';
|
|
6
|
+
import { applyWorkflowWarningIntent } from './workflow-warning-intents.ts';
|
|
7
|
+
import type { WorkflowWarningIntent } from './workflow-warning-intents.ts';
|
|
8
|
+
|
|
9
|
+
export type WorkflowDispatchResult = Readonly<Record<string, unknown>>;
|
|
10
|
+
|
|
11
|
+
type OptionValues = Readonly<Record<string, string | boolean>>;
|
|
12
|
+
type DispatchFailure = Readonly<{
|
|
13
|
+
status: 'failed';
|
|
14
|
+
changed: false;
|
|
15
|
+
error: Readonly<{ code: string; message: string }>;
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
function failed(code: string, message: string): DispatchFailure {
|
|
19
|
+
return { status: 'failed', changed: false, error: { code, message } };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isFailure(value: unknown): value is DispatchFailure {
|
|
23
|
+
return Boolean(value) && typeof value === 'object' && (value as { status?: unknown }).status === 'failed';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function options(
|
|
27
|
+
args: readonly string[],
|
|
28
|
+
allowed: Readonly<Record<string, string>>
|
|
29
|
+
): OptionValues | DispatchFailure {
|
|
30
|
+
const values: Record<string, string | boolean> = {};
|
|
31
|
+
for (let index = 2; index < args.length; index += 1) {
|
|
32
|
+
const flag = args[index]!;
|
|
33
|
+
if (flag === '--dry-run') {
|
|
34
|
+
if (!Object.hasOwn(allowed, flag) || values[flag] !== undefined) return failed('TASK_WORKFLOW_PAYLOAD_INVALID', `invalid option '${flag}'`);
|
|
35
|
+
values[flag] = true;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const key = allowed[flag];
|
|
39
|
+
if (!key || values[flag] !== undefined) return failed('TASK_WORKFLOW_PAYLOAD_INVALID', `invalid option '${flag}'`);
|
|
40
|
+
const value = args[++index];
|
|
41
|
+
if (value === undefined || value.startsWith('--')) return failed('TASK_WORKFLOW_PAYLOAD_INVALID', `option '${flag}' requires a value`);
|
|
42
|
+
if (flag === '--needs-implementation' && value !== 'true' && value !== 'false') {
|
|
43
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', "option '--needs-implementation' must be true or false");
|
|
44
|
+
}
|
|
45
|
+
values[flag] = value;
|
|
46
|
+
}
|
|
47
|
+
return values;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function value(values: OptionValues, flag: string): string | undefined {
|
|
51
|
+
const candidate = values[flag];
|
|
52
|
+
return typeof candidate === 'string' ? candidate : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function required(values: OptionValues, flags: readonly string[]): DispatchFailure | null {
|
|
56
|
+
const missing = flags.find((flag) => value(values, flag) === undefined);
|
|
57
|
+
return missing ? failed('TASK_WORKFLOW_PAYLOAD_INVALID', `option '${missing}' is required`) : null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resultForLock(error: unknown): WorkflowDispatchResult {
|
|
61
|
+
if (error instanceof TaskExecutionLockError) return failed(error.code, error.message);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function ledgerIntent(operation: TaskWorkflowOperation, args: readonly string[]): LedgerIntent | DispatchFailure {
|
|
66
|
+
const [taskRef, kind] = args;
|
|
67
|
+
const mappings: Partial<Record<TaskWorkflowOperation, {
|
|
68
|
+
kind: LedgerIntent['kind']; required: readonly string[]; flags: Readonly<Record<string, string>>;
|
|
69
|
+
}>> = {
|
|
70
|
+
'ledger-finding-response': {
|
|
71
|
+
kind: 'finding-respond', required: ['--id', '--round', '--status', '--evidence'],
|
|
72
|
+
flags: { '--id': 'id', '--round': 'round', '--status': 'status', '--evidence': 'evidence', '--dry-run': 'dryRun' }
|
|
73
|
+
},
|
|
74
|
+
'ledger-finding-review': {
|
|
75
|
+
kind: 'finding-review', required: ['--id', '--status', '--evidence'],
|
|
76
|
+
flags: { '--id': 'id', '--status': 'status', '--evidence': 'evidence', '--needs-implementation': 'needsImplementation', '--dry-run': 'dryRun' }
|
|
77
|
+
},
|
|
78
|
+
'ledger-finding-upsert': {
|
|
79
|
+
kind: 'finding-upsert', required: ['--stage', '--review-artifact', '--ordinal', '--severity', '--evidence'],
|
|
80
|
+
flags: { '--stage': 'stage', '--review-artifact': 'reviewArtifact', '--ordinal': 'ordinal', '--severity': 'severity', '--evidence': 'evidence', '--dry-run': 'dryRun' }
|
|
81
|
+
},
|
|
82
|
+
'decision-next-id': { kind: 'decision-next-id', required: [], flags: {} },
|
|
83
|
+
'decision-upsert': {
|
|
84
|
+
kind: 'decision-upsert', required: ['--id', '--stage', '--artifact'],
|
|
85
|
+
flags: { '--id': 'id', '--stage': 'stage', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation', '--dry-run': 'dryRun' }
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const mapping = mappings[operation];
|
|
89
|
+
if (!mapping || !taskRef || kind === undefined) return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'task workflow ledger request is invalid');
|
|
90
|
+
const values = options(args, mapping.flags);
|
|
91
|
+
if (isFailure(values)) return values;
|
|
92
|
+
const missing = required(values, mapping.required);
|
|
93
|
+
if (missing) return missing;
|
|
94
|
+
const dryRun = values['--dry-run'] === true;
|
|
95
|
+
if (mapping.kind === 'decision-next-id') return { kind: mapping.kind, taskRef };
|
|
96
|
+
if (mapping.kind === 'finding-respond') return {
|
|
97
|
+
kind: mapping.kind, taskRef, id: value(values, '--id')!, round: Number(value(values, '--round')),
|
|
98
|
+
status: value(values, '--status') as never, evidence: value(values, '--evidence')!, ...(dryRun ? { dryRun } : {})
|
|
99
|
+
} as LedgerIntent;
|
|
100
|
+
if (mapping.kind === 'finding-review') return {
|
|
101
|
+
kind: mapping.kind, taskRef, id: value(values, '--id')!, status: value(values, '--status') as never,
|
|
102
|
+
evidence: value(values, '--evidence')!, ...(value(values, '--needs-implementation') === 'true' ? { needsImplementation: true } : {}),
|
|
103
|
+
...(dryRun ? { dryRun } : {})
|
|
104
|
+
} as LedgerIntent;
|
|
105
|
+
if (mapping.kind === 'finding-upsert') return {
|
|
106
|
+
kind: mapping.kind, taskRef, stage: value(values, '--stage') as never, reviewArtifact: value(values, '--review-artifact')!,
|
|
107
|
+
ordinal: Number(value(values, '--ordinal')), severity: value(values, '--severity') as never, evidence: value(values, '--evidence')!,
|
|
108
|
+
...(dryRun ? { dryRun } : {})
|
|
109
|
+
} as LedgerIntent;
|
|
110
|
+
return {
|
|
111
|
+
kind: mapping.kind, taskRef, id: value(values, '--id')!, stage: value(values, '--stage') as never,
|
|
112
|
+
artifact: value(values, '--artifact')!, ...(value(values, '--needs-implementation') === 'true' ? { needsImplementation: true } : {}),
|
|
113
|
+
...(dryRun ? { dryRun } : {})
|
|
114
|
+
} as LedgerIntent;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function warningIntent(args: readonly string[]): WorkflowWarningIntent | DispatchFailure {
|
|
118
|
+
const [taskRef, kind] = args;
|
|
119
|
+
if (!taskRef || kind !== 'add') return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'task workflow warning request is invalid');
|
|
120
|
+
const values = options(args, {
|
|
121
|
+
'--step': 'step', '--severity': 'severity', '--code': 'code', '--target': 'target',
|
|
122
|
+
'--message': 'message', '--action': 'action', '--dry-run': 'dryRun'
|
|
123
|
+
});
|
|
124
|
+
if (isFailure(values)) return values;
|
|
125
|
+
const missing = required(values, ['--step', '--severity', '--code', '--target', '--message', '--action']);
|
|
126
|
+
if (missing) return missing;
|
|
127
|
+
return {
|
|
128
|
+
kind: 'add', taskRef, step: value(values, '--step')!, severity: value(values, '--severity') as never,
|
|
129
|
+
code: value(values, '--code')!, target: value(values, '--target')!, message: value(values, '--message')!,
|
|
130
|
+
action: value(values, '--action')!, ...(values['--dry-run'] === true ? { dryRun: true } : {})
|
|
131
|
+
} as WorkflowWarningIntent;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The caller has already selected and authorized the execution boundary.
|
|
136
|
+
* This function deliberately invokes task domains in that process: starting a
|
|
137
|
+
* second CLI process would discard the broker binding and could misclassify a
|
|
138
|
+
* sandbox process with cleared environment variables as direct-host.
|
|
139
|
+
*/
|
|
140
|
+
export function dispatchWorkflowCommand(
|
|
141
|
+
repoRoot: string,
|
|
142
|
+
operation: TaskWorkflowOperation,
|
|
143
|
+
args: readonly string[]
|
|
144
|
+
): WorkflowDispatchResult {
|
|
145
|
+
const taskRef = args[0];
|
|
146
|
+
if (!taskRef) return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'task ref is required');
|
|
147
|
+
try {
|
|
148
|
+
if (operation === 'invalidation-reconcile') {
|
|
149
|
+
const values = options(args, { '--max-targets': 'maxTargets', '--dry-run': 'dryRun' });
|
|
150
|
+
if (isFailure(values)) return values;
|
|
151
|
+
const maxTargets = value(values, '--max-targets');
|
|
152
|
+
if (maxTargets !== undefined && (!Number.isSafeInteger(Number(maxTargets)) || Number(maxTargets) < 1)) {
|
|
153
|
+
return failed('TASK_WORKFLOW_PAYLOAD_INVALID', '--max-targets must be a positive integer');
|
|
154
|
+
}
|
|
155
|
+
return withTaskExecutionLock(repoRoot, taskRef, 'task-invalidation.reconcile', () => reconcileTaskInvalidation(taskRef, {
|
|
156
|
+
repoRoot, ...(maxTargets === undefined ? {} : { maxTargets: Number(maxTargets) }),
|
|
157
|
+
...(values['--dry-run'] === true ? { dryRun: true } : {})
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
if (operation === 'warning-add') {
|
|
161
|
+
const intent = warningIntent(args);
|
|
162
|
+
if (isFailure(intent)) return intent;
|
|
163
|
+
return withTaskExecutionLock(repoRoot, taskRef, 'task-warning.add', () => applyWorkflowWarningIntent(intent, { repoRoot }));
|
|
164
|
+
}
|
|
165
|
+
const intent = ledgerIntent(operation, args);
|
|
166
|
+
if (isFailure(intent)) return intent;
|
|
167
|
+
return withTaskExecutionLock(repoRoot, taskRef, `task-ledger.${intent.kind}`, () => applyLedgerIntent(intent, { repoRoot }));
|
|
168
|
+
} catch (error) {
|
|
169
|
+
return resultForLock(error);
|
|
170
|
+
}
|
|
171
|
+
}
|
package/lib/version.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import semver from 'semver';
|
|
3
2
|
|
|
4
3
|
const packageJsonUrl = [
|
|
5
4
|
new URL('../package.json', import.meta.url),
|
|
@@ -13,22 +12,9 @@ if (!packageJsonUrl) {
|
|
|
13
12
|
const { version } = JSON.parse(readFileSync(packageJsonUrl, 'utf8'));
|
|
14
13
|
const VERSION = `v${version}`;
|
|
15
14
|
const AGENT_INFRA_VERSION_PATTERN = /^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
16
|
-
const LEGACY_COMPATIBILITY_CUTOFF_VERSION = '1.0.0';
|
|
17
15
|
|
|
18
16
|
function isValidAgentInfraVersion(value: unknown): value is string {
|
|
19
17
|
return typeof value === 'string' && AGENT_INFRA_VERSION_PATTERN.test(value);
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
const normalized = runtimeVersion.startsWith('v') ? runtimeVersion.slice(1) : runtimeVersion;
|
|
24
|
-
const parsed = semver.valid(normalized);
|
|
25
|
-
return parsed !== null && semver.lt(parsed, LEGACY_COMPATIBILITY_CUTOFF_VERSION);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function legacyCompatibilityError(resource: string, runtimeVersion = VERSION): Error & { code: string } {
|
|
29
|
-
return Object.assign(new Error(
|
|
30
|
-
`PLATFORM_IDENTITY_LEGACY_UNSUPPORTED: ${resource} requires migration to the current schema before v${LEGACY_COMPATIBILITY_CUTOFF_VERSION} runtime ${runtimeVersion}`
|
|
31
|
-
), { code: 'PLATFORM_IDENTITY_LEGACY_UNSUPPORTED' });
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export { LEGACY_COMPATIBILITY_CUTOFF_VERSION, VERSION, isLegacyCompatibilityEnabled, isValidAgentInfraVersion, legacyCompatibilityError };
|
|
20
|
+
export { VERSION, isValidAgentInfraVersion };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fitlab-ai/agent-infra",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Bootstrap tool for AI multi-tool collaboration infrastructure — works with Claude Code, Codex, Antigravity CLI, and OpenCode",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -9,6 +9,6 @@ agent-infra-internal platform-issue create {task-id} --agent {standard-agent-tok
|
|
|
9
9
|
agent-infra-internal platform-issue sync {task-id} --agent {standard-agent-token} --status waiting-for-triage --assignees current --milestone initial --issue-type --fields
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
The core reads only persisted task identity, title, type, description, and requirements, and reuses deterministic `ai task issue-body` rendering. It owns template selection, upstream/capabilities, duplicate prevention, POST outcome boundaries, response validation, and atomic `
|
|
12
|
+
The core reads only persisted task identity, title, type, description, and requirements, and reuses deterministic `ai task issue-body` rendering. It owns template selection, upstream/capabilities, duplicate prevention, POST outcome boundaries, response validation, and atomic `platform_issue_identity` binding.
|
|
13
13
|
|
|
14
14
|
`planned|applied|no-op|degraded` exit 0, `failed` exits 1, and `blocked` exits 2. Callers must not fall back to direct platform CLI or GraphQL orchestration.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
agent-infra-internal platform-issue create {task-id} --agent {standard-agent-token}
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
命令只从已落盘 task.md 读取身份、标题、类型、描述与需求,并复用 `ai task issue-body` 的确定性正文渲染。它负责模板选择、upstream/capability、已有绑定检查、非幂等 POST 边界、响应身份校验及通过任务写入内核回写 `
|
|
11
|
+
命令只从已落盘 task.md 读取身份、标题、类型、描述与需求,并复用 `ai task issue-body` 的确定性正文渲染。它负责模板选择、upstream/capability、已有绑定检查、非幂等 POST 边界、响应身份校验及通过任务写入内核回写 `platform_issue_identity`。
|
|
12
12
|
|
|
13
13
|
创建后的初始元数据使用同一适配层同步:
|
|
14
14
|
|
|
@@ -11,20 +11,30 @@
|
|
|
11
11
|
| `artifactChunk` | `<!-- sync-issue:{task-id}:{artifact-stem}:{part}/{total} -->` |
|
|
12
12
|
| `summary` | `<!-- sync-issue:{task-id}:summary -->` |
|
|
13
13
|
| `cancel` | `<!-- sync-issue:{task-id}:cancel -->` |
|
|
14
|
+
| `recoveryAction` | `<!-- sync-issue:{task-id}:recovery-action:{action-id} -->` |
|
|
15
|
+
| `recoveryPrepare` | `<!-- sync-issue:{task-id}:recovery-prepare:{commit-id} -->` |
|
|
16
|
+
| `recoveryCommit` | `<!-- sync-issue:{task-id}:recovery-commit:{commit-id} -->` |
|
|
14
17
|
|
|
15
|
-
`pr-review` content is synced only as an Issue artifact comment (via the `artifact` / `artifactChunk` markers) and is never a `restore-task` recovery source; restore still accepts only an Issue
|
|
18
|
+
`pr-review` content is synced only as an Issue artifact comment (via the `artifact` / `artifactChunk` markers) and is never a `restore-task` recovery source; restore still accepts only an Issue token and reads only registered Issue markers, with no PR source.
|
|
16
19
|
|
|
17
20
|
Comments use `platform-comment`; Issue resources use `platform-issue`:
|
|
18
21
|
|
|
19
22
|
```bash
|
|
23
|
+
agent-infra-internal platform-comment list --issue <issue-token> [--cwd <path>]
|
|
24
|
+
agent-infra-internal platform-comment recover --issue <issue-token> --task-id <TASK-id> \
|
|
25
|
+
--output <.agents/workspace/.restore-staging-*/task.md> [--cwd <path>]
|
|
20
26
|
agent-infra-internal platform-issue inspect {task-id}
|
|
21
27
|
agent-infra-internal platform-issue create {task-id} --agent {standard-agent-token}
|
|
22
|
-
agent-infra-internal platform-issue bind {task-id} --issue {
|
|
28
|
+
agent-infra-internal platform-issue bind {task-id} --issue {issue-token} --agent {standard-agent-token}
|
|
23
29
|
agent-infra-internal platform-issue sync {task-id} --agent {standard-agent-token} {desired-state-flags}
|
|
24
30
|
```
|
|
25
31
|
|
|
26
32
|
The core owns status/in labels, assignees, milestones, Issue Type, pinned fields, requirements, state, capabilities, dry-run, retries, errors, and idempotency. Omitted flags preserve values; `none` explicitly clears them. Status labels converge to at most one, and ambiguous requirement identity fails closed.
|
|
27
33
|
|
|
34
|
+
The task comment synchronizes only a deterministic current-task projection. Process sections such as the Activity Log, Artifact Lifecycle Receipts, and invalidation records are carried by the recovery transaction. Byte preflight and platform verification consume the same final rendered UTF-8 body.
|
|
35
|
+
|
|
36
|
+
Recovery comments use `recovery-action`, `recovery-prepare`, and `recovery-commit` markers with a stable recovery id and a body file. Task sync publishes prepare, action, a task snapshot that is written and re-read, and then commit. Replaying identical content is a no-op; conflicting immutable content fails with `RECOVERY_IMMUTABLE_CONFLICT`. Restore accepts only an author-consistent, complete, phase/digest/marker-valid transaction bound to the current task comment body SHA-256.
|
|
37
|
+
|
|
28
38
|
PR event `in:` synchronization uses `agent-infra-internal platform-pr sync-in-labels --pr <N> [--cwd <path>]`. PR files are the event evidence; a unique closing Issue is written and re-read before the PR. Zero or multiple closing Issues update only the PR and return `degraded`; unknown side effects or failed convergence return `blocked` with `IN_LABEL_SYNC_PARTIAL`.
|
|
29
39
|
|
|
30
40
|
`planned|applied|no-op|degraded` exit 0; `failed` exits 1; `blocked` exits 2.
|
|
@@ -13,10 +13,13 @@
|
|
|
13
13
|
| `artifactChunk` | `<!-- sync-issue:{task-id}:{artifact-stem}:{part}/{total} -->` |
|
|
14
14
|
| `summary` | `<!-- sync-issue:{task-id}:summary -->` |
|
|
15
15
|
| `cancel` | `<!-- sync-issue:{task-id}:cancel -->` |
|
|
16
|
+
| `recoveryAction` | `<!-- sync-issue:{task-id}:recovery-action:{action-id} -->` |
|
|
17
|
+
| `recoveryPrepare` | `<!-- sync-issue:{task-id}:recovery-prepare:{commit-id} -->` |
|
|
18
|
+
| `recoveryCommit` | `<!-- sync-issue:{task-id}:recovery-commit:{commit-id} -->` |
|
|
16
19
|
|
|
17
20
|
`prSummary` 属于 `.agents/rules/pr-sync.md`,本规则不实现 PR 聚合。
|
|
18
21
|
|
|
19
|
-
`pr-review` 原文只通过 `artifact` / `artifactChunk` marker 同步为 Issue artifact 评论,不作为 `restore-task` 恢复来源;恢复仍只接受 Issue
|
|
22
|
+
`pr-review` 原文只通过 `artifact` / `artifactChunk` marker 同步为 Issue artifact 评论,不作为 `restore-task` 恢复来源;恢复仍只接受 Issue 原始 token 并只读注册的 Issue marker,不新增 PR 来源。
|
|
20
23
|
|
|
21
24
|
## 平台 intent
|
|
22
25
|
|
|
@@ -24,15 +27,20 @@
|
|
|
24
27
|
|
|
25
28
|
```bash
|
|
26
29
|
agent-infra-internal platform-context resolve [--cwd <path>]
|
|
27
|
-
agent-infra-internal platform-comment list --issue <
|
|
30
|
+
agent-infra-internal platform-comment list --issue <issue-token> [--cwd <path>]
|
|
31
|
+
agent-infra-internal platform-comment recover --issue <issue-token> --task-id <TASK-id> \
|
|
32
|
+
--output <.agents/workspace/.restore-staging-*/task.md> [--cwd <path>]
|
|
28
33
|
agent-infra-internal platform-comment owner <task-ref>
|
|
29
34
|
agent-infra-internal platform-comment sync <task-ref> \
|
|
30
|
-
--kind task|artifact|summary|cancel --agent {standard-agent-token} \
|
|
35
|
+
--kind task|artifact|summary|cancel|recovery-action|recovery-prepare|recovery-commit --agent {standard-agent-token} \
|
|
31
36
|
[--artifact <canonical.md>] [--body-file <path|->] [--backfill]
|
|
32
37
|
```
|
|
33
38
|
|
|
34
39
|
- `applied|no-op|degraded` → exit 0;`failed` → exit 1;`blocked` → exit 2。
|
|
35
|
-
- task 评论保持 `<details>` frontmatter
|
|
40
|
+
- task 评论保持 `<details>` frontmatter 可逆格式,只同步确定性的当前任务投影;活动日志、产物生命周期收据、失效记录等过程章节由 recovery 事务保存。artifact 原文内联并在超过 profile 上限时使用 `artifactChunk`。
|
|
41
|
+
- 长度预检与 platform verification 使用同一最终渲染正文的 UTF-8 字节数。正文超限时失败关闭;本地 task 文件不会被截断。
|
|
42
|
+
|
|
43
|
+
恢复评论使用 `recovery-action`、`recovery-prepare` 和 `recovery-commit` marker,并携带稳定 recovery id 和 body file。task 同步按 prepare、action、task snapshot 写入并复读、commit 的顺序发布。相同内容重放返回 `no-op`;已有恢复 marker 的正文不同返回 `RECOVERY_IMMUTABLE_CONFLICT`。restore 只接受作者一致、分片完整、phase/摘要/marker 匹配且绑定当前 task 评论正文 SHA-256 的完整事务。
|
|
36
44
|
- 相同 intent 重放必须收敛为 `no-op`;重复 marker 返回 `COMMENT_MARKER_CONFLICT` 且不写入。
|
|
37
45
|
- 外部贡献者锁定统一使用 `platform-comment owner`;不同作者且无 triage 时返回 `COMMENT_OWNER_CONFLICT`。
|
|
38
46
|
|
|
@@ -59,7 +67,7 @@ agent-infra-internal task-warning {task-id} add \
|
|
|
59
67
|
```bash
|
|
60
68
|
agent-infra-internal platform-issue inspect {task-id}
|
|
61
69
|
agent-infra-internal platform-issue create {task-id} --agent {standard-agent-token}
|
|
62
|
-
agent-infra-internal platform-issue bind {task-id} --issue {
|
|
70
|
+
agent-infra-internal platform-issue bind {task-id} --issue {issue-token} --agent {standard-agent-token}
|
|
63
71
|
agent-infra-internal platform-issue sync {task-id} --agent {standard-agent-token} {desired-state-flags}
|
|
64
72
|
```
|
|
65
73
|
|
|
@@ -1,57 +1,65 @@
|
|
|
1
|
-
# General Rule - Model-Driven Local Artifact
|
|
1
|
+
# General Rule - Model-Driven Local Artifact Recovery
|
|
2
2
|
|
|
3
|
-
This rule
|
|
3
|
+
This rule covers the pre-completion gate for `analyze-task`, `plan-task`, and `code-task`, plus the summary finalizers for the three review skills. It handles one canonical artifact in the current task directory only. It does not authorize changes to `task.md`, ledgers, receipts, source code, Git, platform resources, or lifecycle state.
|
|
4
4
|
|
|
5
|
-
## Pre-completion gate
|
|
5
|
+
## Pre-completion gate
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
7
|
+
- Run the applicable finalizer before a completed event; pass only that invocation's `artifactSha256` and `semanticDigest` to the event.
|
|
8
|
+
- When artifact content is invalid, the finalizer returns a controlled `recovery` with a `recoveryId`, `candidatePath`, and baseline digests. The formal artifact remains unchanged until recovery commit.
|
|
9
|
+
- After the mechanical safety gates pass, the model may edit only the returned `candidatePath`. After each byte-changing edit, rerun the complete finalizer with the same task, stage/family, artifact, and `--recovery-id`. Never edit the formal artifact directly and call it a candidate.
|
|
10
|
+
- Preflight stable-reads and validates candidate `S`, then seals immutable digest-addressed generation `R`; candidate SHA is an observation rather than an edit guard.
|
|
11
|
+
- Preflight publishes active `R` through `preflight-ready → preflight-commit-started → preflight-passed`; the business finalizer publishes `F` through `full-finalizer-ready → commit-started → passed` and reuses `R` when `F=R`.
|
|
12
|
+
- Candidate-only is a protocol authorization boundary, not OS isolation: a same-UID process with arbitrary host filesystem write access may tamper with recovery internals. Identity, fingerprint, and state checks detect such anomalies and fail closed, but the protocol does not claim an independent security principal or cross-platform isolation.
|
|
11
13
|
|
|
12
|
-
## Authorization
|
|
14
|
+
## Authorization boundary
|
|
13
15
|
|
|
14
|
-
- The finalizer
|
|
15
|
-
- The
|
|
16
|
-
-
|
|
17
|
-
- `changed=false`, an error code, or a format shape is diagnostic evidence, not automatic authorization. The model must judge each case using the complete diagnostic, artifact content, and context.
|
|
16
|
+
- The finalizer and recovery core validate identity, state, permissions, stable reads, complete artifact semantics, and fingerprints. They do not infer repairability or expose a text-operation allow-list.
|
|
17
|
+
- The model may edit only the one ordinary artifact declared by the current skill. It must not edit `task.md`, ledgers, receipts, source code, other reports, or remote resources.
|
|
18
|
+
- `changed=false`, an error code, or a format shape is diagnostic evidence, not edit authorization. Stop on unproven human-decision semantics, concurrency, permission, I/O, identity, provenance, or unknown state.
|
|
18
19
|
|
|
19
|
-
## Non-bypassable
|
|
20
|
+
## Non-bypassable mechanical gates
|
|
20
21
|
|
|
21
|
-
Before every
|
|
22
|
+
Before every candidate edit, confirm that:
|
|
22
23
|
|
|
23
|
-
1. the finalizer
|
|
24
|
-
2.
|
|
25
|
-
3.
|
|
26
|
-
4. the
|
|
24
|
+
1. the finalizer failed with the same controlled recovery context and nothing formal was committed;
|
|
25
|
+
2. task, family/stage, round, artifact, request/authority, and baseline fingerprints still match;
|
|
26
|
+
3. `candidatePath` is the ordinary file generated by the recovery core and the formal target has not changed externally;
|
|
27
|
+
4. the edit does not change human-decision semantics, detail or ledger identity, and introduces no other side effect.
|
|
27
28
|
|
|
28
|
-
If any condition fails, stop
|
|
29
|
+
If any condition fails, stop without editing or publishing a completed event.
|
|
29
30
|
|
|
30
|
-
##
|
|
31
|
+
## Durable recovery states
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
| State | Formal artifact | Allowed action |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| `awaiting-preflight-recovery` | baseline `B` | Edit `S`; stable-read, validate, and create `R` |
|
|
36
|
+
| `preflight-ready` / `preflight-commit-started` | `B` or `R` | Reconcile/publish only active immutable `R` |
|
|
37
|
+
| `preflight-passed` | `R` | Run the business finalizer for `F` |
|
|
38
|
+
| `full-finalizer-ready` / `commit-started` | `R` or `F` | Reconcile/publish only pending immutable `F` |
|
|
39
|
+
| `passed` | `F` | Completed event validates the final digest and consumes the intent |
|
|
40
|
+
| `consumed` | `F` | Cleanup may reclaim the controlled staging and backup |
|
|
41
|
+
| `aborted` | verified restored `B` | Preserve diagnostics; never fabricate success |
|
|
40
42
|
|
|
41
|
-
The
|
|
43
|
+
The intent is current-only schema version 4. Legacy schemas fail closed; do not add migration, adapters, or dual writes. Candidate, baseline, generation, and intent files live in controlled repo-local directories and use canonical recovery IDs, regular-file checks, stable reads, the task lock, and expected-value CAS. No cross-file atomicity is claimed; unknown combinations remain indeterminate and retain their evidence.
|
|
42
44
|
|
|
43
|
-
##
|
|
45
|
+
## Dynamic convergence loop
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
1. Run the finalizer with the fixed invocation.
|
|
48
|
+
2. If it fails and the gates pass, read the structured diagnostic and `candidatePath`, then make one minimal, explainable candidate edit.
|
|
49
|
+
3. Confirm byte-level progress and rerun every finalizer check with the same `--recovery-id`.
|
|
50
|
+
4. Stop on repeated diagnostics or fingerprints, no byte progress, unknown recovery state, or the eight-edit limit for the current invocation. Do not publish a completed event or generate a cross-stage next step.
|
|
46
51
|
|
|
47
|
-
##
|
|
52
|
+
## Shared entry points
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
- On failure, model stop, lack of progress, repeated diagnostics, or the emergency cap, do not publish a completed event, advance the lifecycle, generate cross-stage commands, or fabricate an approval.
|
|
51
|
-
- Stopping advancement does not discard review results. User output must show the artifact path, the last safely readable summary/findings, actual `repairAttempts`, the last structured diagnostic, and the stop reason.
|
|
52
|
-
- If the summary cannot be safely parsed, show only the artifact path and the raw structured diagnostic; do not infer counts or add a conclusion, and state that lifecycle advancement stopped while the artifact remains available.
|
|
53
|
-
- A stop path may only suggest rerunning the current review skill or handling the issue manually. It must not invoke cross-stage helpers such as `code-task` or `complete-task`.
|
|
54
|
+
The current entry points are `task-artifact ... inspect|init|finalize-local` and `task-review ... finalize-summary`. Recovery retries use the `recoveryId` returned by the finalizer, for example:
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
```text
|
|
57
|
+
agent-infra-internal task-artifact {task-id} finalize-local --family code --artifact {code-artifact} --recovery-id {recovery-id}
|
|
58
|
+
agent-infra-internal task-review {task-id} finalize-summary --stage code --artifact {review-artifact} --recovery-id {recovery-id}
|
|
59
|
+
```
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
There is no `task-artifact repair`, `replace-line`, `insert-section`, or `repairable` authorization. The structural engine returns diagnostics and digests only; the current skill's gates and the model decide whether and how to edit the candidate.
|
|
62
|
+
|
|
63
|
+
## Lifecycle isolation
|
|
64
|
+
|
|
65
|
+
A completed event may legitimately modify `task.md`, but only after finalizer recovery is `passed` and digest, round, request, and authority all match. If authority consumption fails after the event write, retain the formal artifact and use the existing lifecycle recovery compensation; do not replay the completed write or roll back a passed artifact.
|