agent-control-plane 0.1.2 → 0.1.4

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 (182) hide show
  1. package/README.md +6 -0
  2. package/npm/bin/agent-control-plane.js +149 -5
  3. package/package.json +1 -2
  4. package/tools/bin/render-architecture-infographics.sh +0 -110
  5. package/tools/bin/render-dashboard-demo-media.sh +0 -333
  6. package/tools/tests/test-agent-control-plane-npm-cli.sh +0 -280
  7. package/tools/tests/test-agent-github-update-labels-falls-back-to-repository-id.sh +0 -56
  8. package/tools/tests/test-agent-project-claude-session-wrapper-clears-stale-sandbox-artifacts.sh +0 -89
  9. package/tools/tests/test-agent-project-claude-session-wrapper-does-not-retry-provider-quota.sh +0 -82
  10. package/tools/tests/test-agent-project-claude-session-wrapper-retries-transient-failures.sh +0 -90
  11. package/tools/tests/test-agent-project-claude-session-wrapper-times-out.sh +0 -73
  12. package/tools/tests/test-agent-project-claude-session-wrapper.sh +0 -103
  13. package/tools/tests/test-agent-project-cleanup-session-orphan-fallback.sh +0 -90
  14. package/tools/tests/test-agent-project-cleanup-session-skip-worktree-cleanup.sh +0 -90
  15. package/tools/tests/test-agent-project-codex-live-thread-persist.sh +0 -76
  16. package/tools/tests/test-agent-project-codex-recovery.sh +0 -731
  17. package/tools/tests/test-agent-project-codex-session-wrapper-clears-stale-sandbox-artifacts.sh +0 -105
  18. package/tools/tests/test-agent-project-codex-session-wrapper.sh +0 -97
  19. package/tools/tests/test-agent-project-open-pr-worktree-config-prefix.sh +0 -81
  20. package/tools/tests/test-agent-project-openclaw-session-wrapper-clears-stale-sandbox-artifacts.sh +0 -109
  21. package/tools/tests/test-agent-project-openclaw-session-wrapper-infers-blocked-result-contract.sh +0 -89
  22. package/tools/tests/test-agent-project-openclaw-session-wrapper-recovers-literal-env-artifacts.sh +0 -113
  23. package/tools/tests/test-agent-project-openclaw-session-wrapper-recovers-version-mismatch.sh +0 -135
  24. package/tools/tests/test-agent-project-openclaw-session-wrapper-resident.sh +0 -179
  25. package/tools/tests/test-agent-project-openclaw-session-wrapper-reuses-existing-agent-after-add-race.sh +0 -119
  26. package/tools/tests/test-agent-project-openclaw-session-wrapper-terminates-rate-limit-hang.sh +0 -91
  27. package/tools/tests/test-agent-project-openclaw-session-wrapper.sh +0 -117
  28. package/tools/tests/test-agent-project-publish-issue-pr-prunes-stale-worktree-entry.sh +0 -148
  29. package/tools/tests/test-agent-project-publish-issue-pr-reads-archived-session.sh +0 -146
  30. package/tools/tests/test-agent-project-publish-issue-pr-recovers-final-head.sh +0 -145
  31. package/tools/tests/test-agent-project-publish-issue-pr-reuses-existing-worktree.sh +0 -147
  32. package/tools/tests/test-agent-project-reconcile-failure-reason.sh +0 -456
  33. package/tools/tests/test-agent-project-reconcile-issue-archived-session-fallback.sh +0 -96
  34. package/tools/tests/test-agent-project-reconcile-issue-before-blocked.sh +0 -90
  35. package/tools/tests/test-agent-project-reconcile-issue-host-verification-recovery-uses-recovered-worktree.sh +0 -212
  36. package/tools/tests/test-agent-project-reconcile-issue-host-verification-recovery.sh +0 -207
  37. package/tools/tests/test-agent-project-reconcile-issue-provider-quota-schedules-provider-cooldown.sh +0 -101
  38. package/tools/tests/test-agent-project-reconcile-issue-session-backfills-lane-metadata-from-worker-key.sh +0 -113
  39. package/tools/tests/test-agent-project-reconcile-issue-session-clears-stale-failed-summary.sh +0 -117
  40. package/tools/tests/test-agent-project-reconcile-issue-session-initializes-shared-agent-home.sh +0 -55
  41. package/tools/tests/test-agent-project-reconcile-issue-session-normalizes-runner-state.sh +0 -125
  42. package/tools/tests/test-agent-project-reconcile-issue-session-records-invalid-contract-summary.sh +0 -118
  43. package/tools/tests/test-agent-project-reconcile-issue-session-skips-duplicate-blocked-comment.sh +0 -144
  44. package/tools/tests/test-agent-project-reconcile-issue-session-standardizes-no-commits-blocker.sh +0 -145
  45. package/tools/tests/test-agent-project-reconcile-issue-session-synthesizes-blocked-comment.sh +0 -139
  46. package/tools/tests/test-agent-project-reconcile-pr-blocked-host-recovery.sh +0 -242
  47. package/tools/tests/test-agent-project-reconcile-pr-guard-blocked-no-commit.sh +0 -142
  48. package/tools/tests/test-agent-project-reconcile-pr-provider-quota-schedules-provider-cooldown.sh +0 -106
  49. package/tools/tests/test-agent-project-reconcile-pr-session-initializes-shared-agent-home.sh +0 -66
  50. package/tools/tests/test-agent-project-reconcile-pr-updated-branch-noop.sh +0 -129
  51. package/tools/tests/test-audit-agent-worktrees-active-launch-skips-git-inspection.sh +0 -69
  52. package/tools/tests/test-audit-agent-worktrees-broken-worktree.sh +0 -43
  53. package/tools/tests/test-audit-agent-worktrees-pending-launch-owner.sh +0 -46
  54. package/tools/tests/test-audit-agent-worktrees-unreconciled-owner.sh +0 -79
  55. package/tools/tests/test-audit-issue-routing-managed-branch-globs.sh +0 -56
  56. package/tools/tests/test-branch-verification-guard-generated-artifacts.sh +0 -72
  57. package/tools/tests/test-branch-verification-guard-targeted-coverage.sh +0 -125
  58. package/tools/tests/test-codex-quota-manager-failure-driven-rotation.sh +0 -178
  59. package/tools/tests/test-codex-quota-wrapper.sh +0 -37
  60. package/tools/tests/test-contribution-docs.sh +0 -18
  61. package/tools/tests/test-control-plane-dashboard-runtime-smoke.sh +0 -343
  62. package/tools/tests/test-create-follow-up-issue.sh +0 -73
  63. package/tools/tests/test-dashboard-launchd-bootstrap.sh +0 -55
  64. package/tools/tests/test-flow-export-execution-env-exports-repo-id.sh +0 -30
  65. package/tools/tests/test-flow-export-github-cli-auth-env-prefers-git-credential.sh +0 -48
  66. package/tools/tests/test-flow-github-api-repo-fallback-preserves-input.sh +0 -85
  67. package/tools/tests/test-flow-github-api-repo-prefers-explicit-repository-id.sh +0 -60
  68. package/tools/tests/test-flow-github-issue-list-falls-back-to-repository-id.sh +0 -64
  69. package/tools/tests/test-flow-github-pr-list-falls-back-to-repository-id.sh +0 -77
  70. package/tools/tests/test-flow-resident-can-reuse-does-not-leak-metadata.sh +0 -52
  71. package/tools/tests/test-flow-resident-reap-stale-controllers.sh +0 -63
  72. package/tools/tests/test-flow-resolve-codex-quota-tools.sh +0 -104
  73. package/tools/tests/test-flow-runtime-doctor-profile-selection.sh +0 -27
  74. package/tools/tests/test-heartbeat-codex-pr-linked-issue-exclusion.sh +0 -79
  75. package/tools/tests/test-heartbeat-hooks-enqueue-resident-issue-for-idle-controller.sh +0 -115
  76. package/tools/tests/test-heartbeat-hooks-enqueue-resident-issue-for-live-lane-controller.sh +0 -117
  77. package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop-claude.sh +0 -96
  78. package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop-codex.sh +0 -96
  79. package/tools/tests/test-heartbeat-hooks-start-resident-issue-loop.sh +0 -96
  80. package/tools/tests/test-heartbeat-loop-auth-wait-does-not-consume-capacity.sh +0 -170
  81. package/tools/tests/test-heartbeat-loop-blocked-recovery-lane.sh +0 -201
  82. package/tools/tests/test-heartbeat-loop-blocked-recovery-vs-pr-reservation.sh +0 -201
  83. package/tools/tests/test-heartbeat-loop-idle-resident-controller-does-not-block-launches.sh +0 -160
  84. package/tools/tests/test-heartbeat-loop-pr-launch-dedup.sh +0 -133
  85. package/tools/tests/test-heartbeat-loop-provider-cooldown-suppresses-launches.sh +0 -157
  86. package/tools/tests/test-heartbeat-loop-reaps-stale-resident-controller.sh +0 -181
  87. package/tools/tests/test-heartbeat-loop-waiting-provider-resident-controller-does-not-block-launches.sh +0 -160
  88. package/tools/tests/test-heartbeat-ready-issues-blocked-recovery.sh +0 -134
  89. package/tools/tests/test-heartbeat-safe-auto-dynamic-concurrency.sh +0 -162
  90. package/tools/tests/test-heartbeat-safe-auto-no-tmux-sessions.sh +0 -136
  91. package/tools/tests/test-heartbeat-safe-auto-openclaw-skips-codex-quota.sh +0 -139
  92. package/tools/tests/test-heartbeat-safe-auto-quota-health-signal.sh +0 -119
  93. package/tools/tests/test-heartbeat-safe-auto-stale-shared-loop-pid-does-not-skip.sh +0 -140
  94. package/tools/tests/test-heartbeat-safe-auto-static-capacity-without-quota-cache.sh +0 -142
  95. package/tools/tests/test-heartbeat-safe-auto-zero-healthy-pools.sh +0 -141
  96. package/tools/tests/test-heartbeat-sync-issue-labels-empty-schedule.sh +0 -65
  97. package/tools/tests/test-heartbeat-sync-open-agent-prs-terminal-clears-running.sh +0 -179
  98. package/tools/tests/test-install-dashboard-launchd.sh +0 -78
  99. package/tools/tests/test-install-project-launchd-adds-tool-paths.sh +0 -87
  100. package/tools/tests/test-install-project-launchd.sh +0 -110
  101. package/tools/tests/test-issue-local-workspace-install-policy.sh +0 -81
  102. package/tools/tests/test-issue-publish-scope-guard-docs-signal.sh +0 -70
  103. package/tools/tests/test-issue-reconcile-hooks-success-clears-blocked.sh +0 -36
  104. package/tools/tests/test-kick-scheduler-requires-explicit-profile.sh +0 -47
  105. package/tools/tests/test-label-follow-up-issues-falls-back-to-repository-id.sh +0 -132
  106. package/tools/tests/test-manual-operator-entrypoints-require-explicit-profile.sh +0 -64
  107. package/tools/tests/test-package-funding-metadata.sh +0 -21
  108. package/tools/tests/test-package-public-metadata.sh +0 -62
  109. package/tools/tests/test-placeholder-worker-adapters.sh +0 -38
  110. package/tools/tests/test-pr-reconcile-hooks-refreshes-recurring-issue-checklist.sh +0 -110
  111. package/tools/tests/test-pr-risk-cohesive-mobile-locale-scope.sh +0 -70
  112. package/tools/tests/test-pr-risk-fix-label-semantics.sh +0 -114
  113. package/tools/tests/test-pr-risk-local-first-no-checks.sh +0 -70
  114. package/tools/tests/test-prepare-worktree-simple-repo-baseline.sh +0 -67
  115. package/tools/tests/test-profile-activate.sh +0 -33
  116. package/tools/tests/test-profile-adopt-allow-missing-repo.sh +0 -68
  117. package/tools/tests/test-profile-adopt-skip-workspace-sync-missing-file.sh +0 -61
  118. package/tools/tests/test-profile-adopt-syncs-anchor-and-workspace.sh +0 -90
  119. package/tools/tests/test-profile-smoke-collision.sh +0 -44
  120. package/tools/tests/test-profile-smoke-invalid-claude-config.sh +0 -31
  121. package/tools/tests/test-profile-smoke-invalid-provider-pool.sh +0 -68
  122. package/tools/tests/test-profile-smoke-repo-slug-mismatch.sh +0 -36
  123. package/tools/tests/test-profile-smoke.sh +0 -45
  124. package/tools/tests/test-project-init-force-and-skip-sync.sh +0 -61
  125. package/tools/tests/test-project-init-repo-slug-mismatch.sh +0 -29
  126. package/tools/tests/test-project-init.sh +0 -66
  127. package/tools/tests/test-project-launchd-bootstrap.sh +0 -66
  128. package/tools/tests/test-project-remove.sh +0 -150
  129. package/tools/tests/test-project-runtime-supervisor.sh +0 -47
  130. package/tools/tests/test-project-runtimectl-launchd.sh +0 -115
  131. package/tools/tests/test-project-runtimectl-missing-profile.sh +0 -54
  132. package/tools/tests/test-project-runtimectl-start-falls-back-to-bootstrap.sh +0 -108
  133. package/tools/tests/test-project-runtimectl-status-reports-supervisor-as-heartbeat-parent.sh +0 -95
  134. package/tools/tests/test-project-runtimectl-status-supervisor-running.sh +0 -59
  135. package/tools/tests/test-project-runtimectl-stop-cancels-pending-kick.sh +0 -85
  136. package/tools/tests/test-project-runtimectl-stop-clears-running-labels.sh +0 -78
  137. package/tools/tests/test-project-runtimectl.sh +0 -212
  138. package/tools/tests/test-provider-cooldown-state-prefers-runtime-worker-context.sh +0 -39
  139. package/tools/tests/test-provider-cooldown-state.sh +0 -59
  140. package/tools/tests/test-public-repo-docs.sh +0 -161
  141. package/tools/tests/test-reconcile-pr-worker-acp-config-routing.sh +0 -75
  142. package/tools/tests/test-render-dashboard-snapshot.sh +0 -149
  143. package/tools/tests/test-render-flow-config-demo-profile.sh +0 -36
  144. package/tools/tests/test-render-flow-config-provider-pool-fallback.sh +0 -81
  145. package/tools/tests/test-render-flow-config.sh +0 -52
  146. package/tools/tests/test-run-codex-task-claude-routing.sh +0 -125
  147. package/tools/tests/test-run-codex-task-codex-resident-routing.sh +0 -108
  148. package/tools/tests/test-run-codex-task-kilo-routing.sh +0 -98
  149. package/tools/tests/test-run-codex-task-openclaw-resident-routing.sh +0 -117
  150. package/tools/tests/test-run-codex-task-openclaw-routing.sh +0 -113
  151. package/tools/tests/test-run-codex-task-opencode-routing.sh +0 -98
  152. package/tools/tests/test-run-codex-task-provider-pool-fallback-routing.sh +0 -146
  153. package/tools/tests/test-scaffold-profile.sh +0 -108
  154. package/tools/tests/test-serve-dashboard.sh +0 -93
  155. package/tools/tests/test-start-issue-worker-blocked-context.sh +0 -129
  156. package/tools/tests/test-start-issue-worker-blocks-complete-recurring-checklist.sh +0 -189
  157. package/tools/tests/test-start-issue-worker-local-install-routing.sh +0 -157
  158. package/tools/tests/test-start-issue-worker-profile-template-routing.sh +0 -149
  159. package/tools/tests/test-start-issue-worker-recurring-resident-reuse-codex.sh +0 -212
  160. package/tools/tests/test-start-issue-worker-recurring-resident-reuse.sh +0 -219
  161. package/tools/tests/test-start-issue-worker-renders-verification-snippet.sh +0 -155
  162. package/tools/tests/test-start-issue-worker-resident-reuse-falls-back-to-new-worktree.sh +0 -199
  163. package/tools/tests/test-start-pr-fix-worker-host-blocker-context.sh +0 -275
  164. package/tools/tests/test-start-resident-issue-loop-adopts-next-recurring-issue.sh +0 -185
  165. package/tools/tests/test-start-resident-issue-loop-clears-pending-while-waiting-due.sh +0 -152
  166. package/tools/tests/test-start-resident-issue-loop-consumes-queued-lease.sh +0 -186
  167. package/tools/tests/test-start-resident-issue-loop-fails-over-provider-pool.sh +0 -212
  168. package/tools/tests/test-start-resident-issue-loop-immediate-cycles.sh +0 -148
  169. package/tools/tests/test-start-resident-issue-loop-waits-for-provider.sh +0 -194
  170. package/tools/tests/test-start-resident-issue-loop-waits-for-terminal-reconcile-status.sh +0 -198
  171. package/tools/tests/test-start-resident-issue-loop-yields-to-live-lane-controller.sh +0 -145
  172. package/tools/tests/test-sync-pr-labels-fix-lane-uses-repair-queued.sh +0 -67
  173. package/tools/tests/test-sync-recurring-issue-checklist-backfills-workflow-complete-blocker.sh +0 -70
  174. package/tools/tests/test-sync-recurring-issue-checklist.sh +0 -95
  175. package/tools/tests/test-sync-shared-agent-home-local-source-root.sh +0 -66
  176. package/tools/tests/test-sync-shared-agent-home-preserves-unrelated-workflow-catalog-skill.sh +0 -47
  177. package/tools/tests/test-test-smoke.sh +0 -86
  178. package/tools/tests/test-uninstall-project-launchd.sh +0 -37
  179. package/tools/tests/test-update-github-labels-prefers-sibling-helper.sh +0 -49
  180. package/tools/tests/test-vendored-codex-quota-claude-oauth-only.sh +0 -38
  181. package/tools/tests/test-workflow-catalog.sh +0 -43
  182. package/tools/vendor/codex-quota/README.md +0 -451
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.2",
3
+ "version": "0.1.4",
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",
@@ -1,110 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
5
- SOURCE_HTML="${ROOT_DIR}/tools/architecture/architecture-infographics.html"
6
- OUTPUT_DIR="${ROOT_DIR}/assets/architecture"
7
- PDF_OUT="${OUTPUT_DIR}/agent-control-plane-architecture.pdf"
8
- OVERVIEW_OUT="${OUTPUT_DIR}/overview-infographic.png"
9
- RUNTIME_OUT="${OUTPUT_DIR}/runtime-loop-infographic.png"
10
- LIFECYCLE_OUT="${OUTPUT_DIR}/worker-lifecycle-infographic.png"
11
- STATE_OUT="${OUTPUT_DIR}/state-dashboard-infographic.png"
12
-
13
- require_bin() {
14
- local name="$1"
15
- if ! command -v "$name" >/dev/null 2>&1; then
16
- echo "missing required dependency: $name" >&2
17
- exit 1
18
- fi
19
- }
20
-
21
- require_bin python3
22
- require_bin playwright
23
-
24
- PLAYWRIGHT_CLI="$(command -v playwright)"
25
- PLAYWRIGHT_PACKAGE_ROOT="$(
26
- python3 - "$PLAYWRIGHT_CLI" <<'PY'
27
- import os
28
- import sys
29
-
30
- print(os.path.dirname(os.path.realpath(sys.argv[1])))
31
- PY
32
- )"
33
-
34
- mkdir -p "${OUTPUT_DIR}"
35
- tmpdir="$(mktemp -d)"
36
-
37
- cleanup() {
38
- rm -rf "${tmpdir}"
39
- }
40
- trap cleanup EXIT
41
-
42
- cat >"${tmpdir}/render-architecture.js" <<'EOF'
43
- const fs = require("fs");
44
- const path = require("path");
45
- const { chromium } = require(process.env.PLAYWRIGHT_PACKAGE_ROOT);
46
-
47
- async function screenshotSection(page, selector, filename) {
48
- const element = await page.$(selector);
49
- if (!element) {
50
- throw new Error(`missing section for selector: ${selector}`);
51
- }
52
- await element.screenshot({ path: filename });
53
- }
54
-
55
- (async () => {
56
- const sourceHtml = process.env.ACP_ARCH_SOURCE_HTML;
57
- const pdfOut = process.env.ACP_ARCH_PDF_OUT;
58
- const overviewOut = process.env.ACP_ARCH_OVERVIEW_OUT;
59
- const runtimeOut = process.env.ACP_ARCH_RUNTIME_OUT;
60
- const lifecycleOut = process.env.ACP_ARCH_LIFECYCLE_OUT;
61
- const stateOut = process.env.ACP_ARCH_STATE_OUT;
62
-
63
- const browser = await chromium.launch({ headless: true });
64
- const page = await browser.newPage({
65
- viewport: { width: 1664, height: 964 },
66
- deviceScaleFactor: 1,
67
- colorScheme: "light",
68
- });
69
-
70
- await page.goto(`file://${sourceHtml}`, { waitUntil: "load" });
71
- await page.waitForTimeout(400);
72
-
73
- await screenshotSection(page, "#overview-page", overviewOut);
74
- await screenshotSection(page, "#runtime-loop-page", runtimeOut);
75
- await screenshotSection(page, "#worker-lifecycle-page", lifecycleOut);
76
- await screenshotSection(page, "#state-dashboard-page", stateOut);
77
-
78
- await page.pdf({
79
- path: pdfOut,
80
- printBackground: true,
81
- preferCSSPageSize: true,
82
- margin: {
83
- top: "0in",
84
- right: "0in",
85
- bottom: "0in",
86
- left: "0in",
87
- },
88
- });
89
-
90
- await browser.close();
91
- })().catch((error) => {
92
- console.error(error);
93
- process.exit(1);
94
- });
95
- EOF
96
-
97
- PLAYWRIGHT_PACKAGE_ROOT="${PLAYWRIGHT_PACKAGE_ROOT}" \
98
- ACP_ARCH_SOURCE_HTML="${SOURCE_HTML}" \
99
- ACP_ARCH_PDF_OUT="${PDF_OUT}" \
100
- ACP_ARCH_OVERVIEW_OUT="${OVERVIEW_OUT}" \
101
- ACP_ARCH_RUNTIME_OUT="${RUNTIME_OUT}" \
102
- ACP_ARCH_LIFECYCLE_OUT="${LIFECYCLE_OUT}" \
103
- ACP_ARCH_STATE_OUT="${STATE_OUT}" \
104
- node "${tmpdir}/render-architecture.js"
105
-
106
- echo "ARCHITECTURE_PDF=${PDF_OUT}"
107
- echo "ARCHITECTURE_OVERVIEW_PNG=${OVERVIEW_OUT}"
108
- echo "ARCHITECTURE_RUNTIME_PNG=${RUNTIME_OUT}"
109
- echo "ARCHITECTURE_LIFECYCLE_PNG=${LIFECYCLE_OUT}"
110
- echo "ARCHITECTURE_STATE_PNG=${STATE_OUT}"