@fitlab-ai/agent-infra 0.11.9 → 0.11.11
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 +0 -6
- package/dist/lib/agent-clients/adapter.js +20 -0
- package/dist/lib/agent-clients/adapters/codex-sandbox.js +13 -0
- package/dist/lib/agent-clients/adapters/codex.js +4 -0
- package/dist/lib/git/workflow.js +1 -1
- package/dist/lib/internal/cli-route-inventory.js +0 -3
- package/dist/lib/internal/manual-validation.js +2 -2
- package/dist/lib/internal/platform-pr.js +4 -5
- package/dist/lib/internal/sandbox-control.js +30 -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 +14 -25
- package/dist/lib/internal/task-operation-registry.js +6 -141
- 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/pr-summary.js +45 -8
- 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 +83 -16
- package/dist/lib/platform/verification-sync.js +17 -14
- package/dist/lib/sandbox/commands/create.js +18 -2
- package/dist/lib/sandbox/commands/enter.js +47 -33
- package/dist/lib/sandbox/control/client.js +99 -17
- package/dist/lib/sandbox/control/executor.js +12 -14
- package/dist/lib/sandbox/control/host-recovery.js +218 -0
- package/dist/lib/sandbox/control/protocol.js +2 -3
- package/dist/lib/sandbox/control/server.js +42 -2
- package/dist/lib/sandbox/environment.js +150 -0
- package/dist/lib/sandbox/recovery.js +8 -0
- package/dist/lib/sandbox/removal.js +0 -1
- package/dist/lib/sandbox/workspace-identity.js +0 -14
- package/dist/lib/sandbox/workspace-view.js +33 -6
- package/dist/lib/task/artifact-command.js +18 -3
- package/dist/lib/task/artifact-lifecycle.js +121 -63
- package/dist/lib/task/artifact-selection.js +1 -1
- package/dist/lib/task/capabilities.js +59 -22
- 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 -6
- package/dist/lib/task/current-contract.js +1 -5
- package/dist/lib/task/events.js +9 -275
- package/dist/lib/task/execution-inputs.js +14 -0
- package/dist/lib/task/finalization-envelope.js +4 -0
- package/dist/lib/task/finalization.js +43 -86
- package/dist/lib/task/frontmatter.js +25 -2
- package/dist/lib/task/ledger-intents.js +6 -8
- package/dist/lib/task/orchestration.js +11 -10
- package/dist/lib/task/platform-operation-journal.js +29 -10
- package/dist/lib/task/platform-operation-recovery.js +109 -35
- package/dist/lib/task/resolve-ref.js +8 -3
- package/dist/lib/task/review-fingerprint.js +4 -26
- package/dist/lib/task/verification-engine.js +95 -6
- package/dist/lib/task/write.js +0 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +22 -0
- package/lib/agent-clients/adapters/codex-sandbox.ts +13 -0
- package/lib/agent-clients/adapters/codex.ts +4 -0
- package/lib/git/workflow.ts +1 -1
- package/lib/internal/cli-route-inventory.ts +0 -2
- package/lib/internal/manual-validation.ts +2 -2
- package/lib/internal/platform-pr.ts +3 -4
- package/lib/internal/sandbox-control.ts +39 -6
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-create.ts +15 -2
- package/lib/internal/task-finalization.ts +16 -26
- package/lib/internal/task-operation-registry.ts +6 -153
- 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/pr-summary.ts +43 -7
- 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 +78 -15
- 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/platform/verification-sync.ts +18 -14
- package/lib/sandbox/commands/create.ts +24 -2
- package/lib/sandbox/commands/enter.ts +42 -39
- package/lib/sandbox/control/client.ts +107 -19
- package/lib/sandbox/control/executor.ts +13 -15
- package/lib/sandbox/control/host-recovery.ts +195 -0
- package/lib/sandbox/control/protocol.ts +4 -5
- package/lib/sandbox/control/server.ts +42 -1
- package/lib/sandbox/environment.ts +162 -0
- package/lib/sandbox/recovery.ts +10 -0
- package/lib/sandbox/removal.ts +2 -10
- package/lib/sandbox/tool-types.ts +1 -0
- package/lib/sandbox/workspace-identity.ts +0 -26
- package/lib/sandbox/workspace-view.ts +33 -8
- package/lib/task/artifact-command.ts +19 -3
- package/lib/task/artifact-lifecycle.ts +102 -53
- package/lib/task/artifact-selection.ts +1 -1
- package/lib/task/capabilities.ts +52 -26
- 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 -8
- package/lib/task/current-contract.ts +1 -6
- package/lib/task/events.ts +9 -248
- package/lib/task/execution-inputs.ts +25 -0
- package/lib/task/finalization-envelope.ts +21 -0
- package/lib/task/finalization.ts +40 -82
- package/lib/task/frontmatter.ts +26 -2
- package/lib/task/ledger-intents.ts +6 -8
- package/lib/task/orchestration.ts +11 -17
- package/lib/task/platform-operation-journal.ts +47 -13
- package/lib/task/platform-operation-recovery.ts +102 -37
- package/lib/task/resolve-ref.ts +8 -3
- package/lib/task/review-fingerprint.ts +4 -26
- package/lib/task/verification-engine.ts +103 -9
- package/lib/task/write.ts +0 -19
- package/package.json +1 -1
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- 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/issue-sync.en.md +1 -1
- package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
- 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 +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
- 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/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 +25 -9
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +27 -10
- 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/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/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 +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/complete-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +15 -12
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -10
- 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-code/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
- 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/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 +5 -2
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -2
- 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/templates/.agents/templates/task.zh-CN.md +0 -12
- package/templates/.github/workflows/pr-label.yml +1 -2
- package/dist/lib/internal/task-invalidation.js +0 -63
- package/dist/lib/task/finalization-handoff.js +0 -93
- 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/internal/task-invalidation.ts +0 -41
- package/lib/task/finalization-handoff.ts +0 -105
- package/lib/task/intermediate-cleanup.ts +0 -393
- package/lib/task/invalidation-command.ts +0 -88
- package/lib/task/invalidation.ts +0 -313
|
@@ -37,10 +37,7 @@ function validatePlatformProvider(value, providerType) {
|
|
|
37
37
|
ok: false,
|
|
38
38
|
error: {
|
|
39
39
|
code: 'PLATFORM_PROVIDER_CONTRACT_INVALID',
|
|
40
|
-
message: 'Provider must expose matching type, contractVersion, and context.resolve'
|
|
41
|
-
retryable: false,
|
|
42
|
-
providerType,
|
|
43
|
-
phase: 'provider-validation'
|
|
40
|
+
message: 'Provider must expose matching type, contractVersion, and context.resolve'
|
|
44
41
|
}
|
|
45
42
|
};
|
|
46
43
|
}
|
|
@@ -51,10 +48,7 @@ function validatePlatformProvider(value, providerType) {
|
|
|
51
48
|
ok: false,
|
|
52
49
|
error: {
|
|
53
50
|
code: 'PLATFORM_PROVIDER_CONTRACT_INVALID',
|
|
54
|
-
message: 'Provider identity declaration is invalid'
|
|
55
|
-
retryable: false,
|
|
56
|
-
providerType,
|
|
57
|
-
phase: 'provider-validation'
|
|
51
|
+
message: 'Provider identity declaration is invalid'
|
|
58
52
|
}
|
|
59
53
|
};
|
|
60
54
|
}
|
|
@@ -68,10 +62,7 @@ function validatePlatformProvider(value, providerType) {
|
|
|
68
62
|
ok: false,
|
|
69
63
|
error: {
|
|
70
64
|
code: 'PLATFORM_PROVIDER_CONTRACT_INVALID',
|
|
71
|
-
message: `Provider operation group ${groupName} is incomplete
|
|
72
|
-
retryable: false,
|
|
73
|
-
providerType,
|
|
74
|
-
phase: 'provider-validation'
|
|
65
|
+
message: `Provider operation group ${groupName} is incomplete`
|
|
75
66
|
}
|
|
76
67
|
};
|
|
77
68
|
}
|
|
@@ -84,9 +75,6 @@ function validatePlatformProvider(value, providerType) {
|
|
|
84
75
|
error: {
|
|
85
76
|
code: 'PLATFORM_PROVIDER_CONTRACT_INVALID',
|
|
86
77
|
message: `Provider operation group ${groupName} requires an identity declaration for ${resourceKind}`,
|
|
87
|
-
retryable: false,
|
|
88
|
-
providerType,
|
|
89
|
-
phase: 'provider-validation'
|
|
90
78
|
}
|
|
91
79
|
};
|
|
92
80
|
}
|
|
@@ -462,9 +462,11 @@ const PROVIDER_ERROR_CATALOG = {
|
|
|
462
462
|
REMOTE_INVALID: { message: 'The platform remote is invalid', retryable: false },
|
|
463
463
|
UPSTREAM_UNRESOLVED: { message: 'The platform upstream repository could not be resolved', retryable: false },
|
|
464
464
|
RESOURCE_NOT_FOUND: { message: 'The platform resource was not found', retryable: false },
|
|
465
|
+
COMMENT_LIST_FAILED: { message: 'Comments could not be listed', retryable: true },
|
|
465
466
|
PR_NOT_FOUND: { message: 'The pull request was not found', retryable: false },
|
|
466
467
|
ISSUE_NOT_FOUND: { message: 'The issue was not found', retryable: false },
|
|
467
468
|
REMOTE_BUSY: { message: 'Platform provider is temporarily busy', retryable: true },
|
|
469
|
+
IN_LABEL_SYNC_PARTIAL: { message: 'In-label synchronization is partial or unknown', retryable: true },
|
|
468
470
|
NETWORK_TRANSIENT: { message: 'The platform request failed temporarily', retryable: true },
|
|
469
471
|
NETWORK_RETRY: { message: 'The platform request failed temporarily', retryable: true },
|
|
470
472
|
NETWORK_RETRY_EXHAUSTED: { message: 'The platform request retries were exhausted', retryable: true },
|
|
@@ -505,11 +507,14 @@ function safeProviderError(providerType, operation, value) {
|
|
|
505
507
|
return {
|
|
506
508
|
code: definition ? item.code : 'PLATFORM_PROVIDER_OPERATION_FAILED',
|
|
507
509
|
message: definition?.message || PROVIDER_ERROR_CATALOG.PLATFORM_PROVIDER_OPERATION_FAILED.message,
|
|
508
|
-
retryable: definition?.retryable
|
|
510
|
+
retryable: definition?.retryable ?? false,
|
|
509
511
|
providerType,
|
|
510
512
|
phase: operation
|
|
511
513
|
};
|
|
512
514
|
}
|
|
515
|
+
function providerErrorRetryable(code) {
|
|
516
|
+
return PROVIDER_ERROR_CATALOG[code]?.retryable ?? false;
|
|
517
|
+
}
|
|
513
518
|
function validators(providerType, declaration) {
|
|
514
519
|
return {
|
|
515
520
|
'context.resolve': (value) => validateContext(value, providerType),
|
|
@@ -608,8 +613,8 @@ async function invokeProviderOperation(providerType, operation, call, validate)
|
|
|
608
613
|
try {
|
|
609
614
|
exactKeys(envelope, Object.hasOwn(envelope, 'value') ? ['ok', 'error', 'value'] : ['ok', 'error'], `${operation} result`);
|
|
610
615
|
const error = envelope.error;
|
|
611
|
-
exactKeys(error, ['code', 'message'
|
|
612
|
-
if (typeof error.code !== 'string' || !error.code || typeof error.message !== 'string' || !error.message
|
|
616
|
+
exactKeys(error, ['code', 'message'], `${operation} error`);
|
|
617
|
+
if (typeof error.code !== 'string' || !error.code || typeof error.message !== 'string' || !error.message)
|
|
613
618
|
throw new Error('invalid provider error');
|
|
614
619
|
return {
|
|
615
620
|
ok: false,
|
|
@@ -648,5 +653,5 @@ function wrapProviderOperations(provider) {
|
|
|
648
653
|
}
|
|
649
654
|
return wrapped;
|
|
650
655
|
}
|
|
651
|
-
export { invokeProviderOperation, validationError, wrapProviderOperations };
|
|
656
|
+
export { invokeProviderOperation, providerErrorRetryable, validationError, wrapProviderOperations };
|
|
652
657
|
//# sourceMappingURL=provider-validation.js.map
|
|
@@ -9,6 +9,7 @@ import { extractSection } from '../task/sections.js';
|
|
|
9
9
|
import { captureTaskWriteMetadata, writeTask } from '../task/write.js';
|
|
10
10
|
import { resolvePlatformProviderContext } from './context.js';
|
|
11
11
|
import { inspectPlatformIssue } from './issues.js';
|
|
12
|
+
import { providerErrorRetryable } from './provider-validation.js';
|
|
12
13
|
import { planPullRequestMetadata } from './pull-request-metadata.js';
|
|
13
14
|
import { planInLabelUpdate, validateInLabelMapping } from './in-label-sync.js';
|
|
14
15
|
import { platformResult } from './types.js';
|
|
@@ -316,6 +317,21 @@ function normalizeProviderPullRequest(remote, repository, fallbackNumber, fallba
|
|
|
316
317
|
Object.defineProperty(snapshot, 'identity', { value: identity, enumerable: false, configurable: true });
|
|
317
318
|
return snapshot;
|
|
318
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
|
+
}
|
|
319
335
|
function pullRequestIdentity(pullRequest) {
|
|
320
336
|
return pullRequest.identity || { kind: 'number', value: pullRequest.number };
|
|
321
337
|
}
|
|
@@ -338,6 +354,9 @@ async function inspectExternalPullRequest(base, prIdentity) {
|
|
|
338
354
|
: unsupportedProviderOperation(base.provider, 'changeRequests.inspect');
|
|
339
355
|
if (!inspected.ok)
|
|
340
356
|
return providerPullRequestError(base, inspected.error, prNumber);
|
|
357
|
+
const identityError = inspectedPullRequestIdentityError(inspected.value, prIdentity);
|
|
358
|
+
if (identityError)
|
|
359
|
+
return providerPullRequestError(base, identityError, prNumber);
|
|
341
360
|
const pullRequest = normalizeProviderPullRequest(inspected.value, base.context.platform.repository, prNumber || 0, fallback);
|
|
342
361
|
return result('no-op', base.resolved.taskId, base.issueNumber, prNumber, {
|
|
343
362
|
platform: base.context.platform,
|
|
@@ -391,6 +410,12 @@ async function inspectPlatformPullRequestByNumber(prNumber, options = {}) {
|
|
|
391
410
|
platform: context.platform, capabilities: context.capabilities,
|
|
392
411
|
resource: { kind: 'pull-request', number, identity }, error: providerError(inspected.error, 'PLATFORM_PROVIDER_OPERATION_FAILED')
|
|
393
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
|
+
});
|
|
394
419
|
return result('no-op', null, null, number, {
|
|
395
420
|
platform: context.platform, capabilities: context.capabilities,
|
|
396
421
|
resource: { kind: 'pull-request', number, identity },
|
|
@@ -447,27 +472,38 @@ function resourceIdentityLabel(identity) {
|
|
|
447
472
|
const number = resourceIdentityNumber(identity);
|
|
448
473
|
return number === null ? `${identity.kind} ${identity.value}` : `#${number}`;
|
|
449
474
|
}
|
|
450
|
-
function inLabelResource(kind, identity, before, expected, after, effect) {
|
|
451
|
-
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 };
|
|
452
477
|
}
|
|
453
|
-
async function syncPlatformPullRequestInLabels(
|
|
478
|
+
async function syncPlatformPullRequestInLabels(prToken, options = {}) {
|
|
454
479
|
const cwd = path.resolve(options.cwd || process.cwd());
|
|
480
|
+
const token = String(prToken);
|
|
455
481
|
const loaded = await resolvePlatformProviderContext({ cwd, client: options.client });
|
|
456
482
|
const context = loaded.ok ? loaded.value.context : loaded.context;
|
|
457
483
|
const usable = (context.status === 'no-op' || context.status === 'degraded') && context.platform.repository;
|
|
458
484
|
if (!loaded.ok || !usable)
|
|
459
|
-
return result(context.status, null, null,
|
|
485
|
+
return result(context.status, null, null, null, {
|
|
460
486
|
platform: context.platform, capabilities: context.capabilities, operations: context.operations, error: context.error
|
|
461
487
|
});
|
|
462
|
-
if (!
|
|
463
|
-
return result('failed', null, null,
|
|
488
|
+
if (!token || token.trim() !== token)
|
|
489
|
+
return result('failed', null, null, null, {
|
|
464
490
|
platform: context.platform, capabilities: context.capabilities,
|
|
465
|
-
error: { code: '
|
|
491
|
+
error: { code: 'PR_IDENTITY_INVALID', message: 'PR identity token must be non-empty and trimmed', retryable: false }
|
|
466
492
|
});
|
|
467
493
|
const provider = loaded.value.provider;
|
|
468
494
|
const operationContext = providerOperationContext(loaded.value);
|
|
469
495
|
const repository = context.platform.repository;
|
|
470
|
-
|
|
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);
|
|
471
507
|
const providerFailure = (error) => result(error.code === 'PLATFORM_CAPABILITY_UNSUPPORTED' ? 'degraded' : providerStatus(error), null, null, prNumber, {
|
|
472
508
|
platform: context.platform,
|
|
473
509
|
capabilities: context.capabilities,
|
|
@@ -479,7 +515,13 @@ async function syncPlatformPullRequestInLabels(prNumber, options = {}) {
|
|
|
479
515
|
: unsupportedProviderOperation(provider, 'changeRequests.inspect');
|
|
480
516
|
if (!inspected.ok)
|
|
481
517
|
return providerFailure(inspected.error);
|
|
482
|
-
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);
|
|
483
525
|
const files = provider.changeRequests?.listFiles
|
|
484
526
|
? await provider.changeRequests.listFiles({ context: operationContext, target: pullRequestIdentity })
|
|
485
527
|
: unsupportedProviderOperation(provider, 'changeRequests.listFiles');
|
|
@@ -548,7 +590,7 @@ async function syncPlatformPullRequestInLabels(prNumber, options = {}) {
|
|
|
548
590
|
}))
|
|
549
591
|
];
|
|
550
592
|
const willWrite = context.capabilities.triage && !options.dryRun;
|
|
551
|
-
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));
|
|
552
594
|
if (options.dryRun || !context.capabilities.triage)
|
|
553
595
|
return result(!context.capabilities.triage || association.value.length !== 1 ? 'degraded' : plans.some((item) => item.plan.changed) ? 'planned' : 'no-op', null, issueNumber, prNumber, {
|
|
554
596
|
platform: context.platform, capabilities: context.capabilities, pullRequest: fetched,
|
|
@@ -582,9 +624,10 @@ async function syncPlatformPullRequestInLabels(prNumber, options = {}) {
|
|
|
582
624
|
: unsupportedProviderOperation(provider, 'changeRequests.update');
|
|
583
625
|
if (!synced.ok) {
|
|
584
626
|
const state = resourcesState.find((resource) => resource.kind === item.kind && resourceIdentityEquals(resource.identity, item.identity));
|
|
585
|
-
|
|
627
|
+
const retryable = synced.error.retryable || providerErrorRetryable(synced.error.code);
|
|
628
|
+
state.effect = retryable ? 'unknown' : 'no-op';
|
|
586
629
|
state.after = state.effect === 'unknown' ? null : item.snapshot.labels;
|
|
587
|
-
const partial = successfulWrites > 0 ||
|
|
630
|
+
const partial = successfulWrites > 0 || retryable;
|
|
588
631
|
const error = partial
|
|
589
632
|
? { ...synced.error, code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${synced.error.message}` }
|
|
590
633
|
: synced.error;
|
|
@@ -609,6 +652,16 @@ async function syncPlatformPullRequestInLabels(prNumber, options = {}) {
|
|
|
609
652
|
evidence: { kind: 'pull-request-files', pullRequestFiles: files.value, closingIssues: closingIssueIdentities },
|
|
610
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}` }
|
|
611
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
|
+
}
|
|
612
665
|
const after = reread.value.labels || [];
|
|
613
666
|
const state = resourcesState.find((resource) => resource.kind === item.kind && resourceIdentityEquals(resource.identity, item.identity));
|
|
614
667
|
state.after = after;
|
|
@@ -621,7 +674,7 @@ async function syncPlatformPullRequestInLabels(prNumber, options = {}) {
|
|
|
621
674
|
error: { code: 'IN_LABEL_SYNC_PARTIAL', message: `${item.kind} ${resourceIdentityLabel(item.identity)} did not converge to the expected in: labels`, retryable: true }
|
|
622
675
|
});
|
|
623
676
|
if (item.kind === 'pull-request')
|
|
624
|
-
currentPullRequest = normalizeProviderPullRequest(reread.value, repository, prNumber, currentPullRequest);
|
|
677
|
+
currentPullRequest = normalizeProviderPullRequest(reread.value, repository, prNumber ?? currentPullRequest.number, currentPullRequest);
|
|
625
678
|
}
|
|
626
679
|
return result(association.value.length !== 1 ? 'degraded' : successfulWrites > 0 ? 'applied' : 'no-op', null, issueNumber, prNumber, {
|
|
627
680
|
changed: successfulWrites > 0, platform: context.platform, capabilities: context.capabilities,
|
|
@@ -854,9 +907,13 @@ async function resolveExternalPullRequest(taskRef, options) {
|
|
|
854
907
|
? await base.provider.changeRequests.inspect({
|
|
855
908
|
context: providerOperationContext(base.loadedContext),
|
|
856
909
|
target: pullRequestIdentity(selected.selected)
|
|
857
|
-
}).then((response) =>
|
|
858
|
-
|
|
859
|
-
|
|
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
|
+
})
|
|
860
917
|
: unsupportedProviderOperation(base.provider, 'changeRequests.inspect');
|
|
861
918
|
if (!rechecked.ok || !rechecked.value)
|
|
862
919
|
return externalResult(result(rechecked.ok ? 'failed' : rechecked.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
|
|
@@ -1150,6 +1207,9 @@ async function createExternalPullRequest(taskRef, options, base) {
|
|
|
1150
1207
|
: unsupportedProviderOperation(base.provider, 'changeRequests.inspect');
|
|
1151
1208
|
if (!inspected.ok)
|
|
1152
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 });
|
|
1153
1213
|
const pullRequest = normalizeProviderPullRequest(inspected.value, base.context.platform.repository, inspected.value.number ?? fallback.number, fallback);
|
|
1154
1214
|
if (!pullRequest.nodeId || !isUsableResourceIdentity(pullRequestIdentity(pullRequest)))
|
|
1155
1215
|
return withCreation(result('failed', base.resolved.taskId, base.issueNumber, null, {
|
|
@@ -1352,6 +1412,13 @@ async function syncPlatformPullRequestImpl(taskRef, options) {
|
|
|
1352
1412
|
code: 'IN_LABEL_SYNC_PARTIAL',
|
|
1353
1413
|
message: `In-label synchronization is partial or unknown: ${reread.error.message}`
|
|
1354
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));
|
|
1355
1422
|
finalPullRequest = normalizeProviderPullRequest(reread.value, base.context.platform.repository, base.prNumber || 0, inspected.pullRequest);
|
|
1356
1423
|
const expected = ((planned.find((operation) => operation.name === 'labels')?.value || [])
|
|
1357
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`);
|
|
@@ -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 = {
|
|
@@ -866,6 +872,9 @@ export async function create(args, { runProjectInitCommand = true } = {}) {
|
|
|
866
872
|
task: async () => {
|
|
867
873
|
for (const { tool, dir } of effectiveResolvedTools) {
|
|
868
874
|
fs.mkdirSync(dir, { recursive: true });
|
|
875
|
+
for (const { hostSubdir } of tool.hostStateMounts ?? []) {
|
|
876
|
+
fs.mkdirSync(path.join(dir, hostSubdir), { recursive: true });
|
|
877
|
+
}
|
|
869
878
|
for (const { hostPath, sandboxName } of tool.hostPreSeedFiles ?? []) {
|
|
870
879
|
const destination = path.join(dir, sandboxName);
|
|
871
880
|
if (fs.existsSync(hostPath) && !fs.existsSync(destination)) {
|
|
@@ -1105,6 +1114,10 @@ export async function create(args, { runProjectInitCommand = true } = {}) {
|
|
|
1105
1114
|
'-v',
|
|
1106
1115
|
volumeArg(engine, hostPath, path.posix.join(tool.containerMount, containerSubpath))
|
|
1107
1116
|
]));
|
|
1117
|
+
const hostStateMountVolumes = effectiveResolvedTools.flatMap(({ tool, dir }) => (tool.hostStateMounts ?? []).flatMap(({ hostSubdir, containerSubpath }) => [
|
|
1118
|
+
'-v',
|
|
1119
|
+
volumeArg(engine, path.join(dir, hostSubdir), path.posix.join(tool.containerMount, containerSubpath))
|
|
1120
|
+
]));
|
|
1108
1121
|
fs.mkdirSync(shareCommon, { recursive: true });
|
|
1109
1122
|
fs.mkdirSync(shareBranch, { recursive: true });
|
|
1110
1123
|
fs.mkdirSync(clipboardHostDir(effectiveConfig.home), { recursive: true, mode: 0o700 });
|
|
@@ -1133,6 +1146,8 @@ export async function create(args, { runProjectInitCommand = true } = {}) {
|
|
|
1133
1146
|
`${sandboxBranchLabel(effectiveConfig)}=${branch}`,
|
|
1134
1147
|
'--label',
|
|
1135
1148
|
`${sandboxWorkspaceModeLabel(effectiveConfig)}=${target.workspace.mode}`,
|
|
1149
|
+
'-e',
|
|
1150
|
+
'AGENT_INFRA_SANDBOX=1',
|
|
1136
1151
|
...(target.workspace.mode === 'task-bound'
|
|
1137
1152
|
? [
|
|
1138
1153
|
'--label',
|
|
@@ -1163,6 +1178,7 @@ export async function create(args, { runProjectInitCommand = true } = {}) {
|
|
|
1163
1178
|
...tmpfsArgs,
|
|
1164
1179
|
...tmpfsSeedPlan.flatMap(({ volumeArgs }) => volumeArgs),
|
|
1165
1180
|
...liveMountVolumes,
|
|
1181
|
+
...hostStateMountVolumes,
|
|
1166
1182
|
...envFile.dockerArgs,
|
|
1167
1183
|
...tzFlags,
|
|
1168
1184
|
'-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({
|