@fitlab-ai/agent-infra 0.11.7 → 0.11.9

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.
Files changed (128) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/bin/internal-cli.js +8 -2
  4. package/dist/lib/agent-clients/adapters/codex.js +1 -1
  5. package/dist/lib/agent-clients/config.js +11 -3
  6. package/dist/lib/agent-clients/custom-tool-invocations.js +92 -0
  7. package/dist/lib/agent-clients/next-steps.js +1 -1
  8. package/dist/lib/agent-clients/reconcile.js +3 -3
  9. package/dist/lib/agent-clients/schema.js +18 -10
  10. package/dist/lib/defaults.json +6 -3
  11. package/dist/lib/init.js +2 -2
  12. package/dist/lib/internal/agent-client.js +3 -3
  13. package/dist/lib/internal/cli-route-inventory.js +3 -0
  14. package/dist/lib/internal/platform-pr-review.js +20 -3
  15. package/dist/lib/internal/task-operation-registry.js +3 -1
  16. package/dist/lib/internal/task-platform-recovery.js +56 -0
  17. package/dist/lib/merge.js +25 -5
  18. package/dist/lib/platform/completion-backfill.js +2 -1
  19. package/dist/lib/platform/in-label-sync.js +12 -1
  20. package/dist/lib/platform/issue-comments.js +58 -4
  21. package/dist/lib/platform/issues.js +105 -5
  22. package/dist/lib/platform/operation-coordinator.js +74 -0
  23. package/dist/lib/platform/pr-review.js +107 -1
  24. package/dist/lib/platform/pr-summary.js +79 -57
  25. package/dist/lib/platform/pull-requests.js +96 -4
  26. package/dist/lib/sandbox/commands/create.js +6 -3
  27. package/dist/lib/sandbox/commands/ls.js +4 -3
  28. package/dist/lib/sandbox/commands/rm.js +1 -1
  29. package/dist/lib/sandbox/config.js +22 -4
  30. package/dist/lib/sandbox/git-metadata.js +50 -0
  31. package/dist/lib/sandbox/index.js +1 -1
  32. package/dist/lib/sandbox/mounts.js +12 -0
  33. package/dist/lib/sandbox/recovery.js +12 -22
  34. package/dist/lib/sandbox/removal.js +58 -20
  35. package/dist/lib/sandbox/tools.js +31 -7
  36. package/dist/lib/task/activity-intent.js +4 -3
  37. package/dist/lib/task/create-service.js +5 -3
  38. package/dist/lib/task/create.js +22 -5
  39. package/dist/lib/task/finalization-artifacts.js +4 -1
  40. package/dist/lib/task/finalization.js +243 -113
  41. package/dist/lib/task/lifecycle.js +38 -2
  42. package/dist/lib/task/platform-operation-journal.js +152 -0
  43. package/dist/lib/task/platform-operation-recovery.js +459 -0
  44. package/dist/lib/task/verification.js +0 -1
  45. package/dist/lib/task/workflow-warning-intents.js +7 -1
  46. package/dist/lib/update.js +2 -2
  47. package/dist/lifecycle-build-manifest.json +3 -3
  48. package/lib/agent-clients/adapters/codex.ts +1 -1
  49. package/lib/agent-clients/config.ts +9 -3
  50. package/lib/agent-clients/custom-tool-invocations.ts +129 -0
  51. package/lib/agent-clients/next-steps.ts +3 -3
  52. package/lib/agent-clients/reconcile.ts +3 -3
  53. package/lib/agent-clients/schema.ts +18 -10
  54. package/lib/defaults.json +6 -3
  55. package/lib/init.ts +2 -2
  56. package/lib/internal/agent-client.ts +3 -3
  57. package/lib/internal/cli-route-inventory.ts +2 -0
  58. package/lib/internal/platform-pr-review.ts +14 -3
  59. package/lib/internal/task-operation-registry.ts +3 -1
  60. package/lib/internal/task-platform-recovery.ts +53 -0
  61. package/lib/merge.ts +26 -6
  62. package/lib/platform/completion-backfill.ts +2 -1
  63. package/lib/platform/in-label-sync.ts +12 -0
  64. package/lib/platform/issue-comments.ts +59 -3
  65. package/lib/platform/issues.ts +103 -5
  66. package/lib/platform/operation-coordinator.ts +96 -0
  67. package/lib/platform/pr-review.ts +113 -1
  68. package/lib/platform/pr-summary.ts +22 -1
  69. package/lib/platform/pull-requests.ts +105 -5
  70. package/lib/sandbox/commands/create.ts +7 -8
  71. package/lib/sandbox/commands/ls.ts +4 -3
  72. package/lib/sandbox/commands/rm.ts +1 -1
  73. package/lib/sandbox/config.ts +23 -5
  74. package/lib/sandbox/git-metadata.ts +66 -0
  75. package/lib/sandbox/index.ts +1 -1
  76. package/lib/sandbox/mounts.ts +14 -0
  77. package/lib/sandbox/recovery.ts +13 -25
  78. package/lib/sandbox/removal.ts +57 -19
  79. package/lib/sandbox/tool-types.ts +2 -1
  80. package/lib/sandbox/tools.ts +38 -7
  81. package/lib/task/activity-intent.ts +4 -3
  82. package/lib/task/create-service.ts +5 -3
  83. package/lib/task/create.ts +17 -5
  84. package/lib/task/finalization-artifacts.ts +4 -1
  85. package/lib/task/finalization.ts +242 -106
  86. package/lib/task/lifecycle.ts +35 -3
  87. package/lib/task/platform-operation-journal.ts +230 -0
  88. package/lib/task/platform-operation-recovery.ts +435 -0
  89. package/lib/task/verification.ts +1 -2
  90. package/lib/task/workflow-warning-intents.ts +9 -1
  91. package/lib/update.ts +2 -3
  92. package/package.json +1 -1
  93. package/templates/.agents/README.en.md +20 -66
  94. package/templates/.agents/README.zh-CN.md +19 -65
  95. package/templates/.agents/rules/README.en.md +1 -1
  96. package/templates/.agents/rules/README.zh-CN.md +1 -1
  97. package/templates/.agents/rules/next-step-output.en.md +1 -1
  98. package/templates/.agents/rules/next-step-output.zh-CN.md +1 -1
  99. package/templates/.agents/rules/task-management.en.md +12 -0
  100. package/templates/.agents/rules/task-management.zh-CN.md +12 -0
  101. package/templates/.agents/scripts/lib/agent-infra-package.js +0 -7
  102. package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -2
  103. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +4 -2
  104. package/templates/.agents/skills/code-task/SKILL.en.md +2 -2
  105. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  106. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  107. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  108. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -0
  109. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -0
  110. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  111. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  112. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  113. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  114. package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
  115. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
  116. package/templates/.agents/skills/review-code/reference/output-templates.en.md +15 -6
  117. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +15 -6
  118. package/templates/.agents/skills/review-pr/SKILL.en.md +11 -11
  119. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +11 -11
  120. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +3 -3
  121. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +3 -3
  122. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -0
  123. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -0
  124. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +1 -1
  125. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -1
  126. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +21 -274
  127. package/dist/lib/agent-clients/custom-tuis.js +0 -99
  128. package/lib/agent-clients/custom-tuis.ts +0 -149
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { createHash } from 'node:crypto';
3
4
  import { enumerateArtifacts } from '../task/artifacts.js';
4
5
  import { parseTypedTaskFrontmatter } from '../task/frontmatter.js';
5
6
  import { resolveTaskRef } from '../task/resolve-ref.js';
@@ -9,6 +10,7 @@ import { resolvePlatformProviderContext } from './context.js';
9
10
  import { normalizeCommentContent } from './issue-comments.js';
10
11
  import { CONTROL_MARKER_PATTERN, sanitizeMarkdownDocument, splitDocumentPlaceholder } from './comment-safety.js';
11
12
  import { platformResult } from './types.js';
13
+ import { coordinatePlatformWrite } from './operation-coordinator.js';
12
14
  import { providerError, providerOperationContext, providerStatus, providerResourceToken, resourceIdentityNumber, unsupportedProviderOperation } from './provider-bridge.js';
13
15
  import { resourceIdentityEquals } from './resource-identity.js';
14
16
  import { buildPrChangeReport, CANONICAL_REPORT_PLACEHOLDER, readPrChangeReport, replaceCanonicalReportPlaceholder, runMechanicalChangeReport, taskIntentDigest, validateMechanicalReport, validatePrecheckCandidate, writePrChangeReportAtomic } from './pr-change-report.js';
@@ -609,74 +611,94 @@ async function syncPullRequestSummary(taskRef, options) {
609
611
  operations: [{ name: `summary:${reconciliation.action}`, status: 'planned', reasonCode: null }],
610
612
  result: null, warnings: [], ...info
611
613
  };
612
- const existingComment = reconciliation.commentId === null
613
- ? undefined
614
- : providerResourceToken(loaded.value.provider, 'comment', String(reconciliation.commentId));
615
- const written = loaded.value.provider.comments?.write
616
- ? await loaded.value.provider.comments.write({
617
- context: providerOperationContext(loaded.value),
618
- parent: prIdentity,
619
- body: desired,
620
- ...(existingComment ? { existingComment } : {}),
621
- mutation: { idempotencyKey: `pr-summary:${resolved.taskId}` }
622
- })
623
- : unsupportedProviderOperation(loaded.value.provider, 'comments.write');
624
- if (!written.ok)
625
- return fail(providerStatus(written.error) === 'blocked' ? 'blocked' : 'failed', context, providerError(written.error, 'PLATFORM_PROVIDER_OPERATION_FAILED'), prNumber);
626
- const after = await inspectBoundPullRequest(context, resolved.repoRoot, prIdentity, loaded.value);
627
- const id = /^\d+$/.test(written.value.remoteId) ? Number(written.value.remoteId) : written.value.remoteId;
628
- const compensateWrittenSummary = async () => reconciliation.action === 'create'
629
- ? written.value.remoteId && loaded.value.provider.comments?.delete
630
- ? await loaded.value.provider.comments.delete({
631
- context: providerOperationContext(loaded.value),
632
- parent: prIdentity,
633
- comment: providerResourceToken(loaded.value.provider, 'comment', written.value.remoteId),
634
- mutation: { idempotencyKey: `pr-summary-delete:${resolved.taskId}:${written.value.remoteId}` }
635
- })
636
- : { ok: false, error: { code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED', message: 'Created summary comment has no valid id', retryable: true } }
637
- : reconciliation.commentId && previousBody !== null && loaded.value.provider.comments?.write
614
+ const publish = async () => {
615
+ const existingComment = reconciliation.commentId === null
616
+ ? undefined
617
+ : providerResourceToken(loaded.value.provider, 'comment', String(reconciliation.commentId));
618
+ const written = loaded.value.provider.comments?.write
638
619
  ? await loaded.value.provider.comments.write({
639
620
  context: providerOperationContext(loaded.value),
640
621
  parent: prIdentity,
641
- body: previousBody,
642
- existingComment: providerResourceToken(loaded.value.provider, 'comment', String(reconciliation.commentId)),
643
- mutation: { idempotencyKey: `pr-summary-restore:${resolved.taskId}` }
622
+ body: desired,
623
+ ...(existingComment ? { existingComment } : {}),
624
+ mutation: { idempotencyKey: `pr-summary:${resolved.taskId}` }
644
625
  })
645
- : { ok: false, error: { code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED', message: 'Previous summary body is unavailable for restoration', retryable: true } };
646
- if (!after.ok) {
647
- const compensated = await compensateWrittenSummary();
648
- if (!compensated.ok)
626
+ : unsupportedProviderOperation(loaded.value.provider, 'comments.write');
627
+ if (!written.ok)
628
+ return fail(providerStatus(written.error) === 'blocked' ? 'blocked' : 'failed', context, providerError(written.error, 'PLATFORM_PROVIDER_OPERATION_FAILED'), prNumber);
629
+ const after = await inspectBoundPullRequest(context, resolved.repoRoot, prIdentity, loaded.value);
630
+ const id = /^\d+$/.test(written.value.remoteId) ? Number(written.value.remoteId) : written.value.remoteId;
631
+ const compensateWrittenSummary = async () => reconciliation.action === 'create'
632
+ ? written.value.remoteId && loaded.value.provider.comments?.delete
633
+ ? await loaded.value.provider.comments.delete({
634
+ context: providerOperationContext(loaded.value),
635
+ parent: prIdentity,
636
+ comment: providerResourceToken(loaded.value.provider, 'comment', written.value.remoteId),
637
+ mutation: { idempotencyKey: `pr-summary-delete:${resolved.taskId}:${written.value.remoteId}` }
638
+ })
639
+ : { ok: false, error: { code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED', message: 'Created summary comment has no valid id', retryable: true } }
640
+ : reconciliation.commentId && previousBody !== null && loaded.value.provider.comments?.write
641
+ ? await loaded.value.provider.comments.write({
642
+ context: providerOperationContext(loaded.value),
643
+ parent: prIdentity,
644
+ body: previousBody,
645
+ existingComment: providerResourceToken(loaded.value.provider, 'comment', String(reconciliation.commentId)),
646
+ mutation: { idempotencyKey: `pr-summary-restore:${resolved.taskId}` }
647
+ })
648
+ : { ok: false, error: { code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED', message: 'Previous summary body is unavailable for restoration', retryable: true } };
649
+ if (!after.ok) {
650
+ const compensated = await compensateWrittenSummary();
651
+ if (!compensated.ok)
652
+ return fail('blocked', context, {
653
+ code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED',
654
+ message: `Summary post-write verification failed and compensation failed: ${compensated.error.message}`,
655
+ retryable: true
656
+ }, prNumber);
649
657
  return fail('blocked', context, {
650
- code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED',
651
- message: `Summary post-write verification failed and compensation failed: ${compensated.error.message}`,
658
+ code: 'PR_SUMMARY_POSTWRITE_VERIFY_FAILED',
659
+ message: `Summary was written but pull-request verification failed: ${after.error?.message || 'unknown verification error'}; the previous summary was restored or the new comment was deleted`,
652
660
  retryable: true
653
661
  }, prNumber);
654
- return fail('blocked', context, {
655
- code: 'PR_SUMMARY_POSTWRITE_VERIFY_FAILED',
656
- message: `Summary was written but pull-request verification failed: ${after.error?.message || 'unknown verification error'}; the previous summary was restored or the new comment was deleted`,
657
- retryable: true
658
- }, prNumber);
659
- }
660
- if (!sameIdentity(initial.value, after.value)) {
661
- const compensated = await compensateWrittenSummary();
662
- if (!compensated.ok)
662
+ }
663
+ if (!sameIdentity(initial.value, after.value)) {
664
+ const compensated = await compensateWrittenSummary();
665
+ if (!compensated.ok)
666
+ return fail('blocked', context, {
667
+ code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED',
668
+ message: `Pull-request head changed and summary compensation failed: ${compensated.error.message}`,
669
+ retryable: true
670
+ }, prNumber);
663
671
  return fail('blocked', context, {
664
- code: 'PR_SUMMARY_RACE_COMPENSATION_FAILED',
665
- message: `Pull-request head changed and summary compensation failed: ${compensated.error.message}`,
672
+ code: 'PR_SUMMARY_HEAD_RACE',
673
+ message: 'Pull-request head changed while publishing; the previous summary was restored or the new comment was deleted; rerun report and summary sync',
666
674
  retryable: true
667
675
  }, prNumber);
668
- return fail('blocked', context, {
669
- code: 'PR_SUMMARY_HEAD_RACE',
670
- message: 'Pull-request head changed while publishing; the previous summary was restored or the new comment was deleted; rerun report and summary sync',
671
- retryable: true
672
- }, prNumber);
673
- }
674
- return {
675
- ...basePlatformResult('applied', context, resolved.taskId, prNumber),
676
- comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: Number.isInteger(id) ? [id] : [], parts: 1 },
677
- operations: [{ name: `summary:${reconciliation.action}`, status: 'applied', reasonCode: null }],
678
- result: null, warnings: [], ...info
676
+ }
677
+ return {
678
+ ...basePlatformResult('applied', context, resolved.taskId, prNumber),
679
+ comment: { kind: 'summary', marker: summaryMarker(resolved.taskId), ids: Number.isInteger(id) ? [id] : [], parts: 1 },
680
+ operations: [{ name: `summary:${reconciliation.action}`, status: 'applied', reasonCode: null }],
681
+ result: null, warnings: [], ...info
682
+ };
683
+ };
684
+ const operation = {
685
+ taskRef: resolved.taskId,
686
+ cwd: resolved.repoRoot,
687
+ kind: 'pull-request-summary',
688
+ target: JSON.stringify(prIdentity),
689
+ expectedDigest: createHash('sha256').update(desired).digest('hex'),
690
+ dependency: 'deferred',
691
+ pullRequestSummary: { body: options.body, changeReportFile: options.changeReportFile }
679
692
  };
693
+ if (options.skipQueue)
694
+ return publish();
695
+ return coordinatePlatformWrite({
696
+ operation,
697
+ agent: options.agent,
698
+ execute: publish,
699
+ block: (error) => fail('blocked', context, error, prNumber),
700
+ persistenceFailure: (error) => fail('failed', context, error, prNumber)
701
+ });
680
702
  };
681
703
  return await (options.lockAlreadyHeld
682
704
  ? execute()
@@ -1,6 +1,7 @@
1
1
  import { normalizePullRequest } from './github-data.js';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
+ import { createHash } from 'node:crypto';
4
5
  import { execFileSync } from 'node:child_process';
5
6
  import { parseTypedTaskFrontmatter } from '../task/frontmatter.js';
6
7
  import { resolveTaskRef } from '../task/resolve-ref.js';
@@ -19,6 +20,7 @@ import { buildBoundFact, buildSkippedFact, factFrontmatterMutation, readPrDelive
19
20
  import { providerError, providerOperationContext, providerStatus, providerResourceToken, providerResourceIdentity, resourceIdentity, unsupportedProviderOperation } from './provider-bridge.js';
20
21
  import { isResourceIdentity, resourceIdentityEquals, resourceIdentityNumber, serializeResourceIdentity } from './resource-identity.js';
21
22
  import { taskIssueIdentity, taskIssueIdentityError } from './task-identities.js';
23
+ import { coordinatePlatformWrite } from './operation-coordinator.js';
22
24
  function warningResultForPrimary(primaryResult) {
23
25
  if (primaryResult === 'pr_created')
24
26
  return 'pr_created_with_warnings';
@@ -52,6 +54,25 @@ function externalResult(base, overrides = {}) {
52
54
  function withCreation(output, creation) {
53
55
  return { ...output, creation };
54
56
  }
57
+ async function journalPullRequestOperation(taskRef, options, intent, target, identityInput, execute) {
58
+ if (options.dryRun)
59
+ return execute();
60
+ const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
61
+ if (!resolved.ok)
62
+ return execute();
63
+ const expectedDigest = createHash('sha256').update(JSON.stringify({ taskId: resolved.taskId, intent, identityInput })).digest('hex');
64
+ const operation = { kind: 'pull-request', target: intent.action === 'sync' ? `bound-pr:${resolved.taskId}` : target, expectedDigest };
65
+ const dependency = intent.action === 'sync' ? 'deferred' : 'required';
66
+ if (options.skipQueue)
67
+ return execute();
68
+ return coordinatePlatformWrite({
69
+ operation: { taskRef: resolved.taskId, cwd: resolved.repoRoot, ...operation, pullRequest: intent, dependency },
70
+ agent: options.agent,
71
+ execute,
72
+ block: (error) => result('blocked', resolved.taskId, null, null, { error }),
73
+ persistenceFailure: (error) => result('failed', resolved.taskId, null, null, { error })
74
+ });
75
+ }
55
76
  const PRECONDITION_NOT_CREATED = {
56
77
  kind: 'not-created', reason: 'precondition-failed', createdByCurrentOperation: false
57
78
  };
@@ -681,7 +702,7 @@ function bindIdentity(base, pullRequest, agent, source, dryRun = false, expected
681
702
  ]
682
703
  }, { repoRoot: base.resolved.repoRoot, metadataProvider: () => metadata });
683
704
  }
684
- async function bindPlatformPullRequest(taskRef, options) {
705
+ async function bindPlatformPullRequestImpl(taskRef, options) {
685
706
  const base = await resolvedContext(taskRef, options);
686
707
  if (!base.ok)
687
708
  return base.output;
@@ -723,6 +744,10 @@ async function bindPlatformPullRequest(taskRef, options) {
723
744
  operations: [{ name: 'task:bind-pr', status: options.dryRun ? 'planned' : written.status === 'no-op' ? 'no-op' : 'applied', reasonCode: null }], error: null
724
745
  });
725
746
  }
747
+ async function bindPlatformPullRequest(taskRef, options) {
748
+ const intent = { action: 'bind', prToken: String(options.pr) };
749
+ return journalPullRequestOperation(taskRef, options, intent, `pr:${options.pr}`, { pr: String(options.pr) }, () => bindPlatformPullRequestImpl(taskRef, options));
750
+ }
726
751
  function externalEvidenceNote(issueIdentity, source, pullRequest) {
727
752
  return [
728
753
  `authorization=${source}`,
@@ -915,7 +940,7 @@ async function resolveExternalPullRequest(taskRef, options) {
915
940
  error: null
916
941
  }), { mode: 'external', authorization: selected.source, candidates: selected.candidates, eligible: selected.eligible, selected: selectedPullRequest });
917
942
  }
918
- async function createPlatformPullRequest(taskRef, options) {
943
+ async function createPlatformPullRequestImpl(taskRef, options) {
919
944
  const base = await resolvedContext(taskRef, options);
920
945
  if (!base.ok)
921
946
  return withCreation(base.output, PRECONDITION_NOT_CREATED);
@@ -946,6 +971,67 @@ async function createPlatformPullRequest(taskRef, options) {
946
971
  }), PRECONDITION_NOT_CREATED);
947
972
  }
948
973
  }
974
+ async function recoverCreatedPullRequest(taskRef, options) {
975
+ const base = await resolvedContext(taskRef, options);
976
+ if (!base.ok)
977
+ return base.output;
978
+ if (!base.issueIdentity)
979
+ return result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
980
+ error: { code: 'PLATFORM_OPERATION_ISSUE_REQUIRED', message: 'Pull-request recovery requires a bound Issue identity', retryable: false }
981
+ });
982
+ const head = base.provider.changeRequests?.verifyHead
983
+ ? await base.provider.changeRequests.verifyHead({ context: providerOperationContext(base.loadedContext), head: options.head })
984
+ : unsupportedProviderOperation(base.provider, 'changeRequests.verifyHead');
985
+ if (!head.ok)
986
+ return providerPullRequestError(base, head.error, base.prNumber);
987
+ if (base.prIdentity) {
988
+ const inspected = await inspectExternalPullRequest(base, base.prIdentity);
989
+ if (!inspected.pullRequest || inspected.status === 'failed' || inspected.status === 'blocked'
990
+ || inspected.pullRequest.base.repository !== base.context.platform.repository
991
+ || inspected.pullRequest.base.ref !== options.base || inspected.pullRequest.head.ref !== options.head
992
+ || inspected.pullRequest.head.sha !== head.value.sha) {
993
+ return result('blocked', base.resolved.taskId, base.issueNumber, base.prNumber, {
994
+ error: inspected.error ?? { code: 'PLATFORM_OPERATION_PR_NOT_CONFIRMED', message: 'Bound pull request does not match the pending create intent', retryable: true }
995
+ });
996
+ }
997
+ return inspected;
998
+ }
999
+ const candidates = base.provider.changeRequests?.listClosing
1000
+ ? await base.provider.changeRequests.listClosing({ context: providerOperationContext(base.loadedContext), issue: base.issueIdentity })
1001
+ : unsupportedProviderOperation(base.provider, 'changeRequests.listClosing');
1002
+ if (!candidates.ok)
1003
+ return providerPullRequestError(base, candidates.error, null);
1004
+ const matches = candidates.value.map((candidate) => normalizeProviderPullRequest(candidate, base.context.platform.repository, candidate.number || 0))
1005
+ .filter((candidate) => candidate.base.repository === base.context.platform.repository
1006
+ && candidate.base.ref === options.base
1007
+ && candidate.head.repository === base.context.platform.repository
1008
+ && candidate.head.ref === options.head
1009
+ && candidate.head.sha === head.value.sha);
1010
+ if (matches.length !== 1 || !matches[0]?.number)
1011
+ return result('blocked', base.resolved.taskId, base.issueNumber, null, {
1012
+ error: { code: matches.length > 1 ? 'PLATFORM_OPERATION_PR_AMBIGUOUS' : 'PLATFORM_OPERATION_PR_NOT_CONFIRMED',
1013
+ message: 'Pending pull-request creation does not resolve to one exact repository/base/head identity', retryable: true }
1014
+ });
1015
+ const bound = await bindPlatformPullRequest(base.resolved.taskId, { agent: options.agent, cwd: base.resolved.repoRoot, pr: matches[0].number });
1016
+ if (!bound.pullRequest || (bound.status !== 'applied' && bound.status !== 'no-op') || bound.error)
1017
+ return bound;
1018
+ if (bound.pullRequest.base.repository !== base.context.platform.repository || bound.pullRequest.base.ref !== options.base
1019
+ || bound.pullRequest.head.repository !== base.context.platform.repository || bound.pullRequest.head.ref !== options.head
1020
+ || bound.pullRequest.head.sha !== head.value.sha)
1021
+ return result('blocked', base.resolved.taskId, base.issueNumber, bound.task.prNumber, {
1022
+ error: { code: 'PLATFORM_OPERATION_PR_NOT_CONFIRMED', message: 'Bound pull request changed while recovering the create intent', retryable: true }
1023
+ });
1024
+ return bound;
1025
+ }
1026
+ async function createPlatformPullRequest(taskRef, options) {
1027
+ const intent = { action: 'create', baseRef: options.base, headRef: options.head };
1028
+ const identityInput = {
1029
+ title: createHash('sha256').update(options.title).digest('hex'),
1030
+ body: createHash('sha256').update(options.body).digest('hex'),
1031
+ draft: options.draft === true
1032
+ };
1033
+ return journalPullRequestOperation(taskRef, options, intent, `head:${options.head}:base:${options.base}`, identityInput, () => createPlatformPullRequestImpl(taskRef, options));
1034
+ }
949
1035
  async function createExternalPullRequest(taskRef, options, base) {
950
1036
  if (!base.fact)
951
1037
  return withCreation(result('failed', base.resolved.taskId, base.issueNumber, null, {
@@ -1104,7 +1190,7 @@ async function createExternalPullRequest(taskRef, options, base) {
1104
1190
  error: null
1105
1191
  }), { kind: 'created', createdByCurrentOperation: true });
1106
1192
  }
1107
- async function syncPlatformPullRequest(taskRef, options) {
1193
+ async function syncPlatformPullRequestImpl(taskRef, options) {
1108
1194
  const warningResult = warningResultForPrimary(options.primaryResult);
1109
1195
  const softenFailure = (output) => {
1110
1196
  const authorityError = output.error?.code.startsWith('IN_LABEL_SYNC') || output.error?.code.startsWith('PR_IDENTITY');
@@ -1288,6 +1374,12 @@ async function syncPlatformPullRequest(taskRef, options) {
1288
1374
  }));
1289
1375
  }
1290
1376
  }
1377
+ async function syncPlatformPullRequest(taskRef, options) {
1378
+ const intent = { action: 'sync', metadata: options.metadata === true, closingIssue: options.closingIssue === true };
1379
+ return journalPullRequestOperation(taskRef, options, intent, `bound-pr:${taskRef}`, {
1380
+ metadata: options.metadata === true, closingIssue: options.closingIssue === true
1381
+ }, () => syncPlatformPullRequestImpl(taskRef, options));
1382
+ }
1291
1383
  function readProjectPrFlow(repoRoot) {
1292
1384
  try {
1293
1385
  const config = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8'));
@@ -1386,6 +1478,6 @@ async function skipPlatformPullRequestFact(taskRef, options) {
1386
1478
  });
1387
1479
  }
1388
1480
  }
1389
- export { bindPlatformPullRequest, createPlatformPullRequest, inspectPlatformPullRequest, inspectPlatformPullRequestByNumber, resolveExternalPullRequest, skipPlatformPullRequestFact, selectExternalPullRequest, selectPullRequest, syncPlatformPullRequest, syncPlatformPullRequestInLabels };
1481
+ export { bindPlatformPullRequest, createPlatformPullRequest, inspectPlatformPullRequest, inspectPlatformPullRequestByNumber, recoverCreatedPullRequest, resolveExternalPullRequest, skipPlatformPullRequestFact, selectExternalPullRequest, selectPullRequest, syncPlatformPullRequest, syncPlatformPullRequestInLabels };
1390
1482
  export { warningResultForPrimary };
1391
1483
  //# sourceMappingURL=pull-requests.js.map
@@ -22,6 +22,7 @@ import { atomicWriteJson } from '../control/state.js';
22
22
  import { inspectSandboxControlContainer } from '../control/container-identity.js';
23
23
  import { hostJoin, toEnginePath, volumeArg } from '../engines/wsl2-paths.js';
24
24
  import { sandboxCoreBindMounts } from '../mounts.js';
25
+ import { materializeSandboxGitMetadata } from '../git-metadata.js';
25
26
  import { assertSandboxTaskSource, finalizeSandboxControlManifest, materializeSandboxControl, materializeSandboxWorkspaceView, prepareSandboxWorkspaceMountTargets, sandboxControlPaths, } from '../workspace-view.js';
26
27
  import { clipboardHostDir, CONTAINER_CLIPBOARD_MOUNT } from '../clipboard/paths.js';
27
28
  import { validateSelinuxDisableEnv } from '../engines/selinux.js';
@@ -630,7 +631,8 @@ function runSandboxAuthoritySafeCommand(authority, args) {
630
631
  // tmpfs. containerMount is an in-container path, so it is NOT engine-converted.
631
632
  export function buildTmpfsRunArgs(containerMount, tmpfs) {
632
633
  const size = tmpfs.size ?? '512m';
633
- return ['--tmpfs', `${containerMount}:rw,size=${size}`];
634
+ const options = ['rw', ...(tmpfs.exec ? ['exec'] : []), `size=${size}`];
635
+ return ['--tmpfs', `${containerMount}:${options.join(',')}`];
634
636
  }
635
637
  export function buildImage(config, tools, dockerfilePath, imageSignature, { engine, runFn = runEngine, runSafeFn = runSafeEngine, runVerboseFn = runVerboseEngine, env = process.env, refresh = false, lastRefresh, buildProxyArgs = [], buildEnv } = {}) {
636
638
  const selectedEngine = engine ?? detectEngine({ engine: config.engine });
@@ -1050,6 +1052,7 @@ export async function create(args, { runProjectInitCommand = true } = {}) {
1050
1052
  engine,
1051
1053
  replacementLease,
1052
1054
  });
1055
+ const gitMetadata = materializeSandboxGitMetadata(worktree, control.channelDir);
1053
1056
  hostShellConfig = prepareHostShellConfig({
1054
1057
  home: effectiveConfig.home,
1055
1058
  project: effectiveConfig.project,
@@ -1057,6 +1060,8 @@ export async function create(args, { runProjectInitCommand = true } = {}) {
1057
1060
  repoRoot: effectiveConfig.repoRoot
1058
1061
  });
1059
1062
  const coreVolumes = sandboxCoreBindMounts(effectiveConfig, branch, {
1063
+ gitCommonDir: gitMetadata.commonDir,
1064
+ worktreeGitFile: gitMetadata.worktreeGitFile,
1060
1065
  worktree,
1061
1066
  shellConfigHostDir: hostShellConfig.hostDir,
1062
1067
  workspaceViewRoot: workspaceView.root,
@@ -1153,8 +1158,6 @@ export async function create(args, { runProjectInitCommand = true } = {}) {
1153
1158
  `${sandboxRuntimeCapabilityLabel(effectiveConfig)}=${capabilityPlan.runtimeSignature}`,
1154
1159
  ...coreVolumes,
1155
1160
  ...buildClipboardVolumeArgs(engine, effectiveConfig.home),
1156
- '-v',
1157
- volumeArg(engine, path.join(effectiveConfig.repoRoot, '.git'), `${toEnginePath(engine, effectiveConfig.repoRoot)}/.git`),
1158
1161
  ...dotfilesMount,
1159
1162
  ...toolVolumes,
1160
1163
  ...tmpfsArgs,
@@ -15,8 +15,9 @@ display-only row number; the 'SHORT' column shows the active task short
15
15
  id bound to each container's branch (via
16
16
  .agents/workspace/active/.short-ids.json), or '-' if no active task is
17
17
  bound. Pass the SHORT value to "ai sandbox exec" (e.g. 'ai sandbox exec 11').
18
- A '-' means no active task is bound to that branch, so the sandbox is free
19
- to remove with "ai sandbox rm <branch>".
18
+ A SHORT value of '-' means this container has no active task short id and may
19
+ be eligible for "ai sandbox rm --unbound" cleanup. Use a full TASK-id to
20
+ remove a specific task-bound sandbox.
20
21
 
21
22
  Use "ai sandbox show <ref>" for a single sandbox's worktree and per-tool
22
23
  state paths.`;
@@ -63,7 +64,7 @@ export function ls(args = []) {
63
64
  process.stdout.write(` ${row.name}: health=${controlStatus?.state ?? 'unavailable'} task-view=${controlStatus?.taskView.state ?? 'unavailable'}\n`);
64
65
  }
65
66
  if (tableRows.some((r) => r.shortId === '-')) {
66
- process.stdout.write(` SHORT '-' = no active task bound; that sandbox is free to remove with 'ai sandbox rm <branch>'.\n`);
67
+ process.stdout.write(` SHORT '-' = no active task short id is displayed; --unbound may clean this sandbox if its branch is not tied to an active task.\n`);
67
68
  }
68
69
  }
69
70
  }
@@ -8,7 +8,7 @@ import { fetchSandboxRows } from './list-running.js';
8
8
  import { rmOne, rmPurge, rmUnbound } from '../removal.js';
9
9
  const USAGE = `Usage:
10
10
  ai sandbox rm <branch | TASK-id | short id> Remove one sandbox; use a full TASK-id for a task-bound sandbox and a branch for branch-only sandboxes
11
- ai sandbox rm --unbound [--dry-run] [--yes] Remove discovered task-bound and branch-only sandboxes
11
+ ai sandbox rm --unbound [--dry-run] [--yes] Remove sandboxes not bound to an active task short id
12
12
  ai sandbox rm --purge Tear down ALL sandboxes for the project (containers, worktrees, image, VM)`;
13
13
  function resolveMissingTaskSandboxBranch(taskId, config) {
14
14
  const engine = detectEngine(config);
@@ -50,10 +50,22 @@ function cloneDefaults() {
50
50
  };
51
51
  }
52
52
  function resolveSandboxToolIds(sandbox, defaults) {
53
- if (Array.isArray(sandbox.tools)) {
54
- return sandbox.tools.filter((tool) => typeof tool === 'string');
53
+ if (sandbox.tools === undefined) {
54
+ return [...defaults];
55
55
  }
56
- return [...defaults];
56
+ if (typeof sandbox.tools !== 'object' || sandbox.tools === null || Array.isArray(sandbox.tools)) {
57
+ throw new Error('sandbox.tools must be an object when configured');
58
+ }
59
+ const tools = sandbox.tools;
60
+ if (!Array.isArray(tools.ids)) {
61
+ throw new Error('sandbox.tools.ids must be an array');
62
+ }
63
+ return tools.ids.map((id, index) => {
64
+ if (typeof id !== 'string') {
65
+ throw new Error(`sandbox.tools.ids[${index}] must be a string`);
66
+ }
67
+ return id;
68
+ });
57
69
  }
58
70
  export function loadConfig({ platformFn = platform, writeStderr = (chunk) => process.stderr.write(chunk) } = {}) {
59
71
  const repoRoot = detectRepoRoot();
@@ -100,7 +112,13 @@ export function loadConfig({ platformFn = platform, writeStderr = (chunk) => pro
100
112
  ' Update "sandbox.runtimes" in .agents/.airc.json (e.g. "node22"), or relax "engines.node".\n'));
101
113
  }
102
114
  }
103
- const customTools = parseCustomTools(sandbox.customTools, { home });
115
+ const toolsConfig = sandbox.tools && typeof sandbox.tools === 'object' && !Array.isArray(sandbox.tools)
116
+ ? sandbox.tools
117
+ : undefined;
118
+ if (sandbox.customTools !== undefined) {
119
+ throw new Error('sandbox.customTools is no longer supported; use sandbox.tools.definitions');
120
+ }
121
+ const customTools = parseCustomTools(toolsConfig?.definitions, { home });
104
122
  const tools = resolveSandboxToolIds(sandbox, defaults.tools);
105
123
  return {
106
124
  repoRoot,
@@ -0,0 +1,50 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ export const SANDBOX_GIT_COMMON_DIR = '/run/agent-infra/git';
5
+ function gitText(worktreeRoot, args) {
6
+ const env = { ...process.env };
7
+ for (const key of [
8
+ 'GIT_DIR',
9
+ 'GIT_COMMON_DIR',
10
+ 'GIT_WORK_TREE',
11
+ 'GIT_INDEX_FILE',
12
+ 'GIT_PREFIX',
13
+ 'GIT_NAMESPACE',
14
+ 'GIT_OBJECT_DIRECTORY',
15
+ 'GIT_CEILING_DIRECTORIES',
16
+ 'GIT_DISCOVERY_ACROSS_FILESYSTEM'
17
+ ]) {
18
+ delete env[key];
19
+ }
20
+ return execFileSync('git', ['-C', worktreeRoot, ...args], {
21
+ encoding: 'utf8',
22
+ stdio: ['ignore', 'pipe', 'pipe'],
23
+ env
24
+ }).trim();
25
+ }
26
+ export function resolveSandboxGitMetadata(worktreeRoot, controlDir) {
27
+ const commonDir = fs.realpathSync.native(gitText(worktreeRoot, [
28
+ 'rev-parse', '--path-format=absolute', '--git-common-dir'
29
+ ]));
30
+ const gitDir = fs.realpathSync.native(gitText(worktreeRoot, ['rev-parse', '--absolute-git-dir']));
31
+ const relativeGitDir = path.relative(commonDir, gitDir);
32
+ if (path.isAbsolute(relativeGitDir) || relativeGitDir.startsWith(`..${path.sep}`) || relativeGitDir === '..') {
33
+ throw new Error('SANDBOX_GIT_METADATA_INVALID: worktree git directory is outside its common directory');
34
+ }
35
+ const containerGitDir = relativeGitDir === '.'
36
+ ? SANDBOX_GIT_COMMON_DIR
37
+ : path.posix.join(SANDBOX_GIT_COMMON_DIR, ...relativeGitDir.split(path.sep));
38
+ return {
39
+ commonDir,
40
+ worktreeGitFile: path.join(controlDir, 'workspace.git'),
41
+ worktreeGitFileContent: `gitdir: ${containerGitDir}\n`
42
+ };
43
+ }
44
+ export function materializeSandboxGitMetadata(worktreeRoot, controlDir) {
45
+ const metadata = resolveSandboxGitMetadata(worktreeRoot, controlDir);
46
+ fs.mkdirSync(controlDir, { recursive: true });
47
+ fs.writeFileSync(metadata.worktreeGitFile, metadata.worktreeGitFileContent, 'utf8');
48
+ return metadata;
49
+ }
50
+ //# sourceMappingURL=git-metadata.js.map
@@ -19,7 +19,7 @@ Commands:
19
19
  rm <branch | TASK-id> | --unbound | --purge
20
20
  Remove one sandbox; use a full TASK-id for task-bound
21
21
  cleanup and a branch for branch-only cleanup. --unbound
22
- removes discovered task-bound and branch-only sandboxes.
22
+ removes sandboxes not bound to an active task short id.
23
23
  start [--recreate] <branch | TASK-id | N>
24
24
  Verify or recover an existing sandbox container;
25
25
  optionally replace only the container on failure
@@ -1,6 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { shareBranchDir, shareCommonDir, shellConfigDirCandidates, worktreeDirCandidates } from './constants.js';
3
3
  import { sandboxWorkspaceViewStatePaths } from './workspace-view.js';
4
+ import { SANDBOX_GIT_COMMON_DIR } from './git-metadata.js';
4
5
  export function sandboxCoreBindMounts(config, branch, overrides) {
5
6
  const taskSources = overrides.taskSources ?? [];
6
7
  const taskBound = Boolean(taskSources.length > 0 && overrides.taskId);
@@ -15,7 +16,17 @@ export function sandboxCoreBindMounts(config, branch, overrides) {
15
16
  containerPath: path.posix.join('/workspace/.agents/workspace', state),
16
17
  readOnly: true
17
18
  });
19
+ if (Boolean(overrides.gitCommonDir) !== Boolean(overrides.worktreeGitFile)) {
20
+ throw new Error('SANDBOX_GIT_METADATA_INCOMPLETE');
21
+ }
22
+ const gitCommonMount = overrides.gitCommonDir
23
+ ? { hostPaths: [overrides.gitCommonDir], containerPath: SANDBOX_GIT_COMMON_DIR, readOnly: false }
24
+ : null;
25
+ const worktreeGitFileMount = overrides.worktreeGitFile
26
+ ? { hostPaths: [overrides.worktreeGitFile], containerPath: '/workspace/.git', readOnly: true }
27
+ : null;
18
28
  const mounts = [
29
+ ...(gitCommonMount ? [gitCommonMount] : []),
19
30
  {
20
31
  hostPaths: overrides.worktree
21
32
  ? [overrides.worktree]
@@ -23,6 +34,7 @@ export function sandboxCoreBindMounts(config, branch, overrides) {
23
34
  containerPath: '/workspace',
24
35
  readOnly: false
25
36
  },
37
+ ...(worktreeGitFileMount ? [worktreeGitFileMount] : []),
26
38
  ...workspaceMounts,
27
39
  ];
28
40
  if (taskBound) {
@@ -4,11 +4,11 @@ import path from 'node:path';
4
4
  import { spawn } from 'node:child_process';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { nodeEntryArgs } from './control/executor.js';
7
- import { containerNameCandidates, sandboxBranchLabel, sandboxLabel, sandboxRuntimeCapabilityLabel, sandboxTaskIdLabel, sandboxWorkspaceModeLabel, worktreeDirCandidates } from './constants.js';
8
- import { isAgentClientId } from '../agent-clients/types.js';
7
+ import { containerNameCandidates, sandboxBranchLabel, sandboxLabel, sandboxTaskIdLabel, sandboxWorkspaceModeLabel, worktreeDirCandidates } from './constants.js';
9
8
  import { createSandboxCapabilityPlan } from './agent-client-reconciler.js';
10
9
  import { toEnginePath } from './engines/wsl2-paths.js';
11
10
  import { sandboxCoreBindMounts } from './mounts.js';
11
+ import { resolveSandboxGitMetadata } from './git-metadata.js';
12
12
  import { assertSandboxTaskSource, sandboxControlPaths, sandboxWorkspaceViewPaths } from './workspace-view.js';
13
13
  import { resolveTaskWorkspace } from './task-resolver.js';
14
14
  import { parseSandboxWorkspaceIdentity, sameSandboxWorkspaceIdentity } from './workspace-identity.js';
@@ -276,12 +276,6 @@ export function classifySandboxRecovery(snapshot) {
276
276
  : `Container identity ${actualIdentity} on branch ${snapshot.actualBranch ?? 'unknown'} does not match requested ${expectedIdentity} on branch ${snapshot.expectedBranch}.`
277
277
  });
278
278
  }
279
- if (snapshot.runtimeCapabilityOk === false) {
280
- findings.push({
281
- repairKind: 'hard-failure',
282
- message: 'Container runtime capability signature does not match the current sandbox plan.'
283
- });
284
- }
285
279
  if (snapshot.workspaceTopology === 'legacy-parent') {
286
280
  findings.push({
287
281
  repairKind: 'hard-failure',
@@ -306,13 +300,6 @@ export function classifySandboxRecovery(snapshot) {
306
300
  path: '/run/agent-infra/runtime'
307
301
  });
308
302
  }
309
- for (const mountPath of snapshot.unexpectedCapabilityMounts ?? []) {
310
- findings.push({
311
- repairKind: 'hard-failure',
312
- message: `Disabled Agent Client capability remains mounted at ${mountPath}.`,
313
- path: mountPath
314
- });
315
- }
316
303
  for (const mount of snapshot.mounts) {
317
304
  if (snapshot.workspaceTopology === 'legacy-parent'
318
305
  && mount.path.startsWith('/workspace/.agents/workspace'))
@@ -530,7 +517,17 @@ function expectedMounts(params) {
530
517
  ? recoveryTaskSources(config.repoRoot, params.workspace.taskId)
531
518
  : null;
532
519
  const taskId = params.workspace.mode === 'task-bound' ? params.workspace.taskId : null;
520
+ const worktrees = worktreeDirCandidates(config, branch).filter((candidate) => fs.existsSync(path.join(candidate, '.git')));
521
+ const gitMetadata = worktrees.length === 1
522
+ ? resolveSandboxGitMetadata(worktrees[0], control.channelDir)
523
+ : null;
533
524
  const core = sandboxCoreBindMounts(config, branch, {
525
+ ...(gitMetadata === null
526
+ ? {}
527
+ : {
528
+ gitCommonDir: gitMetadata.commonDir,
529
+ worktreeGitFile: gitMetadata.worktreeGitFile
530
+ }),
534
531
  workspaceViewRoot: view.root,
535
532
  controlDir: control.channelDir,
536
533
  controlStatusDir: control.statusDir,
@@ -668,11 +665,6 @@ export function collectSandboxRecoverySnapshot(params) {
668
665
  mode: sandboxWorkspaceModeLabel(params.config),
669
666
  taskId: sandboxTaskIdLabel(params.config)
670
667
  });
671
- const selectedToolIds = new Set(tools.map((tool) => tool.id));
672
- const unexpectedCapabilityMounts = capabilityPlan.cleanupInventory
673
- .filter((tool) => isAgentClientId(tool.id) && !selectedToolIds.has(tool.id))
674
- .map((tool) => tool.containerMount)
675
- .filter((mountPath) => mountsByDestination.has(mountPath));
676
668
  const mountSnapshots = expectedMounts({
677
669
  config: params.config,
678
670
  branch: params.branch,
@@ -725,8 +717,6 @@ export function collectSandboxRecoverySnapshot(params) {
725
717
  identityOk: typeof inspection.Id === 'string' && inspection.Id.length > 0
726
718
  && branchLabel === params.branch
727
719
  && sameSandboxWorkspaceIdentity(containerWorkspace, workspace),
728
- runtimeCapabilityOk: labels[sandboxRuntimeCapabilityLabel(params.config)] === capabilityPlan.runtimeSignature,
729
- unexpectedCapabilityMounts,
730
720
  mounts: mountSnapshots,
731
721
  tmpfs,
732
722
  seeds,