@fitlab-ai/agent-infra 0.11.10 → 0.11.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/internal-cli.js +2 -2
- package/dist/lib/agent-clients/adapter.js +27 -1
- package/dist/lib/agent-clients/adapters/claude-code.js +5 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +78 -63
- package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +114 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +447 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +130 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +131 -0
- package/dist/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.js +2 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +48 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +124 -62
- package/dist/lib/{internal/codex-sandbox-controller.js → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js} +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +222 -210
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +468 -0
- package/dist/lib/agent-clients/adapters/codex-sandbox.js +13 -0
- package/dist/lib/agent-clients/adapters/codex.js +44 -1
- package/dist/lib/agent-clients/adapters/index.js +2 -10
- package/dist/lib/agent-clients/tokens.js +3 -2
- package/dist/lib/internal/agent-client.js +132 -72
- package/dist/lib/internal/cli-route-inventory.js +2 -2
- package/dist/lib/internal/manual-validation.js +2 -2
- package/dist/lib/internal/platform-pr.js +4 -5
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-create.js +15 -1
- package/dist/lib/internal/task-finalization.js +1 -3
- package/dist/lib/internal/task-operation-registry.js +5 -139
- package/dist/lib/internal/task-validate.js +1 -1
- package/dist/lib/platform/issues.js +18 -6
- package/dist/lib/platform/pr-summary.js +45 -8
- package/dist/lib/platform/pull-requests.js +79 -14
- package/dist/lib/platform/verification-sync.js +17 -14
- package/dist/lib/runtime/agent-runtime.js +6 -29
- package/dist/lib/sandbox/commands/control-status.js +1 -0
- package/dist/lib/sandbox/commands/create.js +19 -2
- package/dist/lib/sandbox/commands/enter.js +47 -33
- package/dist/lib/sandbox/control/agent-operation.js +34 -0
- package/dist/lib/sandbox/control/client.js +68 -161
- package/dist/lib/sandbox/control/executor.js +13 -87
- package/dist/lib/sandbox/control/host-recovery.js +218 -0
- package/dist/lib/sandbox/control/protocol.js +15 -44
- package/dist/lib/sandbox/control/server.js +57 -51
- package/dist/lib/sandbox/environment.js +150 -0
- package/dist/lib/sandbox/mounts.js +2 -1
- package/dist/lib/sandbox/recovery.js +11 -1
- package/dist/lib/sandbox/removal.js +9 -5
- package/dist/lib/sandbox/workspace-identity.js +0 -14
- package/dist/lib/sandbox/workspace-view.js +41 -23
- package/dist/lib/task/artifact-command.js +7 -2
- package/dist/lib/task/artifact-lifecycle.js +2 -1
- package/dist/lib/task/capabilities.js +4 -1
- package/dist/lib/task/control-authority.js +54 -266
- package/dist/lib/task/control-recovery.js +1 -1
- package/dist/lib/task/create.js +2 -2
- package/dist/lib/task/delegation-receipts.js +74 -161
- package/dist/lib/task/events.js +89 -45
- package/dist/lib/task/finalization.js +58 -60
- package/dist/lib/task/frontmatter.js +24 -1
- package/dist/lib/task/ledger-intents.js +2 -1
- package/dist/lib/task/lifecycle.js +84 -97
- package/dist/lib/task/orchestration.js +182 -129
- package/dist/lib/task/platform-operation-journal.js +12 -0
- package/dist/lib/task/platform-operation-recovery.js +14 -7
- package/dist/lib/task/resolve-ref.js +8 -3
- package/dist/lib/task/runtime-paths.js +10 -0
- package/dist/lib/task/verification-engine.js +28 -15
- package/dist/lib/task/workspace-snapshot.js +1 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +61 -0
- package/lib/agent-clients/adapters/claude-code.ts +5 -1
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
- package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
- package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
- package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
- package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
- package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +272 -223
- package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
- package/lib/agent-clients/adapters/codex-sandbox.ts +13 -0
- package/lib/agent-clients/adapters/codex.ts +48 -1
- package/lib/agent-clients/adapters/index.ts +7 -20
- package/lib/agent-clients/tokens.ts +4 -3
- package/lib/internal/agent-client.ts +142 -73
- package/lib/internal/cli-route-inventory.ts +2 -2
- package/lib/internal/manual-validation.ts +2 -2
- package/lib/internal/platform-pr.ts +3 -4
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-create.ts +15 -2
- package/lib/internal/task-finalization.ts +1 -3
- package/lib/internal/task-operation-registry.ts +5 -151
- package/lib/internal/task-validate.ts +1 -1
- package/lib/platform/issues.ts +18 -6
- package/lib/platform/pr-summary.ts +43 -7
- package/lib/platform/pull-requests.ts +74 -13
- package/lib/platform/verification-sync.ts +18 -14
- package/lib/runtime/agent-runtime.ts +6 -41
- package/lib/sandbox/commands/control-status.ts +1 -0
- package/lib/sandbox/commands/create.ts +25 -2
- package/lib/sandbox/commands/enter.ts +42 -39
- package/lib/sandbox/control/agent-operation.ts +49 -0
- package/lib/sandbox/control/client.ts +86 -210
- package/lib/sandbox/control/executor.ts +17 -96
- package/lib/sandbox/control/host-recovery.ts +195 -0
- package/lib/sandbox/control/protocol.ts +24 -55
- package/lib/sandbox/control/server.ts +54 -52
- package/lib/sandbox/environment.ts +162 -0
- package/lib/sandbox/mounts.ts +2 -1
- package/lib/sandbox/recovery.ts +13 -1
- package/lib/sandbox/removal.ts +12 -5
- package/lib/sandbox/tool-types.ts +1 -0
- package/lib/sandbox/workspace-identity.ts +0 -26
- package/lib/sandbox/workspace-view.ts +42 -24
- package/lib/task/artifact-command.ts +9 -2
- package/lib/task/artifact-lifecycle.ts +2 -1
- package/lib/task/capabilities.ts +5 -2
- package/lib/task/control-authority.ts +65 -382
- package/lib/task/control-recovery.ts +2 -2
- package/lib/task/create.ts +3 -3
- package/lib/task/delegation-receipts.ts +87 -231
- package/lib/task/events.ts +101 -43
- package/lib/task/finalization.ts +57 -58
- package/lib/task/frontmatter.ts +25 -1
- package/lib/task/ledger-intents.ts +2 -1
- package/lib/task/lifecycle.ts +82 -87
- package/lib/task/orchestration.ts +237 -176
- package/lib/task/platform-operation-journal.ts +29 -2
- package/lib/task/platform-operation-recovery.ts +12 -6
- package/lib/task/resolve-ref.ts +8 -3
- package/lib/task/runtime-paths.ts +12 -0
- package/lib/task/verification-engine.ts +27 -16
- package/lib/task/workspace-snapshot.ts +1 -1
- package/package.json +1 -1
- package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
- package/templates/.agents/rules/README.en.md +2 -2
- package/templates/.agents/rules/README.zh-CN.md +2 -2
- package/templates/.agents/rules/issue-sync.en.md +1 -1
- package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
- package/templates/.agents/rules/next-step-output.en.md +6 -19
- package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
- package/templates/.agents/rules/no-mid-flow-questions.en.md +19 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +19 -0
- package/templates/.agents/rules/review-handshake.en.md +3 -0
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -0
- package/templates/.agents/rules/task-management.en.md +1 -1
- package/templates/.agents/rules/task-management.zh-CN.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -0
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -0
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -0
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -0
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/branch-management.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/branch-management.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +2 -0
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +2 -0
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -2
- package/templates/.agents/skills/complete-task/SKILL.en.md +0 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +0 -3
- package/templates/.agents/skills/create-pr/SKILL.en.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/reference/branch-strategy.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/branch-strategy.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +3 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +3 -4
- package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -5
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-pr/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +4 -1
- package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/test-integration/SKILL.en.md +5 -2
- package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +5 -2
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +4 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +5 -2
- package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +5 -2
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
- package/dist/lib/internal/codex-lifecycle.js +0 -385
- package/dist/lib/task/codex-orchestration.js +0 -206
- package/dist/lib/task/finalization-handoff.js +0 -93
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
- package/lib/internal/codex-lifecycle.ts +0 -390
- package/lib/task/codex-orchestration.ts +0 -276
- package/lib/task/finalization-handoff.ts +0 -105
|
@@ -16,6 +16,7 @@ import { resourceIdentityEquals } from './resource-identity.js';
|
|
|
16
16
|
import { buildPrChangeReport, CANONICAL_REPORT_PLACEHOLDER, readPrChangeReport, replaceCanonicalReportPlaceholder, runMechanicalChangeReport, taskIntentDigest, validateMechanicalReport, validatePrecheckCandidate, writePrChangeReportAtomic } from './pr-change-report.js';
|
|
17
17
|
import { MANUAL_VALIDATION_RECEIPT_PLACEHOLDER, manualValidationFinalSummaryProjectionMatches } from '../task/manual-validation-receipt.js';
|
|
18
18
|
import { readManualValidationCompletion } from '../task/manual-validation-completion.js';
|
|
19
|
+
import { readManualValidationTransaction } from '../task/manual-validation-transaction.js';
|
|
19
20
|
function summaryMarker(taskId) {
|
|
20
21
|
return `<!-- sync-pr:${taskId}:summary -->`;
|
|
21
22
|
}
|
|
@@ -119,6 +120,20 @@ async function inspectBoundPullRequest(context, repoRoot, prIdentity, loadedCont
|
|
|
119
120
|
return { ok: false, status: inspectionError(error), error };
|
|
120
121
|
}
|
|
121
122
|
const remote = inspected.value;
|
|
123
|
+
if (!remote.identity) {
|
|
124
|
+
return {
|
|
125
|
+
ok: false,
|
|
126
|
+
status: 'failed',
|
|
127
|
+
error: { code: 'PR_IDENTITY_MISSING', message: 'Provider pull-request inspection did not return a canonical identity', retryable: false }
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (!resourceIdentityEquals(remote.identity, prIdentity)) {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
status: 'failed',
|
|
134
|
+
error: { code: 'PR_IDENTITY_MISMATCH', message: 'Provider returned a different pull request identity than requested', retryable: false }
|
|
135
|
+
};
|
|
136
|
+
}
|
|
122
137
|
const number = remote.number ?? resourceIdentityNumber(remote.identity) ?? resourceIdentityNumber(prIdentity) ?? 0;
|
|
123
138
|
const head = remote.head || { repository: context.platform.repository, ref: '', sha: remote.headSha || '' };
|
|
124
139
|
const base = remote.base || { repository: context.platform.repository, ref: '', sha: remote.baseSha || '' };
|
|
@@ -127,7 +142,7 @@ async function inspectBoundPullRequest(context, repoRoot, prIdentity, loadedCont
|
|
|
127
142
|
value: {
|
|
128
143
|
repository: context.platform.repository,
|
|
129
144
|
number,
|
|
130
|
-
identity: remote.identity
|
|
145
|
+
identity: remote.identity,
|
|
131
146
|
nodeId: remote.id,
|
|
132
147
|
url: remote.displayUrl || '',
|
|
133
148
|
state: remote.state === 'closed' ? 'closed' : 'open',
|
|
@@ -261,14 +276,18 @@ async function summaryContext(taskRef, options = {}) {
|
|
|
261
276
|
artifacts: []
|
|
262
277
|
};
|
|
263
278
|
const prIdentity = fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.resource : null;
|
|
264
|
-
|
|
279
|
+
let prNumber = resourceIdentityNumber(prIdentity);
|
|
265
280
|
const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
|
|
266
281
|
const context = loaded.ok ? loaded.value.context : loaded.context;
|
|
267
282
|
let pullRequest = null;
|
|
268
283
|
if (loaded.ok && prIdentity && context.platform.repository && ['no-op', 'degraded'].includes(context.status)) {
|
|
269
284
|
const inspected = await inspectBoundPullRequest(context, resolved.repoRoot, prIdentity, loaded.value);
|
|
270
|
-
if (inspected.ok)
|
|
285
|
+
if (inspected.ok) {
|
|
271
286
|
pullRequest = inspected.value;
|
|
287
|
+
if (Number.isSafeInteger(inspected.value.number) && inspected.value.number > 0) {
|
|
288
|
+
prNumber = inspected.value.number;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
272
291
|
}
|
|
273
292
|
const report = summaryReportStatus(reportPath, pullRequest, taskContent, resolved.repoRoot);
|
|
274
293
|
return {
|
|
@@ -493,7 +512,7 @@ async function syncPullRequestSummary(taskRef, options) {
|
|
|
493
512
|
const prIdentity = boundFact.identity.resource;
|
|
494
513
|
const boundPrNumber = resourceIdentityNumber(prIdentity);
|
|
495
514
|
knownPrNumber = boundPrNumber;
|
|
496
|
-
|
|
515
|
+
let prNumber = boundPrNumber;
|
|
497
516
|
const initial = await inspectBoundPullRequest(context, resolved.repoRoot, prIdentity, loaded.value);
|
|
498
517
|
if (!initial.ok)
|
|
499
518
|
return fail(initial.status, context, initial.error);
|
|
@@ -505,9 +524,23 @@ async function syncPullRequestSummary(taskRef, options) {
|
|
|
505
524
|
if (manual && (manual.phase === 'pending' ? hasFinalManualValidation : !hasFinalManualValidation))
|
|
506
525
|
return fail('failed', context, { code: 'MANUAL_VALIDATION_SUMMARY_PHASE_INVALID', message: 'manual-validation summary phase does not match the requested writer phase', retryable: false }, prNumber);
|
|
507
526
|
if (manual) {
|
|
508
|
-
if (
|
|
509
|
-
return fail('failed', context, { code: 'MANUAL_VALIDATION_PR_NUMBER_REQUIRED', message: 'manual-validation summary requires a
|
|
510
|
-
|
|
527
|
+
if (!Number.isSafeInteger(initial.value.number) || initial.value.number <= 0)
|
|
528
|
+
return fail('failed', context, { code: 'MANUAL_VALIDATION_PR_NUMBER_REQUIRED', message: 'manual-validation summary requires a positive safe pull-request number from the authoritative snapshot', retryable: false }, prNumber);
|
|
529
|
+
prNumber = initial.value.number;
|
|
530
|
+
knownPrNumber = prNumber;
|
|
531
|
+
if (!manual.transactionId)
|
|
532
|
+
return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'manual-validation summary requires a transaction ID', retryable: false }, prNumber);
|
|
533
|
+
if (manual.phase === 'pending') {
|
|
534
|
+
const transaction = readManualValidationTransaction(resolved.taskDir, {
|
|
535
|
+
transactionId: manual.transactionId,
|
|
536
|
+
taskId: resolved.taskId,
|
|
537
|
+
prNumber,
|
|
538
|
+
prHeadSha: initial.value.head.sha
|
|
539
|
+
});
|
|
540
|
+
if (!transaction.ok)
|
|
541
|
+
return fail('failed', context, platformError(transaction.error), prNumber);
|
|
542
|
+
}
|
|
543
|
+
if (manual.phase === 'final' && (!manual.receiptDigest || !manual.prHeadSha || manual.prHeadSha !== initial.value.head.sha))
|
|
511
544
|
return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires transaction, receipt, and current head identity', retryable: false }, prNumber);
|
|
512
545
|
if (manual.phase === 'final') {
|
|
513
546
|
if (manual.authority !== 'coordinator')
|
|
@@ -688,7 +721,11 @@ async function syncPullRequestSummary(taskRef, options) {
|
|
|
688
721
|
target: JSON.stringify(prIdentity),
|
|
689
722
|
expectedDigest: createHash('sha256').update(desired).digest('hex'),
|
|
690
723
|
dependency: 'deferred',
|
|
691
|
-
pullRequestSummary: {
|
|
724
|
+
pullRequestSummary: {
|
|
725
|
+
body: options.body,
|
|
726
|
+
changeReportFile: options.changeReportFile,
|
|
727
|
+
...(manual?.transactionId ? { manualValidation: { ...manual, transactionId: manual.transactionId } } : {})
|
|
728
|
+
}
|
|
692
729
|
};
|
|
693
730
|
if (options.skipQueue)
|
|
694
731
|
return publish();
|
|
@@ -317,6 +317,21 @@ function normalizeProviderPullRequest(remote, repository, fallbackNumber, fallba
|
|
|
317
317
|
Object.defineProperty(snapshot, 'identity', { value: identity, enumerable: false, configurable: true });
|
|
318
318
|
return snapshot;
|
|
319
319
|
}
|
|
320
|
+
function inspectedPullRequestIdentityError(remote, requested) {
|
|
321
|
+
if (!remote.identity)
|
|
322
|
+
return {
|
|
323
|
+
code: 'PR_IDENTITY_MISSING',
|
|
324
|
+
message: 'Provider pull-request inspection did not return a canonical identity',
|
|
325
|
+
retryable: false
|
|
326
|
+
};
|
|
327
|
+
if (!resourceIdentityEquals(remote.identity, requested))
|
|
328
|
+
return {
|
|
329
|
+
code: 'PR_IDENTITY_MISMATCH',
|
|
330
|
+
message: 'Provider returned a different pull request identity than requested',
|
|
331
|
+
retryable: false
|
|
332
|
+
};
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
320
335
|
function pullRequestIdentity(pullRequest) {
|
|
321
336
|
return pullRequest.identity || { kind: 'number', value: pullRequest.number };
|
|
322
337
|
}
|
|
@@ -339,6 +354,9 @@ async function inspectExternalPullRequest(base, prIdentity) {
|
|
|
339
354
|
: unsupportedProviderOperation(base.provider, 'changeRequests.inspect');
|
|
340
355
|
if (!inspected.ok)
|
|
341
356
|
return providerPullRequestError(base, inspected.error, prNumber);
|
|
357
|
+
const identityError = inspectedPullRequestIdentityError(inspected.value, prIdentity);
|
|
358
|
+
if (identityError)
|
|
359
|
+
return providerPullRequestError(base, identityError, prNumber);
|
|
342
360
|
const pullRequest = normalizeProviderPullRequest(inspected.value, base.context.platform.repository, prNumber || 0, fallback);
|
|
343
361
|
return result('no-op', base.resolved.taskId, base.issueNumber, prNumber, {
|
|
344
362
|
platform: base.context.platform,
|
|
@@ -392,6 +410,12 @@ async function inspectPlatformPullRequestByNumber(prNumber, options = {}) {
|
|
|
392
410
|
platform: context.platform, capabilities: context.capabilities,
|
|
393
411
|
resource: { kind: 'pull-request', number, identity }, error: providerError(inspected.error, 'PLATFORM_PROVIDER_OPERATION_FAILED')
|
|
394
412
|
});
|
|
413
|
+
const identityError = inspectedPullRequestIdentityError(inspected.value, identity);
|
|
414
|
+
if (identityError)
|
|
415
|
+
return result('failed', null, null, number, {
|
|
416
|
+
platform: context.platform, capabilities: context.capabilities,
|
|
417
|
+
resource: { kind: 'pull-request', number, identity }, error: identityError
|
|
418
|
+
});
|
|
395
419
|
return result('no-op', null, null, number, {
|
|
396
420
|
platform: context.platform, capabilities: context.capabilities,
|
|
397
421
|
resource: { kind: 'pull-request', number, identity },
|
|
@@ -448,27 +472,38 @@ function resourceIdentityLabel(identity) {
|
|
|
448
472
|
const number = resourceIdentityNumber(identity);
|
|
449
473
|
return number === null ? `${identity.kind} ${identity.value}` : `#${number}`;
|
|
450
474
|
}
|
|
451
|
-
function inLabelResource(kind, identity, before, expected, after, effect) {
|
|
452
|
-
return { kind, number:
|
|
475
|
+
function inLabelResource(kind, identity, before, expected, after, effect, displayNumber = resourceIdentityNumber(identity)) {
|
|
476
|
+
return { kind, number: displayNumber, identity, before: [...before].sort(), expected: [...expected].sort(), after: after ? [...after].sort() : null, effect };
|
|
453
477
|
}
|
|
454
|
-
async function syncPlatformPullRequestInLabels(
|
|
478
|
+
async function syncPlatformPullRequestInLabels(prToken, options = {}) {
|
|
455
479
|
const cwd = path.resolve(options.cwd || process.cwd());
|
|
480
|
+
const token = String(prToken);
|
|
456
481
|
const loaded = await resolvePlatformProviderContext({ cwd, client: options.client });
|
|
457
482
|
const context = loaded.ok ? loaded.value.context : loaded.context;
|
|
458
483
|
const usable = (context.status === 'no-op' || context.status === 'degraded') && context.platform.repository;
|
|
459
484
|
if (!loaded.ok || !usable)
|
|
460
|
-
return result(context.status, null, null,
|
|
485
|
+
return result(context.status, null, null, null, {
|
|
461
486
|
platform: context.platform, capabilities: context.capabilities, operations: context.operations, error: context.error
|
|
462
487
|
});
|
|
463
|
-
if (!
|
|
464
|
-
return result('failed', null, null,
|
|
488
|
+
if (!token || token.trim() !== token)
|
|
489
|
+
return result('failed', null, null, null, {
|
|
465
490
|
platform: context.platform, capabilities: context.capabilities,
|
|
466
|
-
error: { code: '
|
|
491
|
+
error: { code: 'PR_IDENTITY_INVALID', message: 'PR identity token must be non-empty and trimmed', retryable: false }
|
|
467
492
|
});
|
|
468
493
|
const provider = loaded.value.provider;
|
|
469
494
|
const operationContext = providerOperationContext(loaded.value);
|
|
470
495
|
const repository = context.platform.repository;
|
|
471
|
-
|
|
496
|
+
let pullRequestIdentity;
|
|
497
|
+
try {
|
|
498
|
+
pullRequestIdentity = providerResourceToken(provider, 'pull-request', token);
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
return result('failed', null, null, null, {
|
|
502
|
+
platform: context.platform, capabilities: context.capabilities,
|
|
503
|
+
error: { code: 'PR_IDENTITY_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false }
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
let prNumber = resourceIdentityNumber(pullRequestIdentity);
|
|
472
507
|
const providerFailure = (error) => result(error.code === 'PLATFORM_CAPABILITY_UNSUPPORTED' ? 'degraded' : providerStatus(error), null, null, prNumber, {
|
|
473
508
|
platform: context.platform,
|
|
474
509
|
capabilities: context.capabilities,
|
|
@@ -480,7 +515,13 @@ async function syncPlatformPullRequestInLabels(prNumber, options = {}) {
|
|
|
480
515
|
: unsupportedProviderOperation(provider, 'changeRequests.inspect');
|
|
481
516
|
if (!inspected.ok)
|
|
482
517
|
return providerFailure(inspected.error);
|
|
483
|
-
const
|
|
518
|
+
const identityError = inspectedPullRequestIdentityError(inspected.value, pullRequestIdentity);
|
|
519
|
+
if (identityError)
|
|
520
|
+
return providerFailure(identityError);
|
|
521
|
+
if (Number.isSafeInteger(inspected.value.number) && inspected.value.number > 0) {
|
|
522
|
+
prNumber = inspected.value.number;
|
|
523
|
+
}
|
|
524
|
+
const fetched = normalizeProviderPullRequest(inspected.value, repository, prNumber ?? 0);
|
|
484
525
|
const files = provider.changeRequests?.listFiles
|
|
485
526
|
? await provider.changeRequests.listFiles({ context: operationContext, target: pullRequestIdentity })
|
|
486
527
|
: unsupportedProviderOperation(provider, 'changeRequests.listFiles');
|
|
@@ -549,7 +590,7 @@ async function syncPlatformPullRequestInLabels(prNumber, options = {}) {
|
|
|
549
590
|
}))
|
|
550
591
|
];
|
|
551
592
|
const willWrite = context.capabilities.triage && !options.dryRun;
|
|
552
|
-
const resourcesState = plans.map((item) => inLabelResource(item.kind, item.identity, item.snapshot.labels, item.plan.target, item.plan.changed && willWrite ? null : item.snapshot.labels, item.plan.changed && willWrite ? 'unknown' : 'no-op'));
|
|
593
|
+
const resourcesState = plans.map((item) => inLabelResource(item.kind, item.identity, item.snapshot.labels, item.plan.target, item.plan.changed && willWrite ? null : item.snapshot.labels, item.plan.changed && willWrite ? 'unknown' : 'no-op', item.number));
|
|
553
594
|
if (options.dryRun || !context.capabilities.triage)
|
|
554
595
|
return result(!context.capabilities.triage || association.value.length !== 1 ? 'degraded' : plans.some((item) => item.plan.changed) ? 'planned' : 'no-op', null, issueNumber, prNumber, {
|
|
555
596
|
platform: context.platform, capabilities: context.capabilities, pullRequest: fetched,
|
|
@@ -611,6 +652,16 @@ async function syncPlatformPullRequestInLabels(prNumber, options = {}) {
|
|
|
611
652
|
evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: closingIssueIdentities },
|
|
612
653
|
error: { ...providerError(reread.error, 'PLATFORM_PROVIDER_OPERATION_FAILED'), code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${reread.error.message}` }
|
|
613
654
|
});
|
|
655
|
+
if (item.kind === 'pull-request') {
|
|
656
|
+
const identityError = inspectedPullRequestIdentityError(reread.value, item.identity);
|
|
657
|
+
if (identityError)
|
|
658
|
+
return result('blocked', null, issueNumber, prNumber, {
|
|
659
|
+
changed: true, platform: context.platform, capabilities: context.capabilities,
|
|
660
|
+
pullRequest: currentPullRequest, resource: { kind: 'pull-request', number: prNumber, identity: pullRequestIdentity }, operations, resources: resourcesState,
|
|
661
|
+
evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: closingIssueIdentities },
|
|
662
|
+
error: { ...identityError, code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${identityError.message}` }
|
|
663
|
+
});
|
|
664
|
+
}
|
|
614
665
|
const after = reread.value.labels || [];
|
|
615
666
|
const state = resourcesState.find((resource) => resource.kind === item.kind && resourceIdentityEquals(resource.identity, item.identity));
|
|
616
667
|
state.after = after;
|
|
@@ -623,7 +674,7 @@ async function syncPlatformPullRequestInLabels(prNumber, options = {}) {
|
|
|
623
674
|
error: { code: 'IN_LABEL_SYNC_PARTIAL', message: `${item.kind} ${resourceIdentityLabel(item.identity)} did not converge to the expected in: labels`, retryable: true }
|
|
624
675
|
});
|
|
625
676
|
if (item.kind === 'pull-request')
|
|
626
|
-
currentPullRequest = normalizeProviderPullRequest(reread.value, repository, prNumber, currentPullRequest);
|
|
677
|
+
currentPullRequest = normalizeProviderPullRequest(reread.value, repository, prNumber ?? currentPullRequest.number, currentPullRequest);
|
|
627
678
|
}
|
|
628
679
|
return result(association.value.length !== 1 ? 'degraded' : successfulWrites > 0 ? 'applied' : 'no-op', null, issueNumber, prNumber, {
|
|
629
680
|
changed: successfulWrites > 0, platform: context.platform, capabilities: context.capabilities,
|
|
@@ -856,9 +907,13 @@ async function resolveExternalPullRequest(taskRef, options) {
|
|
|
856
907
|
? await base.provider.changeRequests.inspect({
|
|
857
908
|
context: providerOperationContext(base.loadedContext),
|
|
858
909
|
target: pullRequestIdentity(selected.selected)
|
|
859
|
-
}).then((response) =>
|
|
860
|
-
|
|
861
|
-
|
|
910
|
+
}).then((response) => {
|
|
911
|
+
if (!response.ok)
|
|
912
|
+
return response;
|
|
913
|
+
const identityError = inspectedPullRequestIdentityError(response.value, pullRequestIdentity(selected.selected));
|
|
914
|
+
return identityError ? { ok: false, error: identityError }
|
|
915
|
+
: { ok: true, value: normalizeProviderPullRequest(response.value, base.context.platform.repository, selected.selected.number, selected.selected) };
|
|
916
|
+
})
|
|
862
917
|
: unsupportedProviderOperation(base.provider, 'changeRequests.inspect');
|
|
863
918
|
if (!rechecked.ok || !rechecked.value)
|
|
864
919
|
return externalResult(result(rechecked.ok ? 'failed' : rechecked.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
@@ -1152,6 +1207,9 @@ async function createExternalPullRequest(taskRef, options, base) {
|
|
|
1152
1207
|
: unsupportedProviderOperation(base.provider, 'changeRequests.inspect');
|
|
1153
1208
|
if (!inspected.ok)
|
|
1154
1209
|
return withCreation(providerPullRequestError(base, inspected.error, number), { kind: 'created', createdByCurrentOperation: true });
|
|
1210
|
+
const identityError = inspectedPullRequestIdentityError(inspected.value, createdIdentity);
|
|
1211
|
+
if (identityError)
|
|
1212
|
+
return withCreation(providerPullRequestError(base, identityError, number), { kind: 'created', createdByCurrentOperation: true });
|
|
1155
1213
|
const pullRequest = normalizeProviderPullRequest(inspected.value, base.context.platform.repository, inspected.value.number ?? fallback.number, fallback);
|
|
1156
1214
|
if (!pullRequest.nodeId || !isUsableResourceIdentity(pullRequestIdentity(pullRequest)))
|
|
1157
1215
|
return withCreation(result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
@@ -1354,6 +1412,13 @@ async function syncPlatformPullRequestImpl(taskRef, options) {
|
|
|
1354
1412
|
code: 'IN_LABEL_SYNC_PARTIAL',
|
|
1355
1413
|
message: `In-label synchronization is partial or unknown: ${reread.error.message}`
|
|
1356
1414
|
}, base.prNumber));
|
|
1415
|
+
const identityError = inspectedPullRequestIdentityError(reread.value, base.prIdentity);
|
|
1416
|
+
if (identityError)
|
|
1417
|
+
return softenFailure(providerPullRequestError(base, {
|
|
1418
|
+
...identityError,
|
|
1419
|
+
code: 'IN_LABEL_SYNC_PARTIAL',
|
|
1420
|
+
message: `Pull-request metadata synchronization is partial or unknown: ${identityError.message}`
|
|
1421
|
+
}, base.prNumber));
|
|
1357
1422
|
finalPullRequest = normalizeProviderPullRequest(reread.value, base.context.platform.repository, base.prNumber || 0, inspected.pullRequest);
|
|
1358
1423
|
const expected = ((planned.find((operation) => operation.name === 'labels')?.value || [])
|
|
1359
1424
|
.filter((label) => label.startsWith('in:') || label.startsWith('type:'))).sort();
|
|
@@ -457,7 +457,7 @@ function checkTaskCommentContent(context, remoteData, shared) {
|
|
|
457
457
|
return shared.failResult(CHECK_TYPE, buildCommentContentMismatchMessage("task", context.issueNumber, expectedBody, commentBody), "check_failed");
|
|
458
458
|
}
|
|
459
459
|
function checkPrTypeLabel(context, remoteData, shared) {
|
|
460
|
-
if (!context.audit.enabled || !context.hasTriage || !context.
|
|
460
|
+
if (!context.audit.enabled || !context.hasTriage || !context.prIdentity || remoteData.prLabels === undefined || remoteData.prLabels === null) {
|
|
461
461
|
return infoResult('PR type-label audit is not applicable because the required PR metadata or capability is unavailable');
|
|
462
462
|
}
|
|
463
463
|
const expectedLabel = mapTaskTypeToLabel(context.task.metadata.type);
|
|
@@ -465,12 +465,12 @@ function checkPrTypeLabel(context, remoteData, shared) {
|
|
|
465
465
|
return infoResult('PR type-label audit is not applicable because the task type has no label mapping');
|
|
466
466
|
}
|
|
467
467
|
if (remoteData.prLabels.includes(expectedLabel)) {
|
|
468
|
-
return shared.passResult(CHECK_TYPE,
|
|
468
|
+
return shared.passResult(CHECK_TYPE, `${pullRequestLabel(context)} has expected type label '${expectedLabel}'`);
|
|
469
469
|
}
|
|
470
|
-
return shared.failResult(CHECK_TYPE, `Expected type label '${expectedLabel}' not found on
|
|
470
|
+
return shared.failResult(CHECK_TYPE, `Expected type label '${expectedLabel}' not found on ${pullRequestLabel(context)}`, "check_failed");
|
|
471
471
|
}
|
|
472
472
|
function checkInLabelsMatchPr(context, remoteData, shared) {
|
|
473
|
-
if (!context.audit.enabled || !context.hasTriage || !context.
|
|
473
|
+
if (!context.audit.enabled || !context.hasTriage || !context.prIdentity || remoteData.prLabels === undefined || remoteData.prLabels === null) {
|
|
474
474
|
return infoResult('PR in: label audit is not applicable because the required PR metadata or capability is unavailable');
|
|
475
475
|
}
|
|
476
476
|
const issueInLabels = extractLabelNames(remoteData.issue.labels)
|
|
@@ -480,9 +480,12 @@ function checkInLabelsMatchPr(context, remoteData, shared) {
|
|
|
480
480
|
.filter((label) => label.startsWith("in:"))
|
|
481
481
|
.sort();
|
|
482
482
|
if (arraysEqual(issueInLabels, prInLabels)) {
|
|
483
|
-
return shared.passResult(CHECK_TYPE,
|
|
483
|
+
return shared.passResult(CHECK_TYPE, `${pullRequestLabel(context)} in: labels match Issue #${context.issueNumber}`);
|
|
484
484
|
}
|
|
485
|
-
return shared.failResult(CHECK_TYPE, `in: labels mismatch —
|
|
485
|
+
return shared.failResult(CHECK_TYPE, `in: labels mismatch — ${pullRequestLabel(context)} has [${formatLabelList(prInLabels)}], Issue #${context.issueNumber} has [${formatLabelList(issueInLabels)}]`, "check_failed");
|
|
486
|
+
}
|
|
487
|
+
function pullRequestLabel(context) {
|
|
488
|
+
return context.prNumber ? `PR #${context.prNumber}` : 'Pull request';
|
|
486
489
|
}
|
|
487
490
|
function checkInLabelsComputed(context, remoteData, shared) {
|
|
488
491
|
if (!context.audit.enabled || !context.hasTriage) {
|
|
@@ -574,13 +577,13 @@ function checkIssueFields(context, remoteData, shared) {
|
|
|
574
577
|
return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} fields match task metadata`);
|
|
575
578
|
}
|
|
576
579
|
function checkPrAssignee(context, remoteData, shared) {
|
|
577
|
-
if (!context.audit.enabled || !context.hasPush || !context.
|
|
580
|
+
if (!context.audit.enabled || !context.hasPush || !context.prIdentity || remoteData.prAssignees === undefined || remoteData.prAssignees === null) {
|
|
578
581
|
return infoResult('PR assignee audit is not applicable because it is disabled or required PR metadata/capability is unavailable');
|
|
579
582
|
}
|
|
580
|
-
if (
|
|
581
|
-
return shared.failResult(CHECK_TYPE,
|
|
583
|
+
if (remoteData.prAssignees.length === 0) {
|
|
584
|
+
return shared.failResult(CHECK_TYPE, `${pullRequestLabel(context)} has no assignee`, "check_failed");
|
|
582
585
|
}
|
|
583
|
-
return shared.passResult(CHECK_TYPE,
|
|
586
|
+
return shared.passResult(CHECK_TYPE, `${pullRequestLabel(context)} has an assignee`);
|
|
584
587
|
}
|
|
585
588
|
function checkMilestone(context, remoteData, shared) {
|
|
586
589
|
if (!context.hasTriage) {
|
|
@@ -589,16 +592,16 @@ function checkMilestone(context, remoteData, shared) {
|
|
|
589
592
|
if (!remoteData.issue?.milestone?.title) {
|
|
590
593
|
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} has no milestone set`, "check_failed");
|
|
591
594
|
}
|
|
592
|
-
if (context.
|
|
593
|
-
return shared.failResult(CHECK_TYPE,
|
|
595
|
+
if (context.prIdentity && remoteData.prMilestone !== undefined && !remoteData.prMilestone?.title) {
|
|
596
|
+
return shared.failResult(CHECK_TYPE, `${pullRequestLabel(context)} has no milestone set`, "check_failed");
|
|
594
597
|
}
|
|
595
598
|
if (context.audit.requireSpecificMilestone) {
|
|
596
599
|
const issueTitle = remoteData.issue.milestone.title;
|
|
597
600
|
if (VERSION_LINE_REGEX.test(issueTitle)) {
|
|
598
601
|
return shared.failResult(CHECK_TYPE, `Issue #${context.issueNumber} milestone '${issueTitle}' is a release line; narrow to a specific version (e.g. ${issueTitle.replace(/\.x$/, ".N")}) before continuing`, "check_failed");
|
|
599
602
|
}
|
|
600
|
-
if (context.
|
|
601
|
-
return shared.failResult(CHECK_TYPE,
|
|
603
|
+
if (context.prIdentity && remoteData.prMilestone?.title && VERSION_LINE_REGEX.test(remoteData.prMilestone.title)) {
|
|
604
|
+
return shared.failResult(CHECK_TYPE, `${pullRequestLabel(context)} milestone '${remoteData.prMilestone.title}' is a release line; narrow to a specific version before continuing`, "check_failed");
|
|
602
605
|
}
|
|
603
606
|
}
|
|
604
607
|
return shared.passResult(CHECK_TYPE, `Issue #${context.issueNumber} milestone satisfies the configured policy`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
|
|
2
|
+
import { resolveTaskRuntimeRoot } from '../task/runtime-paths.js';
|
|
3
3
|
function runtimeError(code, message) {
|
|
4
4
|
const error = new Error(`${code}: ${message}`);
|
|
5
5
|
error.name = code;
|
|
@@ -9,16 +9,9 @@ function boundControlContext(env) {
|
|
|
9
9
|
return [
|
|
10
10
|
'AGENT_INFRA_CONTROL_TOKEN',
|
|
11
11
|
'AGENT_INFRA_CONTROL_GENERATION',
|
|
12
|
-
'AGENT_INFRA_EXECUTOR_MANIFEST'
|
|
13
|
-
'AGENT_INFRA_CODEX_CONTROLLER_CONTEXT'
|
|
12
|
+
'AGENT_INFRA_EXECUTOR_MANIFEST'
|
|
14
13
|
].some((key) => typeof env[key] === 'string' && env[key].length > 0);
|
|
15
14
|
}
|
|
16
|
-
function clientNamespace(client) {
|
|
17
|
-
if (!CLIENT_NAMESPACE.test(client)) {
|
|
18
|
-
throw runtimeError('AGENT_INFRA_RUNTIME_CLIENT_INVALID', `invalid runtime client namespace '${client}'`);
|
|
19
|
-
}
|
|
20
|
-
return client;
|
|
21
|
-
}
|
|
22
15
|
export function resolveAgentRuntimeRoot(options = {}) {
|
|
23
16
|
const env = options.env ?? process.env;
|
|
24
17
|
const configured = env.AGENT_INFRA_RUNTIME_DIR;
|
|
@@ -28,29 +21,13 @@ export function resolveAgentRuntimeRoot(options = {}) {
|
|
|
28
21
|
}
|
|
29
22
|
return path.resolve(configured);
|
|
30
23
|
}
|
|
24
|
+
const taskId = env.AGENT_INFRA_TASK_ID;
|
|
25
|
+
if (taskId) {
|
|
26
|
+
return resolveTaskRuntimeRoot(taskId, { repoRoot: options.repoRoot });
|
|
27
|
+
}
|
|
31
28
|
if (boundControlContext(env)) {
|
|
32
29
|
throw runtimeError('AGENT_INFRA_RUNTIME_DIR_REQUIRED', 'task-bound control context requires AGENT_INFRA_RUNTIME_DIR');
|
|
33
30
|
}
|
|
34
31
|
return path.join(path.resolve(options.repoRoot ?? process.cwd()), '.agents', 'workspace', '.runtime');
|
|
35
32
|
}
|
|
36
|
-
export function resolveAgentRuntimeStoreRoot(options = {}) {
|
|
37
|
-
const client = clientNamespace(options.client ?? 'codex');
|
|
38
|
-
if (options.explicitRoot)
|
|
39
|
-
return path.resolve(options.explicitRoot);
|
|
40
|
-
const env = options.env ?? process.env;
|
|
41
|
-
const runtimeRoot = resolveAgentRuntimeRoot({ repoRoot: options.repoRoot, env });
|
|
42
|
-
if (env.AGENT_INFRA_RUNTIME_DIR) {
|
|
43
|
-
if (!options.store) {
|
|
44
|
-
throw runtimeError('AGENT_INFRA_RUNTIME_STORE_INVALID', 'store kind is required for task-bound runtime');
|
|
45
|
-
}
|
|
46
|
-
return path.join(runtimeRoot, 'clients', client, options.store);
|
|
47
|
-
}
|
|
48
|
-
if (client === 'codex' && options.store) {
|
|
49
|
-
return path.join(runtimeRoot, `codex-${options.store}`);
|
|
50
|
-
}
|
|
51
|
-
if (!options.store) {
|
|
52
|
-
throw runtimeError('AGENT_INFRA_RUNTIME_STORE_INVALID', 'store kind is required');
|
|
53
|
-
}
|
|
54
|
-
return path.join(runtimeRoot, 'clients', client, options.store);
|
|
55
|
-
}
|
|
56
33
|
//# sourceMappingURL=agent-runtime.js.map
|
|
@@ -672,7 +672,7 @@ function readProjectInitCommandDigest(controlRoot) {
|
|
|
672
672
|
function writeProjectInitCommandDigest(controlRoot, commandSha256) {
|
|
673
673
|
atomicWriteJson(path.join(controlRoot, 'project-init.json'), { version: 1, commandSha256 });
|
|
674
674
|
}
|
|
675
|
-
export async function create(args, { runProjectInitCommand = true } = {}) {
|
|
675
|
+
export async function create(args, { runProjectInitCommand = true, workspace: workspaceOverride } = {}) {
|
|
676
676
|
const { values, positionals } = parseArgs({
|
|
677
677
|
args,
|
|
678
678
|
allowPositionals: true,
|
|
@@ -696,7 +696,13 @@ export async function create(args, { runProjectInitCommand = true } = {}) {
|
|
|
696
696
|
validateSelinuxDisableEnv();
|
|
697
697
|
const config = loadConfig();
|
|
698
698
|
const [branchOrTaskId = '', base] = positionals;
|
|
699
|
-
const
|
|
699
|
+
const resolvedTarget = resolveSandboxTarget(branchOrTaskId, config.repoRoot);
|
|
700
|
+
const workspace = workspaceOverride?.mode === 'task-bound'
|
|
701
|
+
&& resolvedTarget.workspace.mode === 'task-bound'
|
|
702
|
+
&& workspaceOverride.taskId === resolvedTarget.workspace.taskId
|
|
703
|
+
? resolvedTarget.workspace
|
|
704
|
+
: workspaceOverride ?? resolvedTarget.workspace;
|
|
705
|
+
const target = { ...resolvedTarget, workspace };
|
|
700
706
|
const branch = target.branch;
|
|
701
707
|
assertValidBranchName(branch);
|
|
702
708
|
const effectiveConfig = {
|
|
@@ -735,6 +741,7 @@ export async function create(args, { runProjectInitCommand = true } = {}) {
|
|
|
735
741
|
const container = containerName(effectiveConfig, branch);
|
|
736
742
|
const controlPaths = sandboxControlPaths({
|
|
737
743
|
base: effectiveConfig.controlBase,
|
|
744
|
+
repoRoot: effectiveConfig.repoRoot,
|
|
738
745
|
project: effectiveConfig.project,
|
|
739
746
|
container,
|
|
740
747
|
identity: target.workspace
|
|
@@ -866,6 +873,9 @@ export async function create(args, { runProjectInitCommand = true } = {}) {
|
|
|
866
873
|
task: async () => {
|
|
867
874
|
for (const { tool, dir } of effectiveResolvedTools) {
|
|
868
875
|
fs.mkdirSync(dir, { recursive: true });
|
|
876
|
+
for (const { hostSubdir } of tool.hostStateMounts ?? []) {
|
|
877
|
+
fs.mkdirSync(path.join(dir, hostSubdir), { recursive: true });
|
|
878
|
+
}
|
|
869
879
|
for (const { hostPath, sandboxName } of tool.hostPreSeedFiles ?? []) {
|
|
870
880
|
const destination = path.join(dir, sandboxName);
|
|
871
881
|
if (fs.existsSync(hostPath) && !fs.existsSync(destination)) {
|
|
@@ -1105,6 +1115,10 @@ export async function create(args, { runProjectInitCommand = true } = {}) {
|
|
|
1105
1115
|
'-v',
|
|
1106
1116
|
volumeArg(engine, hostPath, path.posix.join(tool.containerMount, containerSubpath))
|
|
1107
1117
|
]));
|
|
1118
|
+
const hostStateMountVolumes = effectiveResolvedTools.flatMap(({ tool, dir }) => (tool.hostStateMounts ?? []).flatMap(({ hostSubdir, containerSubpath }) => [
|
|
1119
|
+
'-v',
|
|
1120
|
+
volumeArg(engine, path.join(dir, hostSubdir), path.posix.join(tool.containerMount, containerSubpath))
|
|
1121
|
+
]));
|
|
1108
1122
|
fs.mkdirSync(shareCommon, { recursive: true });
|
|
1109
1123
|
fs.mkdirSync(shareBranch, { recursive: true });
|
|
1110
1124
|
fs.mkdirSync(clipboardHostDir(effectiveConfig.home), { recursive: true, mode: 0o700 });
|
|
@@ -1133,6 +1147,8 @@ export async function create(args, { runProjectInitCommand = true } = {}) {
|
|
|
1133
1147
|
`${sandboxBranchLabel(effectiveConfig)}=${branch}`,
|
|
1134
1148
|
'--label',
|
|
1135
1149
|
`${sandboxWorkspaceModeLabel(effectiveConfig)}=${target.workspace.mode}`,
|
|
1150
|
+
'-e',
|
|
1151
|
+
'AGENT_INFRA_SANDBOX=1',
|
|
1136
1152
|
...(target.workspace.mode === 'task-bound'
|
|
1137
1153
|
? [
|
|
1138
1154
|
'--label',
|
|
@@ -1163,6 +1179,7 @@ export async function create(args, { runProjectInitCommand = true } = {}) {
|
|
|
1163
1179
|
...tmpfsArgs,
|
|
1164
1180
|
...tmpfsSeedPlan.flatMap(({ volumeArgs }) => volumeArgs),
|
|
1165
1181
|
...liveMountVolumes,
|
|
1182
|
+
...hostStateMountVolumes,
|
|
1166
1183
|
...envFile.dockerArgs,
|
|
1167
1184
|
...tzFlags,
|
|
1168
1185
|
'-w',
|
|
@@ -8,15 +8,16 @@ import { dotfilesCacheDir, materializeDotfiles } from '../dotfiles.js';
|
|
|
8
8
|
import { redactCommandError } from '../redaction.js';
|
|
9
9
|
import { runInteractiveWithClipboardBridge } from '../clipboard/bridge.js';
|
|
10
10
|
import { detectHostTimezone } from '../host-timezone.js';
|
|
11
|
-
import { fetchSandboxRows, selectSandboxContainer, } from './list-running.js';
|
|
12
|
-
import {
|
|
11
|
+
import { fetchSandboxRows, selectSandboxContainer, startSandboxContainer, } from './list-running.js';
|
|
12
|
+
import { resolveSandboxTarget } from '../workspace-identity.js';
|
|
13
13
|
const USAGE = `Usage: ai sandbox exec [--recreate] <branch | TASK-id | N> [cmd...]
|
|
14
14
|
|
|
15
15
|
N references an active task short id from
|
|
16
16
|
.agents/workspace/active/.short-ids.json. They resolve only via that
|
|
17
17
|
registry — they do not reference a container's row position in
|
|
18
|
-
'ai sandbox ls' output. --recreate
|
|
19
|
-
|
|
18
|
+
'ai sandbox ls' output. --recreate rebuilds the matched container while
|
|
19
|
+
preserving its task or branch identity. The same token after the target is
|
|
20
|
+
passed to the container command.`;
|
|
20
21
|
const TMUX_ENTRY_PATH = '/usr/local/bin/sandbox-tmux-entry';
|
|
21
22
|
// Terminal-detection variables that interactive TUIs (e.g. claude-code)
|
|
22
23
|
// inspect to enable progressive enhancements such as the kitty keyboard
|
|
@@ -78,41 +79,54 @@ export async function enter(args) {
|
|
|
78
79
|
const config = loadConfig();
|
|
79
80
|
const engine = detectEngine(config);
|
|
80
81
|
const parsed = parseEnterArgs(args);
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
82
|
+
const fullTaskId = /^TASK-\d{8}-\d{6}$/.test(parsed.target);
|
|
83
|
+
const target = fullTaskId ? null : resolveSandboxTarget(parsed.target, config.repoRoot);
|
|
84
|
+
const branch = target?.branch ?? '';
|
|
85
|
+
if (branch)
|
|
86
|
+
assertValidBranchName(branch);
|
|
84
87
|
const { running, nonRunning } = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config), {
|
|
85
88
|
mode: sandboxWorkspaceModeLabel(config),
|
|
86
89
|
taskId: sandboxTaskIdLabel(config)
|
|
87
90
|
});
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
const rows = [...running, ...nonRunning];
|
|
92
|
+
const found = fullTaskId
|
|
93
|
+
? rows.find((row) => row.taskId === parsed.target)
|
|
94
|
+
: rows.find((row) => row.branch === branch)
|
|
95
|
+
?? selectSandboxContainer(rows, containerNameCandidates(config, branch));
|
|
96
|
+
if (!found)
|
|
97
|
+
throw new Error(`No sandbox found for '${parsed.target}'.`);
|
|
98
|
+
const sandboxBranch = found.branch || branch;
|
|
99
|
+
if (sandboxBranch)
|
|
100
|
+
assertValidBranchName(sandboxBranch);
|
|
101
|
+
if (target?.workspace.mode === 'task-bound' && found.taskId !== target.workspace.taskId) {
|
|
102
|
+
throw new Error(`Sandbox task mismatch for short id '${parsed.target}'.`);
|
|
91
103
|
}
|
|
92
|
-
const
|
|
104
|
+
const workspace = found.workspaceMode === 'task-bound' && found.taskId
|
|
93
105
|
? { mode: 'task-bound', taskId: found.taskId }
|
|
94
|
-
:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
106
|
+
: { mode: 'branch-only' };
|
|
107
|
+
let container = found.name;
|
|
108
|
+
if (parsed.recreate) {
|
|
109
|
+
const ready = await ensureSandboxReady({
|
|
110
|
+
config,
|
|
111
|
+
engine,
|
|
112
|
+
branch: sandboxBranch,
|
|
113
|
+
workspace,
|
|
114
|
+
row: found,
|
|
115
|
+
allowRecreate: true,
|
|
116
|
+
forceRecreate: true,
|
|
117
|
+
recreate: async () => {
|
|
118
|
+
const { create } = await import('./create.js');
|
|
119
|
+
await create([sandboxBranch, '--no-refresh'], {
|
|
120
|
+
runProjectInitCommand: false,
|
|
121
|
+
workspace
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
container = ready.container;
|
|
126
|
+
}
|
|
127
|
+
else if (!found.running) {
|
|
128
|
+
startSandboxContainer(engine, found.name);
|
|
129
|
+
}
|
|
116
130
|
const cmd = parsed.command;
|
|
117
131
|
const capabilityPlan = createSandboxCapabilityPlan(config);
|
|
118
132
|
const enterHookResults = await runSandboxHooks({
|