agent-control-plane 0.1.1 → 0.1.3

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 (184) hide show
  1. package/README.md +6 -0
  2. package/npm/bin/agent-control-plane.js +149 -5
  3. package/package.json +2 -3
  4. package/tools/vendor/codex-quota/README.md +8 -16
  5. package/tools/vendor/codex-quota/lib/claude-accounts.js +2 -68
  6. package/tools/vendor/codex-quota/lib/claude-usage.js +76 -667
  7. package/tools/vendor/codex-quota/lib/display.js +6 -14
  8. package/tools/vendor/codex-quota/lib/handlers.js +32 -117
  9. package/tools/vendor/codex-quota/lib/sync.js +9 -14
  10. package/tools/tests/test-agent-control-plane-npm-cli.sh +0 -280
  11. package/tools/tests/test-agent-github-update-labels-falls-back-to-repository-id.sh +0 -56
  12. package/tools/tests/test-agent-project-claude-session-wrapper-clears-stale-sandbox-artifacts.sh +0 -89
  13. package/tools/tests/test-agent-project-claude-session-wrapper-does-not-retry-provider-quota.sh +0 -82
  14. package/tools/tests/test-agent-project-claude-session-wrapper-retries-transient-failures.sh +0 -90
  15. package/tools/tests/test-agent-project-claude-session-wrapper-times-out.sh +0 -73
  16. package/tools/tests/test-agent-project-claude-session-wrapper.sh +0 -103
  17. package/tools/tests/test-agent-project-cleanup-session-orphan-fallback.sh +0 -90
  18. package/tools/tests/test-agent-project-cleanup-session-skip-worktree-cleanup.sh +0 -90
  19. package/tools/tests/test-agent-project-codex-live-thread-persist.sh +0 -76
  20. package/tools/tests/test-agent-project-codex-recovery.sh +0 -731
  21. package/tools/tests/test-agent-project-codex-session-wrapper-clears-stale-sandbox-artifacts.sh +0 -105
  22. package/tools/tests/test-agent-project-codex-session-wrapper.sh +0 -97
  23. package/tools/tests/test-agent-project-open-pr-worktree-config-prefix.sh +0 -81
  24. package/tools/tests/test-agent-project-openclaw-session-wrapper-clears-stale-sandbox-artifacts.sh +0 -109
  25. package/tools/tests/test-agent-project-openclaw-session-wrapper-infers-blocked-result-contract.sh +0 -89
  26. package/tools/tests/test-agent-project-openclaw-session-wrapper-recovers-literal-env-artifacts.sh +0 -113
  27. package/tools/tests/test-agent-project-openclaw-session-wrapper-recovers-version-mismatch.sh +0 -135
  28. package/tools/tests/test-agent-project-openclaw-session-wrapper-resident.sh +0 -179
  29. package/tools/tests/test-agent-project-openclaw-session-wrapper-reuses-existing-agent-after-add-race.sh +0 -119
  30. package/tools/tests/test-agent-project-openclaw-session-wrapper-terminates-rate-limit-hang.sh +0 -91
  31. package/tools/tests/test-agent-project-openclaw-session-wrapper.sh +0 -117
  32. package/tools/tests/test-agent-project-publish-issue-pr-prunes-stale-worktree-entry.sh +0 -148
  33. package/tools/tests/test-agent-project-publish-issue-pr-reads-archived-session.sh +0 -146
  34. package/tools/tests/test-agent-project-publish-issue-pr-recovers-final-head.sh +0 -145
  35. package/tools/tests/test-agent-project-publish-issue-pr-reuses-existing-worktree.sh +0 -147
  36. package/tools/tests/test-agent-project-reconcile-failure-reason.sh +0 -456
  37. package/tools/tests/test-agent-project-reconcile-issue-archived-session-fallback.sh +0 -96
  38. package/tools/tests/test-agent-project-reconcile-issue-before-blocked.sh +0 -90
  39. package/tools/tests/test-agent-project-reconcile-issue-host-verification-recovery-uses-recovered-worktree.sh +0 -212
  40. package/tools/tests/test-agent-project-reconcile-issue-host-verification-recovery.sh +0 -207
  41. package/tools/tests/test-agent-project-reconcile-issue-provider-quota-schedules-provider-cooldown.sh +0 -101
  42. package/tools/tests/test-agent-project-reconcile-issue-session-backfills-lane-metadata-from-worker-key.sh +0 -113
  43. package/tools/tests/test-agent-project-reconcile-issue-session-clears-stale-failed-summary.sh +0 -117
  44. package/tools/tests/test-agent-project-reconcile-issue-session-initializes-shared-agent-home.sh +0 -55
  45. package/tools/tests/test-agent-project-reconcile-issue-session-normalizes-runner-state.sh +0 -125
  46. package/tools/tests/test-agent-project-reconcile-issue-session-records-invalid-contract-summary.sh +0 -118
  47. package/tools/tests/test-agent-project-reconcile-issue-session-skips-duplicate-blocked-comment.sh +0 -144
  48. package/tools/tests/test-agent-project-reconcile-issue-session-standardizes-no-commits-blocker.sh +0 -145
  49. package/tools/tests/test-agent-project-reconcile-issue-session-synthesizes-blocked-comment.sh +0 -139
  50. package/tools/tests/test-agent-project-reconcile-pr-blocked-host-recovery.sh +0 -242
  51. package/tools/tests/test-agent-project-reconcile-pr-guard-blocked-no-commit.sh +0 -142
  52. package/tools/tests/test-agent-project-reconcile-pr-provider-quota-schedules-provider-cooldown.sh +0 -106
  53. package/tools/tests/test-agent-project-reconcile-pr-session-initializes-shared-agent-home.sh +0 -66
  54. package/tools/tests/test-agent-project-reconcile-pr-updated-branch-noop.sh +0 -129
  55. package/tools/tests/test-audit-agent-worktrees-active-launch-skips-git-inspection.sh +0 -69
  56. package/tools/tests/test-audit-agent-worktrees-broken-worktree.sh +0 -43
  57. package/tools/tests/test-audit-agent-worktrees-pending-launch-owner.sh +0 -46
  58. package/tools/tests/test-audit-agent-worktrees-unreconciled-owner.sh +0 -79
  59. package/tools/tests/test-audit-issue-routing-managed-branch-globs.sh +0 -56
  60. package/tools/tests/test-branch-verification-guard-generated-artifacts.sh +0 -72
  61. package/tools/tests/test-branch-verification-guard-targeted-coverage.sh +0 -125
  62. package/tools/tests/test-codex-quota-manager-failure-driven-rotation.sh +0 -178
  63. package/tools/tests/test-codex-quota-wrapper.sh +0 -37
  64. package/tools/tests/test-contribution-docs.sh +0 -18
  65. package/tools/tests/test-control-plane-dashboard-runtime-smoke.sh +0 -343
  66. package/tools/tests/test-create-follow-up-issue.sh +0 -73
  67. package/tools/tests/test-dashboard-launchd-bootstrap.sh +0 -55
  68. package/tools/tests/test-flow-export-execution-env-exports-repo-id.sh +0 -30
  69. package/tools/tests/test-flow-export-github-cli-auth-env-prefers-git-credential.sh +0 -48
  70. package/tools/tests/test-flow-github-api-repo-fallback-preserves-input.sh +0 -85
  71. package/tools/tests/test-flow-github-api-repo-prefers-explicit-repository-id.sh +0 -60
  72. package/tools/tests/test-flow-github-issue-list-falls-back-to-repository-id.sh +0 -64
  73. package/tools/tests/test-flow-github-pr-list-falls-back-to-repository-id.sh +0 -77
  74. package/tools/tests/test-flow-resident-can-reuse-does-not-leak-metadata.sh +0 -52
  75. package/tools/tests/test-flow-resident-reap-stale-controllers.sh +0 -63
  76. package/tools/tests/test-flow-resolve-codex-quota-tools.sh +0 -104
  77. package/tools/tests/test-flow-runtime-doctor-profile-selection.sh +0 -27
  78. package/tools/tests/test-heartbeat-codex-pr-linked-issue-exclusion.sh +0 -79
  79. package/tools/tests/test-heartbeat-hooks-enqueue-resident-issue-for-idle-controller.sh +0 -115
  80. package/tools/tests/test-heartbeat-hooks-enqueue-resident-issue-for-live-lane-controller.sh +0 -117
  81. package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop-claude.sh +0 -96
  82. package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop-codex.sh +0 -96
  83. package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop.sh +0 -96
  84. package/tools/tests/test-heartbeat-loop-auth-wait-does-not-consume-capacity.sh +0 -170
  85. package/tools/tests/test-heartbeat-loop-blocked-recovery-lane.sh +0 -201
  86. package/tools/tests/test-heartbeat-loop-blocked-recovery-vs-pr-reservation.sh +0 -201
  87. package/tools/tests/test-heartbeat-loop-idle-resident-controller-does-not-block-launches.sh +0 -160
  88. package/tools/tests/test-heartbeat-loop-pr-launch-dedup.sh +0 -133
  89. package/tools/tests/test-heartbeat-loop-provider-cooldown-suppresses-launches.sh +0 -157
  90. package/tools/tests/test-heartbeat-loop-reaps-stale-resident-controller.sh +0 -181
  91. package/tools/tests/test-heartbeat-loop-waiting-provider-resident-controller-does-not-block-launches.sh +0 -160
  92. package/tools/tests/test-heartbeat-ready-issues-blocked-recovery.sh +0 -134
  93. package/tools/tests/test-heartbeat-safe-auto-dynamic-concurrency.sh +0 -162
  94. package/tools/tests/test-heartbeat-safe-auto-no-tmux-sessions.sh +0 -136
  95. package/tools/tests/test-heartbeat-safe-auto-openclaw-skips-codex-quota.sh +0 -139
  96. package/tools/tests/test-heartbeat-safe-auto-quota-health-signal.sh +0 -119
  97. package/tools/tests/test-heartbeat-safe-auto-stale-shared-loop-pid-does-not-skip.sh +0 -140
  98. package/tools/tests/test-heartbeat-safe-auto-static-capacity-without-quota-cache.sh +0 -142
  99. package/tools/tests/test-heartbeat-safe-auto-zero-healthy-pools.sh +0 -141
  100. package/tools/tests/test-heartbeat-sync-issue-labels-empty-schedule.sh +0 -65
  101. package/tools/tests/test-heartbeat-sync-open-agent-prs-terminal-clears-running.sh +0 -179
  102. package/tools/tests/test-install-dashboard-launchd.sh +0 -78
  103. package/tools/tests/test-install-project-launchd-adds-tool-paths.sh +0 -87
  104. package/tools/tests/test-install-project-launchd.sh +0 -110
  105. package/tools/tests/test-issue-local-workspace-install-policy.sh +0 -81
  106. package/tools/tests/test-issue-publish-scope-guard-docs-signal.sh +0 -70
  107. package/tools/tests/test-issue-reconcile-hooks-success-clears-blocked.sh +0 -36
  108. package/tools/tests/test-kick-scheduler-requires-explicit-profile.sh +0 -47
  109. package/tools/tests/test-label-follow-up-issues-falls-back-to-repository-id.sh +0 -132
  110. package/tools/tests/test-manual-operator-entrypoints-require-explicit-profile.sh +0 -64
  111. package/tools/tests/test-package-funding-metadata.sh +0 -21
  112. package/tools/tests/test-package-public-metadata.sh +0 -62
  113. package/tools/tests/test-placeholder-worker-adapters.sh +0 -38
  114. package/tools/tests/test-pr-reconcile-hooks-refreshes-recurring-issue-checklist.sh +0 -110
  115. package/tools/tests/test-pr-risk-cohesive-mobile-locale-scope.sh +0 -70
  116. package/tools/tests/test-pr-risk-fix-label-semantics.sh +0 -114
  117. package/tools/tests/test-pr-risk-local-first-no-checks.sh +0 -70
  118. package/tools/tests/test-prepare-worktree-simple-repo-baseline.sh +0 -67
  119. package/tools/tests/test-profile-activate.sh +0 -33
  120. package/tools/tests/test-profile-adopt-allow-missing-repo.sh +0 -68
  121. package/tools/tests/test-profile-adopt-skip-workspace-sync-missing-file.sh +0 -61
  122. package/tools/tests/test-profile-adopt-syncs-anchor-and-workspace.sh +0 -90
  123. package/tools/tests/test-profile-smoke-collision.sh +0 -44
  124. package/tools/tests/test-profile-smoke-invalid-claude-config.sh +0 -31
  125. package/tools/tests/test-profile-smoke-invalid-provider-pool.sh +0 -68
  126. package/tools/tests/test-profile-smoke-repo-slug-mismatch.sh +0 -36
  127. package/tools/tests/test-profile-smoke.sh +0 -45
  128. package/tools/tests/test-project-init-force-and-skip-sync.sh +0 -61
  129. package/tools/tests/test-project-init-repo-slug-mismatch.sh +0 -29
  130. package/tools/tests/test-project-init.sh +0 -66
  131. package/tools/tests/test-project-launchd-bootstrap.sh +0 -66
  132. package/tools/tests/test-project-remove.sh +0 -150
  133. package/tools/tests/test-project-runtime-supervisor.sh +0 -47
  134. package/tools/tests/test-project-runtimectl-launchd.sh +0 -115
  135. package/tools/tests/test-project-runtimectl-missing-profile.sh +0 -54
  136. package/tools/tests/test-project-runtimectl-start-falls-back-to-bootstrap.sh +0 -108
  137. package/tools/tests/test-project-runtimectl-status-reports-supervisor-as-heartbeat-parent.sh +0 -95
  138. package/tools/tests/test-project-runtimectl-status-supervisor-running.sh +0 -59
  139. package/tools/tests/test-project-runtimectl-stop-cancels-pending-kick.sh +0 -85
  140. package/tools/tests/test-project-runtimectl-stop-clears-running-labels.sh +0 -78
  141. package/tools/tests/test-project-runtimectl.sh +0 -212
  142. package/tools/tests/test-provider-cooldown-state-prefers-runtime-worker-context.sh +0 -39
  143. package/tools/tests/test-provider-cooldown-state.sh +0 -59
  144. package/tools/tests/test-public-repo-docs.sh +0 -160
  145. package/tools/tests/test-reconcile-pr-worker-acp-config-routing.sh +0 -75
  146. package/tools/tests/test-render-dashboard-snapshot.sh +0 -149
  147. package/tools/tests/test-render-flow-config-demo-profile.sh +0 -36
  148. package/tools/tests/test-render-flow-config-provider-pool-fallback.sh +0 -81
  149. package/tools/tests/test-render-flow-config.sh +0 -52
  150. package/tools/tests/test-run-codex-task-claude-routing.sh +0 -125
  151. package/tools/tests/test-run-codex-task-codex-resident-routing.sh +0 -108
  152. package/tools/tests/test-run-codex-task-kilo-routing.sh +0 -98
  153. package/tools/tests/test-run-codex-task-openclaw-resident-routing.sh +0 -117
  154. package/tools/tests/test-run-codex-task-openclaw-routing.sh +0 -113
  155. package/tools/tests/test-run-codex-task-opencode-routing.sh +0 -98
  156. package/tools/tests/test-run-codex-task-provider-pool-fallback-routing.sh +0 -146
  157. package/tools/tests/test-scaffold-profile.sh +0 -108
  158. package/tools/tests/test-serve-dashboard.sh +0 -93
  159. package/tools/tests/test-start-issue-worker-blocked-context.sh +0 -129
  160. package/tools/tests/test-start-issue-worker-blocks-complete-recurring-checklist.sh +0 -189
  161. package/tools/tests/test-start-issue-worker-local-install-routing.sh +0 -157
  162. package/tools/tests/test-start-issue-worker-profile-template-routing.sh +0 -149
  163. package/tools/tests/test-start-issue-worker-recurring-resident-reuse-codex.sh +0 -212
  164. package/tools/tests/test-start-issue-worker-recurring-resident-reuse.sh +0 -219
  165. package/tools/tests/test-start-issue-worker-renders-verification-snippet.sh +0 -155
  166. package/tools/tests/test-start-issue-worker-resident-reuse-falls-back-to-new-worktree.sh +0 -199
  167. package/tools/tests/test-start-pr-fix-worker-host-blocker-context.sh +0 -275
  168. package/tools/tests/test-start-resident-issue-loop-adopts-next-recurring-issue.sh +0 -185
  169. package/tools/tests/test-start-resident-issue-loop-clears-pending-while-waiting-due.sh +0 -152
  170. package/tools/tests/test-start-resident-issue-loop-consumes-queued-lease.sh +0 -186
  171. package/tools/tests/test-start-resident-issue-loop-fails-over-provider-pool.sh +0 -212
  172. package/tools/tests/test-start-resident-issue-loop-immediate-cycles.sh +0 -148
  173. package/tools/tests/test-start-resident-issue-loop-waits-for-provider.sh +0 -194
  174. package/tools/tests/test-start-resident-issue-loop-waits-for-terminal-reconcile-status.sh +0 -198
  175. package/tools/tests/test-start-resident-issue-loop-yields-to-live-lane-controller.sh +0 -145
  176. package/tools/tests/test-sync-pr-labels-fix-lane-uses-repair-queued.sh +0 -67
  177. package/tools/tests/test-sync-recurring-issue-checklist-backfills-workflow-complete-blocker.sh +0 -70
  178. package/tools/tests/test-sync-recurring-issue-checklist.sh +0 -95
  179. package/tools/tests/test-sync-shared-agent-home-local-source-root.sh +0 -66
  180. package/tools/tests/test-sync-shared-agent-home-preserves-unrelated-workflow-catalog-skill.sh +0 -47
  181. package/tools/tests/test-test-smoke.sh +0 -86
  182. package/tools/tests/test-uninstall-project-launchd.sh +0 -37
  183. package/tools/tests/test-update-github-labels-prefers-sibling-helper.sh +0 -49
  184. package/tools/tests/test-workflow-catalog.sh +0 -43
package/README.md CHANGED
@@ -302,6 +302,9 @@ During `setup`, ACP can:
302
302
  `openclaw` when ACP knows a safe install command, and otherwise show
303
303
  backend-specific next steps, install/auth/verify examples, and a docs URL it
304
304
  can open for you on interactive machines
305
+ - defer anchor repo sync automatically when ACP can scaffold the profile but
306
+ cannot reach the repo remote yet, so setup can still finish with a clear
307
+ follow-up instead of failing half way through
305
308
  - run one final fix-up summary at the end so you can clear whatever is still
306
309
  red instead of guessing which step to retry next
307
310
  - scaffold the profile, run doctor checks, and optionally start the runtime in
@@ -550,6 +553,9 @@ Use `--purge-paths` only when you want ACP-managed directories removed too.
550
553
  `launchd-uninstall`, and `remove`.
551
554
  - `gh` cannot access the repo
552
555
  Re-run `gh auth login` and confirm the repo slug in the profile is correct.
556
+ - setup deferred anchor repo sync
557
+ ACP could not reach the repo remote yet. Fix Git access or the remote URL,
558
+ then rerun `setup` or `init` without `--skip-anchor-sync`.
553
559
  - backend auth failures from `codex`, `claude`, or `openclaw`
554
560
  Authenticate that backend before starting ACP in the background.
555
561
  - `node` is older than `18`
@@ -191,12 +191,14 @@ function runCapture(command, args, options = {}) {
191
191
  const result = spawnSync(command, args, {
192
192
  encoding: "utf8",
193
193
  env: options.env || process.env,
194
- cwd: options.cwd || process.cwd()
194
+ cwd: options.cwd || process.cwd(),
195
+ timeout: options.timeoutMs || 0
195
196
  });
196
197
  return {
197
198
  status: typeof result.status === "number" ? result.status : (result.error ? 1 : 0),
198
199
  stdout: result.stdout || "",
199
- stderr: result.stderr || ""
200
+ stderr: result.stderr || "",
201
+ error: result.error || null
200
202
  };
201
203
  }
202
204
 
@@ -1044,6 +1046,92 @@ function printPrereqSummary(prereq) {
1044
1046
  console.log(`- backend note: ${backendReadinessHint(prereq.workerCommand)}`);
1045
1047
  }
1046
1048
 
1049
+ function probeAnchorSyncReadiness(repoRoot) {
1050
+ if (!repoRoot || !fs.existsSync(repoRoot)) {
1051
+ return {
1052
+ status: "deferred",
1053
+ reason: "source-repo-missing",
1054
+ remoteUrl: "",
1055
+ details: ""
1056
+ };
1057
+ }
1058
+
1059
+ const remoteResult = runCapture("git", ["remote", "get-url", "origin"], {
1060
+ cwd: repoRoot,
1061
+ timeoutMs: 5000
1062
+ });
1063
+ const remoteUrl = remoteResult.stdout.trim();
1064
+ if (remoteResult.status !== 0 || !remoteUrl) {
1065
+ return {
1066
+ status: "deferred",
1067
+ reason: "no-origin-remote",
1068
+ remoteUrl: "",
1069
+ details: `${remoteResult.stdout}${remoteResult.stderr}`.trim()
1070
+ };
1071
+ }
1072
+
1073
+ const probeResult = runCapture("git", ["ls-remote", "--exit-code", "origin", "HEAD"], {
1074
+ cwd: repoRoot,
1075
+ timeoutMs: 15000
1076
+ });
1077
+ if (probeResult.status === 0) {
1078
+ return {
1079
+ status: "ready",
1080
+ reason: "",
1081
+ remoteUrl,
1082
+ details: ""
1083
+ };
1084
+ }
1085
+
1086
+ const details = `${probeResult.stdout}${probeResult.stderr}`.trim();
1087
+ let reason = "git-remote-unreachable";
1088
+ if (probeResult.error && probeResult.error.code === "ETIMEDOUT") {
1089
+ reason = "git-remote-timeout";
1090
+ } else if (/could not read Username|Authentication failed|Permission denied|Repository not found|access denied|not found/i.test(details)) {
1091
+ reason = "git-remote-auth-or-access-error";
1092
+ } else if (/Could not resolve host|Name or service not known|Temporary failure in name resolution/i.test(details)) {
1093
+ reason = "git-remote-dns-error";
1094
+ }
1095
+
1096
+ return {
1097
+ status: "deferred",
1098
+ reason,
1099
+ remoteUrl,
1100
+ details
1101
+ };
1102
+ }
1103
+
1104
+ function buildAnchorSyncDecision(options, sourceRepoRoot) {
1105
+ if (options.skipAnchorSync) {
1106
+ return {
1107
+ status: "skipped",
1108
+ reason: "disabled",
1109
+ remoteUrl: "",
1110
+ details: "",
1111
+ skipAnchorSync: true
1112
+ };
1113
+ }
1114
+
1115
+ const probe = probeAnchorSyncReadiness(sourceRepoRoot);
1116
+ if (probe.status === "ready") {
1117
+ return {
1118
+ status: "ok",
1119
+ reason: "",
1120
+ remoteUrl: probe.remoteUrl,
1121
+ details: probe.details,
1122
+ skipAnchorSync: false
1123
+ };
1124
+ }
1125
+
1126
+ return {
1127
+ status: "deferred",
1128
+ reason: probe.reason,
1129
+ remoteUrl: probe.remoteUrl,
1130
+ details: probe.details,
1131
+ skipAnchorSync: true
1132
+ };
1133
+ }
1134
+
1047
1135
  function profileExists(profileRegistryRoot, profileId) {
1048
1136
  return fs.existsSync(path.join(profileRegistryRoot, profileId, "control-plane.yaml"));
1049
1137
  }
@@ -1082,6 +1170,7 @@ function buildSetupDryRunPlan(options, context, config) {
1082
1170
  const dependencyPlan = buildDependencyInstallPlan(prereq.missingRequired);
1083
1171
  const workerInstallPlan = buildWorkerBackendInstallPlan(prereq.workerCommand);
1084
1172
  const workerGuide = backendSetupGuide(prereq.workerCommand);
1173
+ const anchorSync = buildAnchorSyncDecision(options, config.paths.sourceRepoRoot);
1085
1174
 
1086
1175
  let dependencyAction = planStatusWithReason("not-needed");
1087
1176
  if (!prereq.coreToolsOk) {
@@ -1128,6 +1217,8 @@ function buildSetupDryRunPlan(options, context, config) {
1128
1217
  runtimeStartAction = planStatusWithReason("blocked", `missing-tools:${prereq.missingRequired.join(",")}`);
1129
1218
  } else if (!prereq.workerAvailable) {
1130
1219
  runtimeStartAction = planStatusWithReason("blocked", `missing-worker:${prereq.workerCommand}`);
1220
+ } else if (anchorSync.status !== "ok") {
1221
+ runtimeStartAction = planStatusWithReason("blocked", `anchor-sync-${anchorSync.reason}`);
1131
1222
  } else if (!prereq.ghAuthOk) {
1132
1223
  runtimeStartAction = planStatusWithReason("blocked", "gh-auth-not-ready");
1133
1224
  } else {
@@ -1156,6 +1247,7 @@ function buildSetupDryRunPlan(options, context, config) {
1156
1247
  workerInstallPlan,
1157
1248
  workerAction,
1158
1249
  workerGuide,
1250
+ anchorSync,
1159
1251
  githubAuthAction,
1160
1252
  runtimeStartAction,
1161
1253
  launchdAction
@@ -1170,6 +1262,7 @@ function printSetupDryRunPlan(context, config, plan) {
1170
1262
  console.log(`- profile registry root: ${context.profileRegistryRoot}`);
1171
1263
  console.log(`- sync packaged runtime: would-run`);
1172
1264
  console.log(`- scaffold/adopt profile: would-run`);
1265
+ console.log(`- anchor repo sync: ${plan.anchorSync.status === "ok" ? "would-run" : `${plan.anchorSync.status} (${plan.anchorSync.reason})`}`);
1173
1266
  console.log(`- doctor: would-run`);
1174
1267
  console.log(`- dependency install: ${plan.dependencyAction.status}${plan.dependencyAction.reason ? ` (${plan.dependencyAction.reason})` : ""}`);
1175
1268
  if (plan.dependencyAction.status !== "not-needed" && plan.dependencyPlan && plan.dependencyPlan.commands.length > 0) {
@@ -1202,6 +1295,11 @@ function buildSetupResultPayload(params) {
1202
1295
  status: params.coreToolsStatus,
1203
1296
  missingRequiredTools: params.missingRequiredTools
1204
1297
  },
1298
+ anchorSync: {
1299
+ status: params.anchorSyncStatus,
1300
+ reason: params.anchorSyncReason,
1301
+ remoteUrl: params.anchorSyncRemoteUrl
1302
+ },
1205
1303
  workerBackend: {
1206
1304
  command: params.workerBackendCommand,
1207
1305
  status: params.workerBackendStatus,
@@ -1252,6 +1350,9 @@ function emitSetupJsonPayload(payload) {
1252
1350
 
1253
1351
  function collectFinalSetupIssues(config, prereq, doctorKv, runtimeStartStatus) {
1254
1352
  const issues = [];
1353
+ if (config.anchorSync && config.anchorSync.status !== "ok") {
1354
+ issues.push(`anchor repo sync ${config.anchorSync.status}: ${config.anchorSync.reason}`);
1355
+ }
1255
1356
  if (!prereq.coreToolsOk) {
1256
1357
  issues.push(`missing core tools: ${prereq.missingRequired.join(", ")}`);
1257
1358
  }
@@ -1271,7 +1372,11 @@ function collectFinalSetupIssues(config, prereq, doctorKv, runtimeStartStatus) {
1271
1372
  }
1272
1373
 
1273
1374
  async function maybeRunFinalSetupFixups(options, scopedContext, config, currentState) {
1274
- const issues = collectFinalSetupIssues(config, currentState.prereq, currentState.doctorKv, currentState.runtimeStartStatus);
1375
+ const effectiveConfig = {
1376
+ ...config,
1377
+ anchorSync: currentState.anchorSync || config.anchorSync || null
1378
+ };
1379
+ const issues = collectFinalSetupIssues(effectiveConfig, currentState.prereq, currentState.doctorKv, currentState.runtimeStartStatus);
1275
1380
  if (issues.length === 0) {
1276
1381
  return {
1277
1382
  status: "not-needed",
@@ -1315,6 +1420,7 @@ async function maybeRunFinalSetupFixups(options, scopedContext, config, currentS
1315
1420
  let githubAuthStep = currentState.githubAuthStep;
1316
1421
  let workerSetupStep = currentState.workerSetupStep;
1317
1422
  let doctorKv = currentState.doctorKv;
1423
+ let anchorSync = currentState.anchorSync || effectiveConfig.anchorSync || null;
1318
1424
  let runtimeStartStatus = currentState.runtimeStartStatus;
1319
1425
  let runtimeStartReason = currentState.runtimeStartReason;
1320
1426
  let runtimeStatusKv = currentState.runtimeStatusKv;
@@ -1353,6 +1459,9 @@ async function maybeRunFinalSetupFixups(options, scopedContext, config, currentS
1353
1459
  } else if (!prereq.workerAvailable) {
1354
1460
  runtimeStartStatus = "skipped";
1355
1461
  runtimeStartReason = `missing-worker:${prereq.workerCommand}`;
1462
+ } else if (anchorSync && anchorSync.status !== "ok") {
1463
+ runtimeStartStatus = "skipped";
1464
+ runtimeStartReason = `anchor-sync-${anchorSync.reason}`;
1356
1465
  } else if (!prereq.ghAuthOk) {
1357
1466
  runtimeStartStatus = "skipped";
1358
1467
  runtimeStartReason = "gh-auth-not-ready";
@@ -1376,7 +1485,7 @@ async function maybeRunFinalSetupFixups(options, scopedContext, config, currentS
1376
1485
  launchdInstallReason = "";
1377
1486
  }
1378
1487
 
1379
- const finalIssues = collectFinalSetupIssues(config, prereq, doctorKv, runtimeStartStatus);
1488
+ const finalIssues = collectFinalSetupIssues({ ...effectiveConfig, anchorSync }, prereq, doctorKv, runtimeStartStatus);
1380
1489
  let status = "ok";
1381
1490
  if (dependencyInstall.status === "failed" || githubAuthStep.status === "failed" || workerSetupStep.installStatus === "failed") {
1382
1491
  status = "failed";
@@ -1387,6 +1496,7 @@ async function maybeRunFinalSetupFixups(options, scopedContext, config, currentS
1387
1496
  return {
1388
1497
  status,
1389
1498
  actions,
1499
+ anchorSync,
1390
1500
  prereq,
1391
1501
  dependencyInstall,
1392
1502
  githubAuthStep,
@@ -1564,6 +1674,9 @@ async function runSetupFlow(forwardedArgs) {
1564
1674
  worktreeRoot: config.paths.worktreeRoot,
1565
1675
  coreToolsStatus: plan.prereq.coreToolsOk ? "ok" : "missing",
1566
1676
  missingRequiredTools: plan.prereq.missingRequired,
1677
+ anchorSyncStatus: plan.anchorSync.status === "ok" ? "would-run" : plan.anchorSync.status,
1678
+ anchorSyncReason: plan.anchorSync.reason || "",
1679
+ anchorSyncRemoteUrl: plan.anchorSync.remoteUrl || "",
1567
1680
  workerBackendCommand: plan.prereq.workerCommand,
1568
1681
  workerBackendStatus: plan.prereq.workerAvailable ? "ok" : "missing",
1569
1682
  workerSetupGuideStatus: "planned",
@@ -1610,6 +1723,13 @@ async function runSetupFlow(forwardedArgs) {
1610
1723
  console.log(`PROFILE_EXISTS=${plan.profileExists ? "yes" : "no"}`);
1611
1724
  console.log(`CORE_TOOLS_STATUS=${plan.prereq.coreToolsOk ? "ok" : "missing"}`);
1612
1725
  console.log(`MISSING_REQUIRED_TOOLS=${plan.prereq.missingRequired.join(",")}`);
1726
+ console.log(`ANCHOR_SYNC_STATUS=${plan.anchorSync.status === "ok" ? "would-run" : plan.anchorSync.status}`);
1727
+ if (plan.anchorSync.reason) {
1728
+ console.log(`ANCHOR_SYNC_REASON=${plan.anchorSync.reason}`);
1729
+ }
1730
+ if (plan.anchorSync.remoteUrl) {
1731
+ console.log(`ANCHOR_SYNC_REMOTE_URL=${plan.anchorSync.remoteUrl}`);
1732
+ }
1613
1733
  console.log(`WORKER_BACKEND_COMMAND=${plan.prereq.workerCommand}`);
1614
1734
  console.log(`WORKER_BACKEND_STATUS=${plan.prereq.workerAvailable ? "ok" : "missing"}`);
1615
1735
  console.log(`GITHUB_AUTH_STATUS=${plan.prereq.ghAuthOk ? "ok" : "not-ready"}`);
@@ -1686,6 +1806,16 @@ async function runSetupFlow(forwardedArgs) {
1686
1806
  prereq = collectPrereqStatus(config.codingWorker);
1687
1807
 
1688
1808
  const scopedContext = buildScopedContext(context, config.profileId);
1809
+ const anchorSync = buildAnchorSyncDecision(options, config.paths.sourceRepoRoot);
1810
+
1811
+ if (anchorSync.status === "deferred") {
1812
+ console.log("\nAnchor repo sync will be deferred for this setup run.");
1813
+ if (anchorSync.remoteUrl) {
1814
+ console.log(`- remote: ${anchorSync.remoteUrl}`);
1815
+ }
1816
+ console.log(`- reason: ${anchorSync.reason}`);
1817
+ console.log("- next step: authenticate Git access or fix the repo remote, then rerun `setup` or `init` without skipping anchor sync.");
1818
+ }
1689
1819
 
1690
1820
  runSetupStep(scopedContext, "Sync packaged runtime into ~/.agent-runtime", "tools/bin/sync-shared-agent-home.sh", []);
1691
1821
 
@@ -1704,7 +1834,7 @@ async function runSetupFlow(forwardedArgs) {
1704
1834
  if (options.force) {
1705
1835
  initArgs.push("--force");
1706
1836
  }
1707
- if (options.skipAnchorSync) {
1837
+ if (anchorSync.skipAnchorSync) {
1708
1838
  initArgs.push("--skip-anchor-sync");
1709
1839
  }
1710
1840
  if (options.skipWorkspaceSync) {
@@ -1731,6 +1861,9 @@ async function runSetupFlow(forwardedArgs) {
1731
1861
  } else if (!prereq.workerAvailable) {
1732
1862
  runtimeStartReason = `missing-worker:${prereq.workerCommand}`;
1733
1863
  console.log(`runtime start skipped: ${prereq.workerCommand} is not available on PATH`);
1864
+ } else if (anchorSync.status !== "ok") {
1865
+ runtimeStartReason = `anchor-sync-${anchorSync.reason}`;
1866
+ console.log("runtime start skipped: ACP deferred anchor repo sync for this setup run.");
1734
1867
  } else if (!prereq.ghAuthOk) {
1735
1868
  runtimeStartReason = "gh-auth-not-ready";
1736
1869
  console.log("runtime start skipped: GitHub CLI is not authenticated yet. Run `gh auth login` and start the runtime afterwards.");
@@ -1760,6 +1893,7 @@ async function runSetupFlow(forwardedArgs) {
1760
1893
  }
1761
1894
 
1762
1895
  const finalFixup = await maybeRunFinalSetupFixups(options, scopedContext, config, {
1896
+ anchorSync,
1763
1897
  prereq,
1764
1898
  dependencyInstall,
1765
1899
  githubAuthStep,
@@ -1796,6 +1930,9 @@ async function runSetupFlow(forwardedArgs) {
1796
1930
  worktreeRoot: config.paths.worktreeRoot,
1797
1931
  coreToolsStatus: prereq.coreToolsOk ? "ok" : "missing",
1798
1932
  missingRequiredTools: prereq.missingRequired,
1933
+ anchorSyncStatus: anchorSync.status,
1934
+ anchorSyncReason: anchorSync.reason || "",
1935
+ anchorSyncRemoteUrl: anchorSync.remoteUrl || "",
1799
1936
  workerBackendCommand: prereq.workerCommand,
1800
1937
  workerBackendStatus: prereq.workerAvailable ? "ok" : "missing",
1801
1938
  workerSetupGuideStatus: workerSetupStep.status,
@@ -1846,6 +1983,13 @@ async function runSetupFlow(forwardedArgs) {
1846
1983
  console.log(`CODING_WORKER=${config.codingWorker}`);
1847
1984
  console.log(`CORE_TOOLS_STATUS=${prereq.coreToolsOk ? "ok" : "missing"}`);
1848
1985
  console.log(`MISSING_REQUIRED_TOOLS=${prereq.missingRequired.join(",")}`);
1986
+ console.log(`ANCHOR_SYNC_STATUS=${anchorSync.status}`);
1987
+ if (anchorSync.reason) {
1988
+ console.log(`ANCHOR_SYNC_REASON=${anchorSync.reason}`);
1989
+ }
1990
+ if (anchorSync.remoteUrl) {
1991
+ console.log(`ANCHOR_SYNC_REMOTE_URL=${anchorSync.remoteUrl}`);
1992
+ }
1849
1993
  console.log(`WORKER_BACKEND_COMMAND=${prereq.workerCommand}`);
1850
1994
  console.log(`WORKER_BACKEND_STATUS=${prereq.workerAvailable ? "ok" : "missing"}`);
1851
1995
  console.log(`WORKER_SETUP_GUIDE_STATUS=${workerSetupStep.status}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-control-plane",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Help a repo keep GitHub-driven coding agents running reliably without constant human babysitting",
5
5
  "homepage": "https://github.com/ducminhnguyen0319/agent-control-plane",
6
6
  "bugs": {
@@ -32,7 +32,6 @@
32
32
  "tools/dashboard/server.py",
33
33
  "tools/dashboard/styles.css",
34
34
  "tools/templates",
35
- "tools/tests",
36
35
  "tools/vendor/codex-quota/LICENSE",
37
36
  "tools/vendor/codex-quota/codex-quota.js",
38
37
  "tools/vendor/codex-quota/lib",
@@ -41,7 +40,7 @@
41
40
  "scripts": {
42
41
  "doctor": "node ./npm/bin/agent-control-plane.js doctor",
43
42
  "smoke": "node ./npm/bin/agent-control-plane.js smoke",
44
- "test": "bash tools/tests/test-agent-control-plane-npm-cli.sh"
43
+ "test": "bash tools/tests/test-agent-control-plane-npm-cli.sh && bash tools/tests/test-vendored-codex-quota-claude-oauth-only.sh"
45
44
  },
46
45
  "keywords": [
47
46
  "agents",
@@ -280,7 +280,7 @@ Root-level fields are preserved on write; unknown root fields are kept intact.
280
280
 
281
281
  Claude multi-account files (`~/.claude-accounts.json`) use the same root fields
282
282
  (`schemaVersion`, `activeLabel`) and store account entries that include a
283
- `sessionKey` or OAuth tokens.
283
+ Claude OAuth token and refresh metadata.
284
284
 
285
285
  ## OAuth Flow
286
286
 
@@ -400,17 +400,13 @@ To add a Claude credential interactively:
400
400
  codex-quota claude add
401
401
  ```
402
402
 
403
- This uses your local Claude session to call:
404
- - `https://claude.ai/api/organizations`
405
- - `https://claude.ai/api/organizations/{orgId}/usage`
406
- - `https://claude.ai/api/organizations/{orgId}/overage_spend_limit`
407
- - `https://claude.ai/api/account`
403
+ This uses OAuth credentials to call:
404
+ - `https://api.anthropic.com/api/oauth/usage`
408
405
 
409
406
  Authentication sources (in order):
410
407
  1. `CLAUDE_ACCOUNTS` env var (JSON array or `{ accounts: [...] }`)
411
- 2. `~/.claude-accounts.json` (multi-account format)
412
- 3. Browser cookies (Chromium/Chrome) to read `sessionKey` and `lastActiveOrg`
413
- 4. `~/.claude/.credentials.json` OAuth `accessToken`
408
+ 2. `~/.claude-accounts.json` (multi-account format with `oauthToken`)
409
+ 3. `~/.claude/.credentials.json` OAuth `accessToken`
414
410
 
415
411
  Multi-account format (Claude):
416
412
  ```json
@@ -418,8 +414,6 @@ Multi-account format (Claude):
418
414
  "accounts": [
419
415
  {
420
416
  "label": "personal",
421
- "sessionKey": "sk-ant-oat...",
422
- "cfClearance": "cf_clearance...",
423
417
  "oauthToken": "claude-ai-access-token",
424
418
  "orgId": "org_uuid_optional"
425
419
  }
@@ -428,13 +422,12 @@ Multi-account format (Claude):
428
422
  ```
429
423
 
430
424
  Notes:
431
- - Only `label` plus one of `sessionKey` or `oauthToken` is required.
432
- - `cfClearance`, `orgId`, and `cookies` are optional.
425
+ - `label` plus `oauthToken` is required.
426
+ - `orgId` is optional.
433
427
 
434
428
  Environment overrides:
435
429
  - `CLAUDE_ACCOUNTS` to supply multi-account JSON directly
436
430
  - `CLAUDE_CREDENTIALS_PATH` to point to a different credentials file
437
- - `CLAUDE_COOKIE_DB_PATH` to point to a specific Chromium/Chrome Cookies DB
438
431
 
439
432
  Codex overrides:
440
433
  - `CODEX_ACCOUNTS` to supply multi-account JSON directly (read-only)
@@ -443,8 +436,7 @@ Codex overrides:
443
436
  - `PI_AUTH_PATH` to point to a different pi auth file
444
437
 
445
438
  Notes:
446
- - On Linux, cookie access requires `sqlite3` and `secret-tool` (libsecret) to decrypt cookies.
447
- - For best results, keep `claude.ai` logged in within your Chromium/Chrome profile.
439
+ - Claude usage in the bundled public package is OAuth-only.
448
440
 
449
441
  ## Releasing
450
442
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Claude account loading, session/OAuth resolution.
2
+ * Claude account loading and OAuth resolution.
3
3
  * Depends on: lib/constants.js, lib/container.js, lib/paths.js
4
4
  */
5
5
 
@@ -8,52 +8,18 @@ import { CLAUDE_CREDENTIALS_PATH, CLAUDE_MULTI_ACCOUNT_PATHS } from "./constants
8
8
  import { readMultiAccountContainer, writeMultiAccountContainer } from "./container.js";
9
9
  import { getOpencodeAuthPath } from "./paths.js";
10
10
 
11
- export function isClaudeSessionKey(value) {
12
- return typeof value === "string" && value.startsWith("sk-ant-");
13
- }
14
-
15
- export function findClaudeSessionKey(value) {
16
- if (isClaudeSessionKey(value)) return value;
17
- if (typeof value === "string") {
18
- const match = value.match(/sk-ant-[a-z0-9_-]+/i);
19
- if (match) return match[0];
20
- }
21
- if (!value || typeof value !== "object") return null;
22
-
23
- const direct = value.sessionKey
24
- ?? value.session_key
25
- ?? value.token
26
- ?? value.sessionToken
27
- ?? value.accessToken
28
- ?? value.access_token
29
- ?? value.oauthAccessToken;
30
- if (isClaudeSessionKey(direct)) return direct;
31
-
32
- for (const child of Object.values(value)) {
33
- const found = findClaudeSessionKey(child);
34
- if (found) return found;
35
- }
36
- return null;
37
- }
38
-
39
11
  export function normalizeClaudeAccount(raw, source) {
40
12
  if (!raw || typeof raw !== "object") return null;
41
13
  const label = raw.label ?? null;
42
- const sessionKey = raw.sessionKey ?? raw.session_key ?? null;
43
14
  const oauthToken = raw.oauthToken ?? raw.oauth_token ?? raw.accessToken ?? raw.access_token ?? null;
44
- const cfClearance = raw.cfClearance ?? raw.cf_clearance ?? null;
45
15
  const orgId = raw.orgId ?? raw.org_id ?? null;
46
- const cookies = raw.cookies && typeof raw.cookies === "object" ? raw.cookies : null;
47
16
  const oauthRefreshToken = raw.oauthRefreshToken ?? raw.oauth_refresh_token ?? null;
48
17
  const oauthExpiresAt = raw.oauthExpiresAt ?? raw.oauth_expires_at ?? null;
49
18
  const oauthScopes = raw.oauthScopes ?? raw.oauth_scopes ?? null;
50
19
  return {
51
20
  label,
52
- sessionKey,
53
21
  oauthToken,
54
- cfClearance,
55
22
  orgId,
56
- cookies,
57
23
  oauthRefreshToken,
58
24
  oauthExpiresAt,
59
25
  oauthScopes,
@@ -63,9 +29,8 @@ export function normalizeClaudeAccount(raw, source) {
63
29
 
64
30
  export function isValidClaudeAccount(account) {
65
31
  if (!account?.label) return false;
66
- const sessionKey = account.sessionKey ?? findClaudeSessionKey(account.cookies);
67
32
  const oauthToken = account.oauthToken ?? null;
68
- return Boolean(sessionKey || oauthToken);
33
+ return Boolean(oauthToken);
69
34
  }
70
35
 
71
36
  export function loadClaudeAccountsFromEnv() {
@@ -147,37 +112,6 @@ export function saveClaudeAccounts(accounts) {
147
112
  return result.path;
148
113
  }
149
114
 
150
- export function loadClaudeSessionFromCredentials() {
151
- const credentialsPath = process.env.CLAUDE_CREDENTIALS_PATH || CLAUDE_CREDENTIALS_PATH;
152
- if (!existsSync(credentialsPath)) {
153
- return {
154
- sessionKey: null,
155
- source: credentialsPath,
156
- error: `Claude credentials not found at ${credentialsPath}`,
157
- };
158
- }
159
-
160
- try {
161
- const raw = readFileSync(credentialsPath, "utf-8");
162
- const parsed = JSON.parse(raw);
163
- const sessionKey = findClaudeSessionKey(parsed);
164
- if (!sessionKey) {
165
- return {
166
- sessionKey: null,
167
- source: credentialsPath,
168
- error: "No Claude sessionKey found in credentials file",
169
- };
170
- }
171
- return { sessionKey, source: credentialsPath };
172
- } catch (err) {
173
- return {
174
- sessionKey: null,
175
- source: credentialsPath,
176
- error: `Failed to read Claude credentials: ${err?.message ?? String(err)}`,
177
- };
178
- }
179
- }
180
-
181
115
  export function loadClaudeOAuthToken() {
182
116
  const credentialsPath = process.env.CLAUDE_CREDENTIALS_PATH || CLAUDE_CREDENTIALS_PATH;
183
117
  if (!existsSync(credentialsPath)) {