@fitlab-ai/agent-infra 0.11.8 → 0.11.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/bin/internal-cli.js +0 -6
- package/dist/lib/agent-clients/custom-tool-invocations.js +92 -0
- package/dist/lib/agent-clients/next-steps.js +1 -1
- package/dist/lib/agent-clients/reconcile.js +3 -3
- package/dist/lib/agent-clients/schema.js +18 -10
- package/dist/lib/git/workflow.js +1 -1
- package/dist/lib/init.js +2 -2
- package/dist/lib/internal/agent-client.js +3 -3
- package/dist/lib/internal/cli-route-inventory.js +0 -3
- package/dist/lib/internal/sandbox-control.js +30 -5
- package/dist/lib/internal/task-finalization.js +14 -23
- package/dist/lib/internal/task-operation-registry.js +2 -3
- package/dist/lib/internal/task-platform-recovery.js +9 -4
- package/dist/lib/platform/completion-backfill.js +2 -1
- package/dist/lib/platform/context.js +4 -2
- package/dist/lib/platform/github-client.js +20 -17
- package/dist/lib/platform/github-provider.js +42 -22
- package/dist/lib/platform/github-release-notes.js +4 -2
- package/dist/lib/platform/in-label-sync.js +2 -1
- package/dist/lib/platform/issue-comments.js +13 -9
- package/dist/lib/platform/operation-coordinator.js +1 -1
- package/dist/lib/platform/pr-review.js +2 -1
- package/dist/lib/platform/provider-bridge.js +4 -5
- package/dist/lib/platform/provider-contract.d.ts +3 -6
- package/dist/lib/platform/provider-contract.js +3 -15
- package/dist/lib/platform/provider-validation.js +9 -4
- package/dist/lib/platform/pull-requests.js +4 -2
- package/dist/lib/sandbox/control/client.js +54 -6
- package/dist/lib/sandbox/control/executor.js +8 -3
- package/dist/lib/sandbox/recovery.js +1 -22
- package/dist/lib/sandbox/removal.js +0 -1
- package/dist/lib/sandbox/tools.js +2 -0
- package/dist/lib/task/artifact-command.js +11 -1
- package/dist/lib/task/artifact-lifecycle.js +119 -62
- package/dist/lib/task/artifact-selection.js +1 -1
- package/dist/lib/task/capabilities.js +55 -21
- package/dist/lib/task/commands/status.js +0 -27
- package/dist/lib/task/commit-identity.js +22 -7
- package/dist/lib/task/commit-intent.js +67 -26
- package/dist/lib/task/commit-operation.js +19 -8
- package/dist/lib/task/completion-facts.js +32 -10
- package/dist/lib/task/control-authority.js +2 -4
- package/dist/lib/task/current-contract.js +1 -5
- package/dist/lib/task/events.js +8 -274
- package/dist/lib/task/execution-inputs.js +14 -0
- package/dist/lib/task/finalization-envelope.js +4 -0
- package/dist/lib/task/finalization.js +100 -129
- package/dist/lib/task/frontmatter.js +1 -1
- package/dist/lib/task/ledger-intents.js +4 -7
- package/dist/lib/task/orchestration.js +11 -10
- package/dist/lib/task/platform-operation-journal.js +17 -10
- package/dist/lib/task/platform-operation-recovery.js +95 -28
- package/dist/lib/task/review-fingerprint.js +4 -26
- package/dist/lib/task/verification-engine.js +95 -6
- package/dist/lib/task/verification.js +0 -1
- package/dist/lib/task/write.js +0 -11
- package/dist/lib/update.js +2 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/custom-tool-invocations.ts +129 -0
- package/lib/agent-clients/next-steps.ts +3 -3
- package/lib/agent-clients/reconcile.ts +3 -3
- package/lib/agent-clients/schema.ts +18 -10
- package/lib/git/workflow.ts +1 -1
- package/lib/init.ts +2 -2
- package/lib/internal/agent-client.ts +3 -3
- package/lib/internal/cli-route-inventory.ts +0 -2
- package/lib/internal/sandbox-control.ts +39 -6
- package/lib/internal/task-finalization.ts +16 -24
- package/lib/internal/task-operation-registry.ts +2 -3
- package/lib/internal/task-platform-recovery.ts +9 -4
- package/lib/platform/completion-backfill.ts +2 -1
- package/lib/platform/context.ts +8 -4
- package/lib/platform/github-client.ts +24 -20
- package/lib/platform/github-provider.ts +47 -26
- package/lib/platform/github-release-notes.ts +5 -3
- package/lib/platform/in-label-sync.ts +2 -1
- package/lib/platform/issue-comments.ts +14 -9
- package/lib/platform/operation-coordinator.ts +1 -1
- package/lib/platform/pr-review.ts +2 -1
- package/lib/platform/provider-bridge.ts +10 -9
- package/lib/platform/provider-contract.ts +6 -21
- package/lib/platform/provider-loader.ts +7 -6
- package/lib/platform/provider-validation.ts +30 -8
- package/lib/platform/pull-requests.ts +4 -2
- package/lib/platform/repository-metadata.ts +3 -2
- package/lib/platform/security-alerts.ts +1 -1
- package/lib/platform/types.ts +2 -0
- package/lib/sandbox/control/client.ts +57 -6
- package/lib/sandbox/control/executor.ts +8 -3
- package/lib/sandbox/recovery.ts +0 -25
- package/lib/sandbox/removal.ts +2 -10
- package/lib/sandbox/tool-types.ts +1 -0
- package/lib/sandbox/tools.ts +2 -0
- package/lib/task/artifact-command.ts +10 -1
- package/lib/task/artifact-lifecycle.ts +100 -52
- package/lib/task/artifact-selection.ts +1 -1
- package/lib/task/capabilities.ts +47 -24
- package/lib/task/commands/status.ts +0 -31
- package/lib/task/commit-identity.ts +24 -7
- package/lib/task/commit-intent.ts +58 -24
- package/lib/task/commit-operation.ts +17 -8
- package/lib/task/completion-facts.ts +31 -9
- package/lib/task/control-authority.ts +1 -4
- package/lib/task/current-contract.ts +1 -6
- package/lib/task/events.ts +8 -247
- package/lib/task/execution-inputs.ts +25 -0
- package/lib/task/finalization-envelope.ts +21 -0
- package/lib/task/finalization.ts +101 -123
- package/lib/task/frontmatter.ts +1 -1
- package/lib/task/ledger-intents.ts +4 -7
- package/lib/task/orchestration.ts +11 -17
- package/lib/task/platform-operation-journal.ts +18 -11
- package/lib/task/platform-operation-recovery.ts +90 -31
- package/lib/task/review-fingerprint.ts +4 -26
- package/lib/task/verification-engine.ts +103 -9
- package/lib/task/verification.ts +1 -2
- package/lib/task/write.ts +0 -19
- package/lib/update.ts +2 -3
- package/package.json +1 -1
- package/templates/.agents/README.en.md +2 -50
- package/templates/.agents/README.zh-CN.md +2 -50
- package/templates/.agents/rules/commit-and-pr.en.md +5 -5
- package/templates/.agents/rules/commit-and-pr.zh-CN.md +5 -5
- package/templates/.agents/rules/decision-qualification.en.md +1 -1
- package/templates/.agents/rules/decision-qualification.zh-CN.md +1 -1
- package/templates/.agents/rules/next-step-output.en.md +1 -1
- package/templates/.agents/rules/next-step-output.zh-CN.md +1 -1
- package/templates/.agents/scripts/lib/agent-infra-package.js +0 -7
- package/templates/.agents/skills/code-task/SKILL.en.md +25 -9
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +26 -9
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +5 -16
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +5 -16
- package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/SKILL.en.md +1 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +15 -12
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -10
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +15 -6
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +15 -6
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +2 -2
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +2 -268
- package/templates/.agents/templates/task.zh-CN.md +0 -12
- package/templates/.github/workflows/pr-label.yml +1 -2
- package/dist/lib/agent-clients/custom-tuis.js +0 -99
- package/dist/lib/internal/task-invalidation.js +0 -63
- package/dist/lib/task/intermediate-cleanup.js +0 -327
- package/dist/lib/task/invalidation-command.js +0 -88
- package/dist/lib/task/invalidation.js +0 -242
- package/lib/agent-clients/custom-tuis.ts +0 -149
- package/lib/internal/task-invalidation.ts +0 -41
- package/lib/task/intermediate-cleanup.ts +0 -393
- package/lib/task/invalidation-command.ts +0 -88
- package/lib/task/invalidation.ts +0 -313
|
@@ -18,6 +18,8 @@ import { canonicalizeSummaryBody } from '../platform/comment-safety.js';
|
|
|
18
18
|
import { platformResult } from '../platform/types.js';
|
|
19
19
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
20
20
|
import { readPlatformOperationJournal, recordPlatformOperation } from './platform-operation-journal.js';
|
|
21
|
+
import { providerErrorRetryable } from '../platform/provider-validation.js';
|
|
22
|
+
const DEFAULT_RECOVERY_ATTEMPTS = 3;
|
|
21
23
|
function labelsMatchOwnedPrefix(actual, expected, prefix) {
|
|
22
24
|
return actual.filter((label) => label.startsWith(prefix)).sort().join('\0')
|
|
23
25
|
=== expected.filter((label) => label.startsWith(prefix)).sort().join('\0');
|
|
@@ -26,7 +28,7 @@ function fieldsMatchExpected(actual, expected) {
|
|
|
26
28
|
return Object.entries(expected).every(([name, value]) => actual[name] === value);
|
|
27
29
|
}
|
|
28
30
|
function result(status, recovered, pending, error = null) {
|
|
29
|
-
return { status, changed: recovered.length > 0, recovered, pending, error };
|
|
31
|
+
return { status, changed: recovered.length > 0, recovered, pending, error: error ? { code: error.code, message: error.message } : null };
|
|
30
32
|
}
|
|
31
33
|
function summaryPayload(taskDir, taskId) {
|
|
32
34
|
const file = path.join(taskDir, '.delivery-summary.json');
|
|
@@ -198,7 +200,7 @@ async function replayPullRequestSummary(taskId, operation, agent, cwd, client) {
|
|
|
198
200
|
? await resolved.value.provider.comments.list({ context: providerOperationContext(resolved.value), parent: identity })
|
|
199
201
|
: unsupportedProviderOperation(resolved.value.provider, 'comments.list');
|
|
200
202
|
if (!listed.ok)
|
|
201
|
-
return platformResult(listed.error.
|
|
203
|
+
return platformResult(providerErrorRetryable(listed.error.code) ? 'blocked' : 'failed', { error: providerError(listed.error, 'PLATFORM_PROVIDER_OPERATION_FAILED') });
|
|
202
204
|
const marker = `<!-- sync-pr:${taskId}:summary -->`;
|
|
203
205
|
const summaries = listed.value.filter((comment) => normalizeCommentContent(comment.body).split('\n', 1)[0] === marker);
|
|
204
206
|
if (summaries.length > 1)
|
|
@@ -293,7 +295,7 @@ async function replayPullRequest(taskId, operation, agent, cwd) {
|
|
|
293
295
|
}
|
|
294
296
|
return recoverCreatedPullRequest(taskId, { agent, cwd, base: intent.baseRef || '', head: intent.headRef || '', skipQueue: true });
|
|
295
297
|
}
|
|
296
|
-
async function
|
|
298
|
+
async function recoverPlatformOperationsOnce(taskRef, selection, options) {
|
|
297
299
|
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
298
300
|
if (!resolved.ok)
|
|
299
301
|
return result('failed', [], [], { code: resolved.code, message: resolved.message, retryable: false });
|
|
@@ -307,34 +309,23 @@ async function recoverPlatformOperations(taskRef, selection, options) {
|
|
|
307
309
|
}
|
|
308
310
|
const candidates = journal.operations.filter((operation) => operation.id !== options.excludeId
|
|
309
311
|
&& (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown'
|
|
310
|
-
|| (operation.state === 'failed' && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED')))
|
|
311
|
-
.filter((operation) => selection === 'all' || operation.dependency === selection)
|
|
312
|
-
.sort((left, right) => left.updatedAt.localeCompare(right.updatedAt));
|
|
312
|
+
|| (operation.state === 'failed' && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED')));
|
|
313
313
|
if (!candidates.length)
|
|
314
314
|
return result('no-op', [], []);
|
|
315
|
+
const head = candidates[0];
|
|
316
|
+
if (selection !== 'all' && head.dependency !== selection) {
|
|
317
|
+
return result('blocked', [], candidates.map((operation) => operation.id), {
|
|
318
|
+
code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
|
|
319
|
+
message: 'The first unresolved platform operation belongs to another recovery selection',
|
|
320
|
+
retryable: true
|
|
321
|
+
});
|
|
322
|
+
}
|
|
315
323
|
const limit = options.limit === undefined ? candidates.length : Math.max(1, options.limit);
|
|
316
324
|
const recovered = [];
|
|
317
325
|
const pending = [];
|
|
318
326
|
const selected = candidates.slice(0, limit);
|
|
319
327
|
for (const [index, operation] of selected.entries()) {
|
|
320
|
-
|
|
321
|
-
pending.push(operation.id);
|
|
322
|
-
return result('blocked', recovered, pending, {
|
|
323
|
-
code: operation.lastCode || 'PLATFORM_OPERATION_FAILED',
|
|
324
|
-
message: 'A previous platform operation failed permanently; later writes remain queued',
|
|
325
|
-
retryable: false
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
if (operation.attempts >= operation.maxAttempts) {
|
|
329
|
-
pending.push(operation.id, ...selected.slice(index + 1).map((next) => next.id));
|
|
330
|
-
pending.push(...candidates.slice(limit).map((next) => next.id));
|
|
331
|
-
return result('blocked', recovered, pending, {
|
|
332
|
-
code: operation.lastCode || 'PLATFORM_OPERATION_RETRY_LIMIT_REACHED',
|
|
333
|
-
message: 'A previous platform operation exhausted its recovery attempts; later writes remain queued',
|
|
334
|
-
retryable: false
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
if (operation.kind !== 'issue-create') {
|
|
328
|
+
{
|
|
338
329
|
try {
|
|
339
330
|
recordPlatformOperation({
|
|
340
331
|
taskRef: resolved.taskId, cwd: resolved.repoRoot, kind: operation.kind,
|
|
@@ -368,8 +359,8 @@ async function recoverPlatformOperations(taskRef, selection, options) {
|
|
|
368
359
|
if (current.id !== operation.id) {
|
|
369
360
|
try {
|
|
370
361
|
recordPlatformOperation({ taskRef: resolved.taskId, cwd: resolved.repoRoot, kind: operation.kind, target: operation.target,
|
|
371
|
-
expectedDigest:
|
|
372
|
-
state: '
|
|
362
|
+
expectedDigest: current.expectedDigest, issueMetadata: current.issueMetadata, dependency: operation.dependency,
|
|
363
|
+
state: 'queued', replaceOperationId: operation.id });
|
|
373
364
|
targetOperation = { ...operation, expectedDigest: current.expectedDigest, issueMetadata: current.issueMetadata, id: current.id };
|
|
374
365
|
}
|
|
375
366
|
catch (error) {
|
|
@@ -396,7 +387,7 @@ async function recoverPlatformOperations(taskRef, selection, options) {
|
|
|
396
387
|
if (current.id !== operation.id) {
|
|
397
388
|
try {
|
|
398
389
|
recordPlatformOperation({ taskRef: resolved.taskId, cwd: resolved.repoRoot, kind: operation.kind, target: operation.target,
|
|
399
|
-
expectedDigest:
|
|
390
|
+
expectedDigest: current.expectedDigest, dependency: operation.dependency, state: 'queued', replaceOperationId: operation.id });
|
|
400
391
|
targetOperation = { ...operation, expectedDigest: current.expectedDigest, id: current.id };
|
|
401
392
|
}
|
|
402
393
|
catch (error) {
|
|
@@ -437,7 +428,7 @@ async function recoverPlatformOperations(taskRef, selection, options) {
|
|
|
437
428
|
...(operation.pullRequestSummary ? { pullRequestSummary: operation.pullRequestSummary } : {}),
|
|
438
429
|
...(operation.pullRequestReview ? { pullRequestReview: operation.pullRequestReview } : {}),
|
|
439
430
|
dependency: operation.dependency,
|
|
440
|
-
state: succeeded ? 'succeeded' :
|
|
431
|
+
state: succeeded ? 'succeeded' : 'unknown',
|
|
441
432
|
lastCode: remote.error?.code ?? null
|
|
442
433
|
});
|
|
443
434
|
}
|
|
@@ -455,5 +446,81 @@ async function recoverPlatformOperations(taskRef, selection, options) {
|
|
|
455
446
|
pending.push(...remaining);
|
|
456
447
|
return result(pending.length ? 'blocked' : recovered.length ? 'applied' : 'no-op', recovered, pending, pending.length ? { code: 'PLATFORM_OPERATION_RECOVERY_PENDING', message: 'Platform operations remain pending after the recovery budget', retryable: true } : null);
|
|
457
448
|
}
|
|
449
|
+
async function recoverPlatformOperations(taskRef, selection, options) {
|
|
450
|
+
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
451
|
+
if (!resolved.ok)
|
|
452
|
+
return result('failed', [], [], { code: resolved.code, message: resolved.message, retryable: false });
|
|
453
|
+
let initial;
|
|
454
|
+
try {
|
|
455
|
+
initial = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot);
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
const value = error;
|
|
459
|
+
return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
|
|
460
|
+
}
|
|
461
|
+
const eligible = (operation) => operation.id !== options.excludeId
|
|
462
|
+
&& (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown'
|
|
463
|
+
|| (operation.state === 'failed' && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED'));
|
|
464
|
+
const first = initial.operations.filter(eligible);
|
|
465
|
+
if (!first.length)
|
|
466
|
+
return result('no-op', [], []);
|
|
467
|
+
const operationLimit = options.limit === undefined ? first.length : Math.max(1, options.limit);
|
|
468
|
+
const attemptLimit = options.attempts ?? DEFAULT_RECOVERY_ATTEMPTS;
|
|
469
|
+
if (!Number.isSafeInteger(attemptLimit) || attemptLimit <= 0) {
|
|
470
|
+
return result('failed', [], [], { code: 'PLATFORM_OPERATION_RECOVERY_PAYLOAD_INVALID', message: 'Recovery attempts must be a positive safe integer', retryable: false });
|
|
471
|
+
}
|
|
472
|
+
const recovered = [];
|
|
473
|
+
for (let operationIndex = 0; operationIndex < operationLimit; operationIndex += 1) {
|
|
474
|
+
let latest;
|
|
475
|
+
try {
|
|
476
|
+
latest = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot);
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
const value = error;
|
|
480
|
+
return result('failed', recovered, [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
|
|
481
|
+
}
|
|
482
|
+
const head = latest.operations.find(eligible);
|
|
483
|
+
if (!head)
|
|
484
|
+
break;
|
|
485
|
+
if (selection !== 'all' && head.dependency !== selection) {
|
|
486
|
+
const pending = latest.operations.filter(eligible).map((operation) => operation.id);
|
|
487
|
+
return result('blocked', recovered, pending, {
|
|
488
|
+
code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
|
|
489
|
+
message: 'The first unresolved platform operation belongs to another recovery selection',
|
|
490
|
+
retryable: true
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
let last = null;
|
|
494
|
+
for (let attempt = 0; attempt < attemptLimit; attempt += 1) {
|
|
495
|
+
last = await recoverPlatformOperationsOnce(taskRef, selection, { ...options, limit: 1 });
|
|
496
|
+
recovered.push(...last.recovered.filter((id) => !recovered.includes(id)));
|
|
497
|
+
if (last.recovered.length > 0 || last.status === 'applied' || last.status === 'no-op')
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
if (!last || (last.recovered.length === 0 && last.status !== 'applied' && last.status !== 'no-op')) {
|
|
501
|
+
let current;
|
|
502
|
+
try {
|
|
503
|
+
current = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot);
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
return result('blocked', recovered, [head.id], last?.error ?? null);
|
|
507
|
+
}
|
|
508
|
+
const pending = current.operations.filter(eligible).map((operation) => operation.id);
|
|
509
|
+
return result(last?.status === 'failed' ? 'failed' : 'blocked', recovered, pending, last?.error ?? {
|
|
510
|
+
code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
|
|
511
|
+
message: 'The first queued platform operation remains unresolved after this recovery run',
|
|
512
|
+
retryable: true
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
let remaining;
|
|
517
|
+
try {
|
|
518
|
+
remaining = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot).operations.filter(eligible).map((operation) => operation.id);
|
|
519
|
+
}
|
|
520
|
+
catch {
|
|
521
|
+
remaining = [];
|
|
522
|
+
}
|
|
523
|
+
return result(remaining.length ? 'blocked' : recovered.length ? 'applied' : 'no-op', recovered, remaining, remaining.length ? { code: 'PLATFORM_OPERATION_RECOVERY_PENDING', message: 'Platform operations remain queued after this recovery run', retryable: true } : null);
|
|
524
|
+
}
|
|
458
525
|
export { fieldsMatchExpected, labelsMatchOwnedPrefix, recoverPlatformOperations };
|
|
459
526
|
//# sourceMappingURL=platform-operation-recovery.js.map
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from "./invalidation.js";
|
|
3
|
+
import { inspectArtifactDirectory } from "./artifact-lifecycle.js";
|
|
5
4
|
const REVIEWED_TREE_RE = /^[a-f0-9]{40,64}$/;
|
|
6
5
|
export function parseReviewedGitTree(value) {
|
|
7
6
|
return REVIEWED_TREE_RE.test(value) ? value : null;
|
|
@@ -28,32 +27,11 @@ export function resolvePostReviewGlobs(config = {}, reviewConfig = {}) {
|
|
|
28
27
|
return [":/", ...excludes.map((p) => `:(top,exclude)${p}`)];
|
|
29
28
|
}
|
|
30
29
|
export function findAuthoritativeReviewCodeArtifact(taskDir) {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
if (round === 0) {
|
|
30
|
+
const inventory = inspectArtifactDirectory(taskDir, 'review-code');
|
|
31
|
+
if (inventory.status !== 'ready' || !inventory.latest) {
|
|
34
32
|
return { ok: false, round: 0, fileName: null, path: null, error: null };
|
|
35
33
|
}
|
|
36
|
-
const fileName =
|
|
37
|
-
let taskContent;
|
|
38
|
-
try {
|
|
39
|
-
taskContent = fs.readFileSync(path.join(taskDir, "task.md"), "utf8");
|
|
40
|
-
}
|
|
41
|
-
catch (error) {
|
|
42
|
-
return {
|
|
43
|
-
ok: false, round, fileName, path: null,
|
|
44
|
-
error: error instanceof Error ? error.message : String(error)
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
const invalidation = parseInvalidationDocument(taskContent);
|
|
48
|
-
if (!invalidation.ok) {
|
|
49
|
-
return { ok: false, round, fileName, path: null, error: invalidation.message };
|
|
50
|
-
}
|
|
51
|
-
if (invalidationBlocks(invalidation.document)) {
|
|
52
|
-
return { ok: false, round, fileName, path: null, error: "artifact invalidation is incomplete; reconcile task.md before using review-code" };
|
|
53
|
-
}
|
|
54
|
-
if (isArtifactInvalidated(invalidation.document, "review-code", fileName)) {
|
|
55
|
-
return { ok: false, round, fileName, path: null, error: `${fileName} is invalidated; re-run review-code` };
|
|
56
|
-
}
|
|
34
|
+
const { round, name: fileName } = inventory.latest;
|
|
57
35
|
return {
|
|
58
36
|
ok: true,
|
|
59
37
|
round,
|
|
@@ -19,7 +19,7 @@ import { inspectDecisionDetailDuplicates } from "./decision-details.js";
|
|
|
19
19
|
import { parseImplementationInputs } from "./implementation-inputs.js";
|
|
20
20
|
import { scanVisibleMarkdown } from "./markdown.js";
|
|
21
21
|
import { loadVerificationConfig } from "./verification-config.js";
|
|
22
|
-
import { inspectActivityLog } from "./activity-log.js";
|
|
22
|
+
import { inspectActivityLog, pairEntries } from "./activity-log.js";
|
|
23
23
|
import { parseWorkflowWarnings } from "./workflow-warnings.js";
|
|
24
24
|
import { OrchestrationStateError, readRun } from "./orchestration.js";
|
|
25
25
|
import { readPrDeliveryFact } from "./pr-delivery-fact.js";
|
|
@@ -30,9 +30,11 @@ import { normalizeVerificationRecord } from './gate-policy.js';
|
|
|
30
30
|
import { manualValidationFinalSummaryProjectionMatches } from "./manual-validation-receipt.js";
|
|
31
31
|
import { readManualValidationCompletion } from "./manual-validation-completion.js";
|
|
32
32
|
import { sha256File } from "./artifact-receipts.js";
|
|
33
|
+
import { inspectArtifactDirectory } from "./artifact-lifecycle.js";
|
|
34
|
+
import { parseArtifactName } from "./artifact-name.js";
|
|
33
35
|
import { summaryCommentState } from "../platform/pr-summary.js";
|
|
34
36
|
import { taskIssueIdentity } from "../platform/task-identities.js";
|
|
35
|
-
import { hasArtifactCompletionFact, hasArtifactCompletionLog } from './completion-facts.js';
|
|
37
|
+
import { hasArtifactCompletionFact, hasArtifactCompletionLog, parseCompletionFacts } from './completion-facts.js';
|
|
36
38
|
import { inspectReviewIdentity } from './review-identity.js';
|
|
37
39
|
const TASK_ENUMS = {
|
|
38
40
|
type: ["feature", "bugfix", "refactor", "docs", "chore"],
|
|
@@ -264,7 +266,7 @@ function checkOrchestrationEvidence({ taskDir }) {
|
|
|
264
266
|
return passResult('orchestration-evidence', 'Persisted orchestration model and delegation evidence is internally consistent');
|
|
265
267
|
}
|
|
266
268
|
// === Check Functions ===
|
|
267
|
-
function checkTaskMeta({ taskDir, config, repositoryRoot }) {
|
|
269
|
+
function checkTaskMeta({ taskDir, config, repositoryRoot, skillName, artifactFile }) {
|
|
268
270
|
const task = loadTask(taskDir);
|
|
269
271
|
if (!task.ok) {
|
|
270
272
|
return failResult("task-meta", task.message);
|
|
@@ -306,7 +308,22 @@ function checkTaskMeta({ taskDir, config, repositoryRoot }) {
|
|
|
306
308
|
}
|
|
307
309
|
const expectedStep = config.expected_step;
|
|
308
310
|
if (expectedStep && metadata.current_step !== expectedStep) {
|
|
309
|
-
|
|
311
|
+
let validLifecycleArtifact = false;
|
|
312
|
+
if (currentTask) {
|
|
313
|
+
const activityConfig = loadVerificationConfig(repositoryRoot, skillName).checks?.["activity-log"];
|
|
314
|
+
const { section, invalidEntries } = inspectActivityLog(task.content);
|
|
315
|
+
if (activityConfig && section && invalidEntries.length === 0) {
|
|
316
|
+
let latestAction = "";
|
|
317
|
+
for (const entry of section.entries) {
|
|
318
|
+
if (!ACTIVITY_LOG_STARTED_RE.test(entry.step))
|
|
319
|
+
latestAction = entry.step;
|
|
320
|
+
}
|
|
321
|
+
validLifecycleArtifact = checkArtifactLifecycleActivityLog(taskDir, artifactFile, skillName, activityConfig, task, section.entries, latestAction)?.status === "pass";
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (!validLifecycleArtifact) {
|
|
325
|
+
return failResult("task-meta", `Expected current_step '${expectedStep}', got '${metadata.current_step || "(empty)"}'`);
|
|
326
|
+
}
|
|
310
327
|
}
|
|
311
328
|
const expectedStatus = config.expected_status;
|
|
312
329
|
if (expectedStatus && metadata.status !== expectedStatus) {
|
|
@@ -604,7 +621,73 @@ function checkImplementationInput({ taskDir, artifactFile }) {
|
|
|
604
621
|
}
|
|
605
622
|
return passResult("implementation-input", `${actionDecision} matches Activity Log, report, and task table`);
|
|
606
623
|
}
|
|
607
|
-
|
|
624
|
+
const ARTIFACT_ACTIVITY_STAGES = {
|
|
625
|
+
"analyze-task": { family: "analysis", event: "analyze.completed" },
|
|
626
|
+
"plan-task": { family: "plan", event: "plan.completed" },
|
|
627
|
+
"review-analysis": { family: "review-analysis", event: "review-analysis.completed" },
|
|
628
|
+
"review-plan": { family: "review-plan", event: "review-plan.completed" },
|
|
629
|
+
"code-task": { family: "code", event: "code.completed" },
|
|
630
|
+
"review-code": { family: "review-code", event: "review-code.completed" }
|
|
631
|
+
};
|
|
632
|
+
function checkArtifactLifecycleActivityLog(taskDir, artifactFile, skillName, config, task, entries, latestAction) {
|
|
633
|
+
const stage = ARTIFACT_ACTIVITY_STAGES[skillName];
|
|
634
|
+
if (!stage)
|
|
635
|
+
return null;
|
|
636
|
+
const inventory = inspectArtifactDirectory(taskDir, stage.family);
|
|
637
|
+
if (inventory.status !== "ready" || !inventory.latest) {
|
|
638
|
+
return failResult("activity-log", `Current ${stage.family} artifact is unavailable: ${inventory.error?.message ?? "no artifact exists"}`);
|
|
639
|
+
}
|
|
640
|
+
const output = artifactFile || inventory.latest.name;
|
|
641
|
+
if (output !== inventory.latest.name) {
|
|
642
|
+
return failResult("activity-log", `Artifact '${output}' is not the current ${stage.family} artifact; rerun ${skillName} for '${inventory.latest.name}'`);
|
|
643
|
+
}
|
|
644
|
+
const identity = parseArtifactName(output);
|
|
645
|
+
if (!identity || identity.family !== stage.family) {
|
|
646
|
+
return failResult("activity-log", `Artifact '${output}' does not belong to ${stage.family}`);
|
|
647
|
+
}
|
|
648
|
+
if (!hasArtifactCompletionFact(task.content, path.join(taskDir, output), stage.event)) {
|
|
649
|
+
return failResult("activity-log", `Completion fact for '${output}' is missing or stale; rerun ${skillName} to record a current completion`);
|
|
650
|
+
}
|
|
651
|
+
const frontmatter = parseTypedTaskFrontmatter(task.content);
|
|
652
|
+
const fact = parseCompletionFacts(frontmatter.completion_facts).find((item) => item.event === stage.event && item.output === output);
|
|
653
|
+
if (!fact)
|
|
654
|
+
return failResult("activity-log", `Completion fact for '${output}' is missing; rerun ${skillName}`);
|
|
655
|
+
if (!Array.isArray(fact.lifecycleInputs)) {
|
|
656
|
+
return failResult("activity-log", `Completion fact for '${output}' has no input snapshot; rerun ${skillName}`);
|
|
657
|
+
}
|
|
658
|
+
for (const input of fact.lifecycleInputs) {
|
|
659
|
+
if (!input || typeof input.name !== "string" || !/^[a-f0-9]{64}$/u.test(input.sha256)) {
|
|
660
|
+
return failResult("activity-log", `Completion fact for '${output}' has an invalid input snapshot; rerun ${skillName}`);
|
|
661
|
+
}
|
|
662
|
+
const inputIdentity = parseArtifactName(input.name);
|
|
663
|
+
if (!inputIdentity)
|
|
664
|
+
return failResult("activity-log", `Completion fact for '${output}' has invalid input '${input.name}'; rerun ${skillName}`);
|
|
665
|
+
const inputInventory = inspectArtifactDirectory(taskDir, inputIdentity.family);
|
|
666
|
+
if (inputInventory.status !== "ready" || inputInventory.latest?.name !== input.name
|
|
667
|
+
|| sha256File(path.join(taskDir, input.name)) !== input.sha256) {
|
|
668
|
+
return failResult("activity-log", `Input '${input.name}' changed or is no longer current; rerun ${skillName} to refresh '${output}'`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
const expectedAction = new RegExp(config.expected_action_pattern);
|
|
672
|
+
const roundAction = entries.find((entry) => {
|
|
673
|
+
if (ACTIVITY_LOG_STARTED_RE.test(entry.step))
|
|
674
|
+
return false;
|
|
675
|
+
if (!expectedAction.test(entry.step))
|
|
676
|
+
return false;
|
|
677
|
+
expectedAction.lastIndex = 0;
|
|
678
|
+
const actionRound = /\(Round (\d+)/.exec(entry.step)?.[1];
|
|
679
|
+
return actionRound === String(identity.round) && entry.note.includes(`→ ${output}`);
|
|
680
|
+
});
|
|
681
|
+
if (!roundAction) {
|
|
682
|
+
return failResult("activity-log", `No completed Activity Log row records '${output}' for ${skillName} Round ${identity.round}`);
|
|
683
|
+
}
|
|
684
|
+
if (skillName === "plan-task" && /^(?:Human Decision|Decision)\b/.test(latestAction)
|
|
685
|
+
&& expectedAction.test(latestAction)) {
|
|
686
|
+
return passResult("activity-log", `Plan completion '${output}' is current; latest action '${latestAction}' is a human decision`);
|
|
687
|
+
}
|
|
688
|
+
return passResult("activity-log", `Current ${stage.family} completion '${output}' is valid for Round ${identity.round}`);
|
|
689
|
+
}
|
|
690
|
+
function checkActivityLog({ taskDir, config, skillName, artifactFile }) {
|
|
608
691
|
const task = loadTask(taskDir);
|
|
609
692
|
if (!task.ok) {
|
|
610
693
|
return failResult("activity-log", task.message);
|
|
@@ -638,6 +721,9 @@ function checkActivityLog({ taskDir, config }) {
|
|
|
638
721
|
doneActions.push(action);
|
|
639
722
|
}
|
|
640
723
|
}
|
|
724
|
+
const artifactLifecycleResult = checkArtifactLifecycleActivityLog(taskDir, artifactFile, skillName, config, task, entries, latestAction);
|
|
725
|
+
if (artifactLifecycleResult)
|
|
726
|
+
return artifactLifecycleResult;
|
|
641
727
|
if (config.expected_action_pattern && !new RegExp(config.expected_action_pattern).test(latestAction)) {
|
|
642
728
|
const expected = new RegExp(config.expected_action_pattern);
|
|
643
729
|
let index = doneActions.length - 1;
|
|
@@ -799,7 +885,10 @@ async function checkManualValidation({ taskDir, repositoryRoot }) {
|
|
|
799
885
|
const inspectedLog = inspectActivityLog(task.content);
|
|
800
886
|
if (!inspectedLog.section)
|
|
801
887
|
return failResult("manual-validation", "Activity Log is missing or invalid");
|
|
802
|
-
const
|
|
888
|
+
const reviewRow = pairEntries(inspectedLog.section.entries).find((row) => row.step === `Review Code (Round ${review.round})` && row.started !== '' && row.done !== '');
|
|
889
|
+
const reviewDoneIndex = reviewRow
|
|
890
|
+
? inspectedLog.section.entries.findIndex((entry) => entry.step === reviewRow.step && entry.time === reviewRow.done)
|
|
891
|
+
: -1;
|
|
803
892
|
const completedIndex = inspectedLog.section.entries.findIndex((entry) => entry.step === "Complete Manual Validation"
|
|
804
893
|
&& entry.note.includes(`Manual validation passed → ${artifactName};`)
|
|
805
894
|
&& entry.note.includes(`transaction=${receipt.transactionId};`)
|
|
@@ -22,7 +22,6 @@ const VERIFICATION_CATALOG = {
|
|
|
22
22
|
'commit.completed': gate('commit', 'active'),
|
|
23
23
|
'complete-task.preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['required-pr-delivery'] },
|
|
24
24
|
'complete-task.hard-preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['required-pr-delivery'] },
|
|
25
|
-
'complete-task.prepared': gate('complete-task', 'active'),
|
|
26
25
|
'complete-task.completed': gate('complete-task', 'completed'),
|
|
27
26
|
'create-pr.completed': gate('create-pr', 'active'),
|
|
28
27
|
'create-task.completed': gate('create-task', 'active'),
|
package/dist/lib/task/write.js
CHANGED
|
@@ -6,7 +6,6 @@ import { parseTypedTaskFrontmatter, updateTaskFrontmatter } from './frontmatter.
|
|
|
6
6
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
7
7
|
import { mutateTableRow, upsertSection } from './sections.js';
|
|
8
8
|
import { validateCurrentTaskContract } from './current-contract.js';
|
|
9
|
-
import { invalidationBlocks, parseInvalidationDocument } from './invalidation.js';
|
|
10
9
|
const DEFAULT_FILE_SYSTEM = {
|
|
11
10
|
readFileSync: (file) => fs.readFileSync(file, 'utf8'),
|
|
12
11
|
statModeSync: (file) => fs.statSync(file).mode,
|
|
@@ -91,16 +90,6 @@ function writeTaskCore(request, options = {}) {
|
|
|
91
90
|
if (!contract.ok) {
|
|
92
91
|
return failure(request, identity, contract.code, contract.message);
|
|
93
92
|
}
|
|
94
|
-
if (invalidationBlocks(contract.invalidation)
|
|
95
|
-
&& options.invalidationContext !== 'source-completion'
|
|
96
|
-
&& options.invalidationContext !== 'reconcile') {
|
|
97
|
-
return failure(request, identity, 'TASK_INVALIDATION_BLOCKED', 'task has an incomplete artifact invalidation operation; reconcile it before writing downstream state');
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
const invalidation = parseInvalidationDocument(original);
|
|
102
|
-
if (!invalidation.ok)
|
|
103
|
-
return failure(request, identity, 'TASK_INVALIDATION_INVALID', invalidation.message);
|
|
104
93
|
}
|
|
105
94
|
let candidate = original;
|
|
106
95
|
const operations = [];
|
package/dist/lib/update.js
CHANGED
|
@@ -132,8 +132,8 @@ async function cmdSync() {
|
|
|
132
132
|
ok('Seed files synced successfully!');
|
|
133
133
|
console.log('');
|
|
134
134
|
if (workflowPlan.nextSteps.length === 0) {
|
|
135
|
-
console.log(' No Agent Client
|
|
136
|
-
console.log(` Configure "
|
|
135
|
+
console.log(' No Agent Client or sandbox custom tool integration enabled.');
|
|
136
|
+
console.log(` Configure a custom tool in "sandbox.tools.definitions" and select it in "sandbox.tools.ids" in ${CONFIG_PATH}.`);
|
|
137
137
|
console.log('');
|
|
138
138
|
}
|
|
139
139
|
else {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"protocolVersion": 3,
|
|
3
|
-
"packageVersion": "0.11.
|
|
4
|
-
"sourceInputHash": "
|
|
5
|
-
"internalExecutableBuildHash": "
|
|
3
|
+
"packageVersion": "0.11.10",
|
|
4
|
+
"sourceInputHash": "3500e8eb7fac8bbad3d95124002f8042af3d160185a5b635eb9b4d8f88a991cb",
|
|
5
|
+
"internalExecutableBuildHash": "51a4c472b6a54b3ca3c28ab049683175d3ea4730fe560868f853cd603b5a98f1"
|
|
6
6
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
type CustomToolInvocation = Readonly<{
|
|
2
|
+
name: string;
|
|
3
|
+
invocation: string;
|
|
4
|
+
}>;
|
|
5
|
+
|
|
6
|
+
type CustomToolInvocationDiagnosticCode =
|
|
7
|
+
| 'INVALID_CUSTOM_TOOL_CONFIG'
|
|
8
|
+
| 'INVALID_CUSTOM_TOOL_INVOCATION'
|
|
9
|
+
| 'INVALID_CUSTOM_TOOL_INVOCATION_PLACEHOLDER';
|
|
10
|
+
|
|
11
|
+
type CustomToolInvocationDiagnostic = Readonly<{
|
|
12
|
+
code: CustomToolInvocationDiagnosticCode;
|
|
13
|
+
path: string;
|
|
14
|
+
}>;
|
|
15
|
+
|
|
16
|
+
type NormalizeCustomToolInvocationsResult = Readonly<{
|
|
17
|
+
items: readonly CustomToolInvocation[];
|
|
18
|
+
diagnostics: readonly CustomToolInvocationDiagnostic[];
|
|
19
|
+
}>;
|
|
20
|
+
|
|
21
|
+
const ALLOWED_PLACEHOLDERS = Object.freeze(['skillName', 'projectName']);
|
|
22
|
+
|
|
23
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
24
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isNonEmptySingleLine(value: unknown): value is string {
|
|
28
|
+
return typeof value === 'string'
|
|
29
|
+
&& value.trim() !== ''
|
|
30
|
+
&& !/[\r\n]/.test(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function hasValidPlaceholders(invocation: string): boolean {
|
|
34
|
+
const placeholders = [
|
|
35
|
+
...invocation.matchAll(/\$\{([^}]+)\}/g)
|
|
36
|
+
].map((match) => match[1]!);
|
|
37
|
+
return placeholders.includes('skillName')
|
|
38
|
+
&& placeholders.every((placeholder) =>
|
|
39
|
+
ALLOWED_PLACEHOLDERS.includes(placeholder)
|
|
40
|
+
)
|
|
41
|
+
&& !invocation
|
|
42
|
+
.replaceAll('${skillName}', '')
|
|
43
|
+
.replaceAll('${projectName}', '')
|
|
44
|
+
.includes('${');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function normalizeCustomToolInvocations(config: unknown): NormalizeCustomToolInvocationsResult {
|
|
48
|
+
const items: CustomToolInvocation[] = [];
|
|
49
|
+
const diagnostics: CustomToolInvocationDiagnostic[] = [];
|
|
50
|
+
|
|
51
|
+
const root = isRecord(config) ? config : {};
|
|
52
|
+
const sandbox = isRecord(root.sandbox) ? root.sandbox : {};
|
|
53
|
+
const sandboxTools = isRecord(sandbox.tools) ? sandbox.tools : { ids: ['agent-infra'] };
|
|
54
|
+
const definitions = sandboxTools.definitions ?? {};
|
|
55
|
+
const selectedIds = sandboxTools.ids;
|
|
56
|
+
|
|
57
|
+
if (!Array.isArray(selectedIds)) {
|
|
58
|
+
return Object.freeze({
|
|
59
|
+
items: Object.freeze([]),
|
|
60
|
+
diagnostics: Object.freeze([
|
|
61
|
+
Object.freeze({
|
|
62
|
+
code: 'INVALID_CUSTOM_TOOL_CONFIG' as const,
|
|
63
|
+
path: 'sandbox.tools.ids'
|
|
64
|
+
})
|
|
65
|
+
])
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!isRecord(definitions)) {
|
|
70
|
+
return Object.freeze({
|
|
71
|
+
items: Object.freeze([]),
|
|
72
|
+
diagnostics: Object.freeze([
|
|
73
|
+
Object.freeze({
|
|
74
|
+
code: 'INVALID_CUSTOM_TOOL_CONFIG' as const,
|
|
75
|
+
path: 'sandbox.tools.definitions'
|
|
76
|
+
})
|
|
77
|
+
])
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const [index, id] of selectedIds.entries()) {
|
|
82
|
+
if (typeof id !== 'string') {
|
|
83
|
+
diagnostics.push({
|
|
84
|
+
code: 'INVALID_CUSTOM_TOOL_INVOCATION',
|
|
85
|
+
path: `sandbox.tools.ids[${index}]`
|
|
86
|
+
});
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const candidate = definitions[id];
|
|
90
|
+
if (!isRecord(candidate)) continue;
|
|
91
|
+
if (candidate.invoke === undefined) continue;
|
|
92
|
+
const base = `sandbox.tools.definitions.${id}`;
|
|
93
|
+
const name = candidate.name === undefined ? id : candidate.name;
|
|
94
|
+
if (!isNonEmptySingleLine(name)) {
|
|
95
|
+
diagnostics.push({ code: 'INVALID_CUSTOM_TOOL_INVOCATION', path: `${base}.name` });
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (!isNonEmptySingleLine(candidate.invoke)) {
|
|
99
|
+
diagnostics.push({ code: 'INVALID_CUSTOM_TOOL_INVOCATION', path: `${base}.invoke` });
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (!hasValidPlaceholders(candidate.invoke)) {
|
|
103
|
+
diagnostics.push({
|
|
104
|
+
code: 'INVALID_CUSTOM_TOOL_INVOCATION_PLACEHOLDER',
|
|
105
|
+
path: `${base}.invoke`
|
|
106
|
+
});
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
items.push(Object.freeze({
|
|
110
|
+
name,
|
|
111
|
+
invocation: candidate.invoke
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return Object.freeze({
|
|
116
|
+
items: Object.freeze(items),
|
|
117
|
+
diagnostics: Object.freeze(
|
|
118
|
+
diagnostics.map((diagnostic) => Object.freeze(diagnostic))
|
|
119
|
+
)
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export { normalizeCustomToolInvocations };
|
|
124
|
+
export type {
|
|
125
|
+
CustomToolInvocation,
|
|
126
|
+
CustomToolInvocationDiagnostic,
|
|
127
|
+
CustomToolInvocationDiagnosticCode,
|
|
128
|
+
NormalizeCustomToolInvocationsResult
|
|
129
|
+
};
|
|
@@ -2,7 +2,7 @@ import { listEnabledAgentClientAdapters } from './registry.ts';
|
|
|
2
2
|
import { renderAgentClientInvocation } from './invocation.ts';
|
|
3
3
|
import semver from 'semver';
|
|
4
4
|
import { isTaskScopeSkill } from '../task/skill-scope.ts';
|
|
5
|
-
import type {
|
|
5
|
+
import type { CustomToolInvocation } from './custom-tool-invocations.ts';
|
|
6
6
|
import type { AgentClientId, AgentClientState } from './types.ts';
|
|
7
7
|
|
|
8
8
|
type NextStepCommand = Readonly<{
|
|
@@ -15,7 +15,7 @@ type NextStepCommand = Readonly<{
|
|
|
15
15
|
type RenderNextStepsInput = Readonly<{
|
|
16
16
|
projectName: string;
|
|
17
17
|
state: AgentClientState;
|
|
18
|
-
|
|
18
|
+
customToolInvocations: readonly CustomToolInvocation[];
|
|
19
19
|
skillName: string;
|
|
20
20
|
taskRef?: string;
|
|
21
21
|
version?: string;
|
|
@@ -69,7 +69,7 @@ function renderNextStepCommands(
|
|
|
69
69
|
)
|
|
70
70
|
})
|
|
71
71
|
);
|
|
72
|
-
const custom = input.
|
|
72
|
+
const custom = input.customToolInvocations.map(
|
|
73
73
|
(tool): NextStepCommand => Object.freeze({
|
|
74
74
|
source: 'custom',
|
|
75
75
|
displayName: tool.name,
|
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
|
|
5
5
|
import defaults from '../defaults.json' with { type: 'json' };
|
|
6
6
|
import { normalizeAgentClients, serializeAgentClients } from './config.ts';
|
|
7
|
-
import {
|
|
7
|
+
import { normalizeCustomToolInvocations } from './custom-tool-invocations.ts';
|
|
8
8
|
import { renderNextStepCommands } from './next-steps.ts';
|
|
9
9
|
import { planAgentClientProjectAssets } from './project-assets.ts';
|
|
10
10
|
import {
|
|
@@ -216,11 +216,11 @@ function planAgentClientReconciliation(input: Readonly<{
|
|
|
216
216
|
delete (nextConfig.files as Record<string, unknown>).managedBaselines;
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
const custom =
|
|
219
|
+
const custom = normalizeCustomToolInvocations(input.config);
|
|
220
220
|
const nextSteps = renderNextStepCommands({
|
|
221
221
|
projectName,
|
|
222
222
|
state: desired,
|
|
223
|
-
|
|
223
|
+
customToolInvocations: custom.items,
|
|
224
224
|
skillName: 'update-agent-infra'
|
|
225
225
|
});
|
|
226
226
|
const actionableSeed = seedOperations.some((operation) =>
|