create-walle 0.10.2 → 0.10.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 (488) hide show
  1. package/README.md +3 -1
  2. package/lib/release-schema.json +2 -2
  3. package/lib/runtime-closure-policy.json +8 -7
  4. package/package.json +1 -1
  5. package/template/coding-task-manager/api-prompts.js +45 -7
  6. package/template/coding-task-manager/bin/claude-hook.js +55 -1
  7. package/template/coding-task-manager/bin/ctm-relay-client.js +35 -12
  8. package/template/coding-task-manager/bin/land.js +2069 -0
  9. package/template/coding-task-manager/bin/memory-census.js +4 -2
  10. package/template/coding-task-manager/bin/memory-sync-verify.js +151 -0
  11. package/template/coding-task-manager/bin/probe-refusal-scope.js +530 -0
  12. package/template/coding-task-manager/db.js +156 -32
  13. package/template/coding-task-manager/lib/acp/acp-client.js +383 -0
  14. package/template/coding-task-manager/lib/acp/dsh-context-truth.js +176 -0
  15. package/template/coding-task-manager/lib/acp/dsh-launch-model.js +302 -0
  16. package/template/coding-task-manager/lib/acp/dsh-mcp-servers.js +194 -0
  17. package/template/coding-task-manager/lib/acp/dsh-prompt-lane.js +616 -0
  18. package/template/coding-task-manager/lib/acp/dsh-route-patch.js +308 -0
  19. package/template/coding-task-manager/lib/acp/dsh-session-runtime.js +522 -0
  20. package/template/coding-task-manager/lib/acp/dsh-transcript.js +395 -0
  21. package/template/coding-task-manager/lib/acp/dsh-usage-log.js +162 -0
  22. package/template/coding-task-manager/lib/acp/ndjson-stdio-transport.js +225 -0
  23. package/template/coding-task-manager/lib/adopted-host-redial.js +47 -11
  24. package/template/coding-task-manager/lib/adopted-launch-environment.js +123 -0
  25. package/template/coding-task-manager/lib/agent-auto-update-runtime.js +0 -3
  26. package/template/coding-task-manager/lib/agent-auto-update.js +26 -10
  27. package/template/coding-task-manager/lib/agent-bundle/build.js +657 -0
  28. package/template/coding-task-manager/lib/agent-bundle/codex-install.js +558 -0
  29. package/template/coding-task-manager/lib/agent-bundle/drift.js +120 -0
  30. package/template/coding-task-manager/lib/agent-bundle/gc.js +522 -0
  31. package/template/coding-task-manager/lib/agent-bundle/inventory.js +346 -0
  32. package/template/coding-task-manager/lib/agent-bundle/push.js +717 -0
  33. package/template/coding-task-manager/lib/agent-bundle/skill-transfer.js +724 -0
  34. package/template/coding-task-manager/lib/agent-bundle/spawn.js +119 -0
  35. package/template/coding-task-manager/lib/agent-bundle/store.js +568 -0
  36. package/template/coding-task-manager/lib/agent-capabilities.js +142 -12
  37. package/template/coding-task-manager/lib/agent-cli-cache.js +30 -5
  38. package/template/coding-task-manager/lib/agent-definitions.js +19 -0
  39. package/template/coding-task-manager/lib/agent-hooks-codex-toml.js +28 -16
  40. package/template/coding-task-manager/lib/agent-hooks-codex-workspace.js +9 -1
  41. package/template/coding-task-manager/lib/agent-hooks-installer.js +23 -8
  42. package/template/coding-task-manager/lib/agent-hooks-prune.js +37 -11
  43. package/template/coding-task-manager/lib/agent-hooks-shim.js +105 -0
  44. package/template/coding-task-manager/lib/agent-presets.js +84 -1
  45. package/template/coding-task-manager/lib/agent-upgrade-jobs.js +12 -1
  46. package/template/coding-task-manager/lib/agent-version-service.js +74 -11
  47. package/template/coding-task-manager/lib/all-sessions-query.js +6 -2
  48. package/template/coding-task-manager/lib/api/agent-bundles.js +436 -0
  49. package/template/coding-task-manager/lib/api/backup-retention.js +83 -0
  50. package/template/coding-task-manager/lib/api/conversation-sync-routes.js +124 -0
  51. package/template/coding-task-manager/lib/api/diagnostics.js +549 -69
  52. package/template/coding-task-manager/lib/api/fleet.js +118 -0
  53. package/template/coding-task-manager/lib/api/hub-bridge.js +243 -0
  54. package/template/coding-task-manager/lib/api/ingress.js +9 -1
  55. package/template/coding-task-manager/lib/api/landing.js +403 -0
  56. package/template/coding-task-manager/lib/api/machines.js +691 -2
  57. package/template/coding-task-manager/lib/api/memory-sync-routes.js +335 -0
  58. package/template/coding-task-manager/lib/api/misc-ops.js +8 -3
  59. package/template/coding-task-manager/lib/api/remote-sessions.js +1326 -0
  60. package/template/coding-task-manager/lib/api/session-attach.js +623 -0
  61. package/template/coding-task-manager/lib/api/session-messages.js +1384 -0
  62. package/template/coding-task-manager/lib/api/sessions.js +77 -51
  63. package/template/coding-task-manager/lib/api/setup-network.js +544 -16
  64. package/template/coding-task-manager/lib/api/setup-wizard-handlers.js +884 -0
  65. package/template/coding-task-manager/lib/api/setup.js +829 -10
  66. package/template/coding-task-manager/lib/api/walle-bridge.js +9 -1
  67. package/template/coding-task-manager/lib/attachment-reconciler-wiring.js +5 -2
  68. package/template/coding-task-manager/lib/auth-rate-limit.js +23 -0
  69. package/template/coding-task-manager/lib/auth-rules.js +292 -21
  70. package/template/coding-task-manager/lib/background-stop.js +369 -0
  71. package/template/coding-task-manager/lib/best-of-attempts.js +4 -1
  72. package/template/coding-task-manager/lib/best-of-launch-plan.js +52 -0
  73. package/template/coding-task-manager/lib/box-keep-awake.js +89 -0
  74. package/template/coding-task-manager/lib/broadcast-reason-stats.js +152 -0
  75. package/template/coding-task-manager/lib/broadcast-serialize.js +5 -0
  76. package/template/coding-task-manager/lib/build-info.js +4 -2
  77. package/template/coding-task-manager/lib/claude-model-truth.js +13 -2
  78. package/template/coding-task-manager/lib/claude-transcript-terminal-renderer.js +17 -13
  79. package/template/coding-task-manager/lib/client-diag-handler.js +64 -2
  80. package/template/coding-task-manager/lib/codex-app-server-client.js +14 -16
  81. package/template/coding-task-manager/lib/codex-configuration-history.js +55 -0
  82. package/template/coding-task-manager/lib/codex-harness-qualification.js +43 -20
  83. package/template/coding-task-manager/lib/codex-rollout-history.js +743 -0
  84. package/template/coding-task-manager/lib/coding-agent-models.js +27 -12
  85. package/template/coding-task-manager/lib/coding-cli-skill-installer.js +44 -14
  86. package/template/coding-task-manager/lib/coding-session-model-adapters.js +43 -2
  87. package/template/coding-task-manager/lib/coding-session-model-owner.js +1 -1
  88. package/template/coding-task-manager/lib/coding-session-model-public.js +7 -1
  89. package/template/coding-task-manager/lib/coding-session-model-runtime.js +23 -4
  90. package/template/coding-task-manager/lib/coding-session-model-scan.js +31 -0
  91. package/template/coding-task-manager/lib/coding-session-model-state.js +7 -0
  92. package/template/coding-task-manager/lib/conversation-freshness-diagnostics.js +280 -0
  93. package/template/coding-task-manager/lib/conversation-item-clock.js +261 -0
  94. package/template/coding-task-manager/lib/conversation-source-registry.js +5 -0
  95. package/template/coding-task-manager/lib/conversation-store-v2.js +44 -0
  96. package/template/coding-task-manager/lib/conversation-sync.js +355 -0
  97. package/template/coding-task-manager/lib/create/resolve-launch-params.js +96 -12
  98. package/template/coding-task-manager/lib/create/worker-role-profile.js +4 -4
  99. package/template/coding-task-manager/lib/create-intent-lease.js +51 -0
  100. package/template/coding-task-manager/lib/ctm-session-context-api.js +97 -6
  101. package/template/coding-task-manager/lib/db-domain-tables.js +1 -0
  102. package/template/coding-task-manager/lib/db-domains.js +1 -0
  103. package/template/coding-task-manager/lib/db-owner-dispatch-probe.js +32 -0
  104. package/template/coding-task-manager/lib/db-owner-request-counts.js +178 -0
  105. package/template/coding-task-manager/lib/db-owner-worker-client.js +68 -14
  106. package/template/coding-task-manager/lib/db-schema-source-files.js +5 -0
  107. package/template/coding-task-manager/lib/deferred-restore-attempt.js +6 -2
  108. package/template/coding-task-manager/lib/feature-manifest.js +4 -0
  109. package/template/coding-task-manager/lib/fleet-conversation-route.js +97 -0
  110. package/template/coding-task-manager/lib/fleet-fanout.js +78 -0
  111. package/template/coding-task-manager/lib/fleet-hub.js +762 -0
  112. package/template/coding-task-manager/lib/fleet-origin-follower.js +252 -0
  113. package/template/coding-task-manager/lib/fleet-resolver-effects.js +147 -0
  114. package/template/coding-task-manager/lib/fleet-resolver-tick.js +148 -0
  115. package/template/coding-task-manager/lib/fleet-resolver.js +454 -0
  116. package/template/coding-task-manager/lib/fleet-session-list-routes.js +114 -0
  117. package/template/coding-task-manager/lib/fleet-session-lists.js +672 -0
  118. package/template/coding-task-manager/lib/fleet-update.js +798 -0
  119. package/template/coding-task-manager/lib/fs-watcher-root-watch.js +141 -0
  120. package/template/coding-task-manager/lib/fs-watcher.js +58 -42
  121. package/template/coding-task-manager/lib/harness-history-transition.js +50 -9
  122. package/template/coding-task-manager/lib/headless-term-service.js +84 -8
  123. package/template/coding-task-manager/lib/hermetic-agent-launch-guard.js +10 -0
  124. package/template/coding-task-manager/lib/history-transition-read-op.js +175 -8
  125. package/template/coding-task-manager/lib/host-cpu-categories.js +310 -0
  126. package/template/coding-task-manager/lib/host-fseventsd-sampler.js +399 -0
  127. package/template/coding-task-manager/lib/host-handoff.js +16 -19
  128. package/template/coding-task-manager/lib/host-stats.js +150 -3
  129. package/template/coding-task-manager/lib/hosted-sync-worker.js +43 -320
  130. package/template/coding-task-manager/lib/hub-bridge-access.js +798 -0
  131. package/template/coding-task-manager/lib/hub-bridge-box.js +409 -0
  132. package/template/coding-task-manager/lib/hub-bridge-mcp.js +380 -0
  133. package/template/coding-task-manager/lib/hub-bridge-mesh.js +313 -0
  134. package/template/coding-task-manager/lib/hub-bridge-ops.js +721 -0
  135. package/template/coding-task-manager/lib/hub-bridge.js +398 -0
  136. package/template/coding-task-manager/lib/idle-notify.js +491 -0
  137. package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +8 -0
  138. package/template/coding-task-manager/lib/instance-manifest.js +5 -1
  139. package/template/coding-task-manager/lib/instance-sync-status.js +18 -8
  140. package/template/coding-task-manager/lib/landing/agent-rule.txt +1 -0
  141. package/template/coding-task-manager/lib/landing/batch-runner.js +366 -0
  142. package/template/coding-task-manager/lib/landing/config.js +536 -0
  143. package/template/coding-task-manager/lib/landing/ctm-view.js +256 -0
  144. package/template/coding-task-manager/lib/landing/gate.js +360 -0
  145. package/template/coding-task-manager/lib/landing/git.js +344 -0
  146. package/template/coding-task-manager/lib/landing/lease.js +247 -0
  147. package/template/coding-task-manager/lib/landing/merge-delta.js +380 -0
  148. package/template/coding-task-manager/lib/landing/proof.js +223 -0
  149. package/template/coding-task-manager/lib/landing/queue.js +998 -0
  150. package/template/coding-task-manager/lib/landing/radar.js +489 -0
  151. package/template/coding-task-manager/lib/landing/resolve-cli.js +424 -0
  152. package/template/coding-task-manager/lib/landing/state.js +263 -0
  153. package/template/coding-task-manager/lib/landing/stop.js +49 -0
  154. package/template/coding-task-manager/lib/landing/sync-cli.js +475 -0
  155. package/template/coding-task-manager/lib/landing/sync.js +456 -0
  156. package/template/coding-task-manager/lib/landing/train.js +885 -0
  157. package/template/coding-task-manager/lib/landing/window.js +57 -0
  158. package/template/coding-task-manager/lib/launch-environment.js +121 -0
  159. package/template/coding-task-manager/lib/launch-model-override.js +36 -10
  160. package/template/coding-task-manager/lib/launch-plan-pool.js +2 -1
  161. package/template/coding-task-manager/lib/launch-plan-warm-cache.js +6 -0
  162. package/template/coding-task-manager/lib/lead-model-observer.js +57 -1
  163. package/template/coding-task-manager/lib/lead-model-options.js +160 -3
  164. package/template/coding-task-manager/lib/lead-model-restore-reconciliation.js +106 -24
  165. package/template/coding-task-manager/lib/lead-model-switch-runtime.js +352 -68
  166. package/template/coding-task-manager/lib/lead-model-switch.js +114 -10
  167. package/template/coding-task-manager/lib/lead-model-transition-runtime.js +14 -2
  168. package/template/coding-task-manager/lib/lead-model-transition-store.js +24 -6
  169. package/template/coding-task-manager/lib/lead-route-preflight.js +179 -0
  170. package/template/coding-task-manager/lib/lead-switch-history-mode.js +110 -0
  171. package/template/coding-task-manager/lib/lead-wire-agent.js +7 -0
  172. package/template/coding-task-manager/lib/lifecycle-wake-experiment.js +9 -2
  173. package/template/coding-task-manager/lib/live-defer-announce.js +45 -0
  174. package/template/coding-task-manager/lib/machine-registry.js +750 -5
  175. package/template/coding-task-manager/lib/macos-capabilities.js +32 -15
  176. package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +433 -53
  177. package/template/coding-task-manager/lib/mainloop-observability.js +14 -9
  178. package/template/coding-task-manager/lib/media-draft-owner.js +16 -3
  179. package/template/coding-task-manager/lib/memory-join.js +337 -0
  180. package/template/coding-task-manager/lib/memory-sync-verify.js +393 -0
  181. package/template/coding-task-manager/lib/memory-sync.js +565 -0
  182. package/template/coding-task-manager/lib/migrations/v57-delegate-pref-mirror-opencode.js +68 -0
  183. package/template/coding-task-manager/lib/migrations/v58-subagent-launch-native-subagents.js +42 -0
  184. package/template/coding-task-manager/lib/migrations/v59-coding-session-model-state-dsh.js +73 -0
  185. package/template/coding-task-manager/lib/migrations/v60-delegate-pref-mirror-dsh.js +78 -0
  186. package/template/coding-task-manager/lib/migrations/v61-conversation-item-clock.js +71 -0
  187. package/template/coding-task-manager/lib/model-availability.js +727 -0
  188. package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +4 -6
  189. package/template/coding-task-manager/lib/model-host-receipt.js +6 -3
  190. package/template/coding-task-manager/lib/model-id-rule.js +19 -0
  191. package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
  192. package/template/coding-task-manager/lib/model-proxy/auto-mode-review.js +110 -23
  193. package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
  194. package/template/coding-task-manager/lib/model-proxy/generation-probe.js +60 -17
  195. package/template/coding-task-manager/lib/model-proxy/historical-tool-declarations.js +37 -0
  196. package/template/coding-task-manager/lib/model-proxy/lead-request-validation.js +1 -0
  197. package/template/coding-task-manager/lib/model-proxy/lead-token-admin.js +130 -0
  198. package/template/coding-task-manager/lib/model-proxy/refusal-scope.js +151 -0
  199. package/template/coding-task-manager/lib/model-proxy/request-budget-store.js +11 -2
  200. package/template/coding-task-manager/lib/model-proxy/tokens.js +69 -5
  201. package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
  202. package/template/coding-task-manager/lib/native-agent-model-args.js +25 -22
  203. package/template/coding-task-manager/lib/native-history-control.js +6 -4
  204. package/template/coding-task-manager/lib/native-lead-settings-client.js +44 -4
  205. package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +173 -36
  206. package/template/coding-task-manager/lib/online-backup-worker-client.js +2 -0
  207. package/template/coding-task-manager/lib/opencode-cli.js +480 -0
  208. package/template/coding-task-manager/lib/opencode-launch-model.js +471 -0
  209. package/template/coding-task-manager/lib/opencode-v1-plugin/model-carrier.mjs +35 -0
  210. package/template/coding-task-manager/lib/opencode-v2-plugin/server.js +118 -0
  211. package/template/coding-task-manager/lib/owner-action-manifest-data.json +1 -1
  212. package/template/coding-task-manager/lib/park-composer-draft.js +54 -0
  213. package/template/coding-task-manager/lib/park-fresh-relaunch.js +41 -10
  214. package/template/coding-task-manager/lib/perf-phase-trace.js +72 -0
  215. package/template/coding-task-manager/lib/perf-summary.js +7 -0
  216. package/template/coding-task-manager/lib/perf-tracker.js +63 -2
  217. package/template/coding-task-manager/lib/project-config.js +7 -0
  218. package/template/coding-task-manager/lib/prompt-purge-v2.js +52 -1
  219. package/template/coding-task-manager/lib/provider-login-broker.js +72 -15
  220. package/template/coding-task-manager/lib/provider-login-tab.js +10 -2
  221. package/template/coding-task-manager/lib/read-pool-client.js +89 -2
  222. package/template/coding-task-manager/lib/read-pool-worker-handle.js +29 -4
  223. package/template/coding-task-manager/lib/recent-catalog-query.js +15 -0
  224. package/template/coding-task-manager/lib/recent-session-catalog-response.js +67 -10
  225. package/template/coding-task-manager/lib/recovery-evidence.js +26 -11
  226. package/template/coding-task-manager/lib/relay-client.js +57 -3
  227. package/template/coding-task-manager/lib/relink-audit.js +34 -5
  228. package/template/coding-task-manager/lib/remote-machine-catalog.js +321 -26
  229. package/template/coding-task-manager/lib/remote-machine-create.js +576 -36
  230. package/template/coding-task-manager/lib/remote-machine-link.js +546 -59
  231. package/template/coding-task-manager/lib/remote-machine-proxy.js +143 -9
  232. package/template/coding-task-manager/lib/remote-session-forward.js +65 -7
  233. package/template/coding-task-manager/lib/remote-terminal-target.js +9 -2
  234. package/template/coding-task-manager/lib/restart-guard.js +18 -6
  235. package/template/coding-task-manager/lib/restore-seed-modes.js +87 -0
  236. package/template/coding-task-manager/lib/restored-scrollback-hydration-runtime.js +22 -2
  237. package/template/coding-task-manager/lib/resume-admission.js +17 -3
  238. package/template/coding-task-manager/lib/review-repo-registry.js +58 -19
  239. package/template/coding-task-manager/lib/root-delivery-runtime.js +2 -2
  240. package/template/coding-task-manager/lib/root-prompt-flow.js +1 -1
  241. package/template/coding-task-manager/lib/runtime-contract.js +3 -1
  242. package/template/coding-task-manager/lib/schema-indexes.js +1 -0
  243. package/template/coding-task-manager/lib/session-context-locator.js +742 -0
  244. package/template/coding-task-manager/lib/session-create/attachment.js +2 -2
  245. package/template/coding-task-manager/lib/session-create/create-phases.js +118 -0
  246. package/template/coding-task-manager/lib/session-create/delegate-worker.js +86 -16
  247. package/template/coding-task-manager/lib/session-create/dsh-session.js +930 -0
  248. package/template/coding-task-manager/lib/session-create/lead-route-launch.js +186 -13
  249. package/template/coding-task-manager/lib/session-create/native-lead-lifetime.js +55 -3
  250. package/template/coding-task-manager/lib/session-create/opencode-lead-provider.js +50 -0
  251. package/template/coding-task-manager/lib/session-create/prepared.js +162 -38
  252. package/template/coding-task-manager/lib/session-create/provider-process.js +160 -28
  253. package/template/coding-task-manager/lib/session-create/response.js +7 -7
  254. package/template/coding-task-manager/lib/session-create/structured-session.js +27 -0
  255. package/template/coding-task-manager/lib/session-create/subagent-pick-table.js +3 -1
  256. package/template/coding-task-manager/lib/session-create/subagent-pick.js +458 -9
  257. package/template/coding-task-manager/lib/session-create/transcript.js +1 -1
  258. package/template/coding-task-manager/lib/session-create/worker-budget-setup.js +21 -2
  259. package/template/coding-task-manager/lib/session-create/worker-continuation.js +7 -1
  260. package/template/coding-task-manager/lib/session-create/worker-launch-sandbox.js +39 -10
  261. package/template/coding-task-manager/lib/session-create/worker-observability.js +2 -2
  262. package/template/coding-task-manager/lib/session-empty.js +13 -10
  263. package/template/coding-task-manager/lib/session-host-manager.js +60 -0
  264. package/template/coding-task-manager/lib/session-jobs.js +3 -1
  265. package/template/coding-task-manager/lib/session-lifecycle.js +10 -4
  266. package/template/coding-task-manager/lib/session-model-action-preconditions.js +6 -2
  267. package/template/coding-task-manager/lib/session-model-intent-contract.js +11 -8
  268. package/template/coding-task-manager/lib/session-park.js +738 -0
  269. package/template/coding-task-manager/lib/session-path-grant.js +13 -4
  270. package/template/coding-task-manager/lib/session-status-ledger.js +106 -4
  271. package/template/coding-task-manager/lib/session-transport.js +17 -0
  272. package/template/coding-task-manager/lib/session-workspace-projection.js +54 -5
  273. package/template/coding-task-manager/lib/session-workspace-tracking-runtime.js +213 -1
  274. package/template/coding-task-manager/lib/skill-autocomplete-query.js +120 -3
  275. package/template/coding-task-manager/lib/skill-autocomplete.js +15 -1
  276. package/template/coding-task-manager/lib/sqlite-file-set.js +39 -0
  277. package/template/coding-task-manager/lib/standup-search-adapter.js +16 -2
  278. package/template/coding-task-manager/lib/state-sync/client-ledger.js +12 -0
  279. package/template/coding-task-manager/lib/state-sync/frame-emitter.js +319 -100
  280. package/template/coding-task-manager/lib/storage/coding-session-model-store.js +4 -1
  281. package/template/coding-task-manager/lib/storage/delegate-pref-mirror-store.js +32 -9
  282. package/template/coding-task-manager/lib/structured-session-boot-sweep.js +235 -0
  283. package/template/coding-task-manager/lib/submission-ack.js +90 -2
  284. package/template/coding-task-manager/lib/systemd-scope.js +115 -0
  285. package/template/coding-task-manager/lib/telemetry-receiver.js +39 -0
  286. package/template/coding-task-manager/lib/terminal-attach-geometry.js +985 -0
  287. package/template/coding-task-manager/lib/terminal-bootstrap.js +30 -0
  288. package/template/coding-task-manager/lib/terminal-draft-operation.js +357 -0
  289. package/template/coding-task-manager/lib/terminal-input-admission.js +17 -1
  290. package/template/coding-task-manager/lib/terminal-input-prepared.js +563 -0
  291. package/template/coding-task-manager/lib/transcript-cache-temps.js +171 -0
  292. package/template/coding-task-manager/lib/transcript-frame-cache.js +8 -3
  293. package/template/coding-task-manager/lib/transcript-source.js +36 -4
  294. package/template/coding-task-manager/lib/transcript-store.js +9 -0
  295. package/template/coding-task-manager/lib/typing-interaction-trace.js +106 -3
  296. package/template/coding-task-manager/lib/usage-ledger/backfill.js +40 -5
  297. package/template/coding-task-manager/lib/usage-ledger/client.js +4 -1
  298. package/template/coding-task-manager/lib/usage-ledger/dsh-parse.js +152 -0
  299. package/template/coding-task-manager/lib/usage-ledger/dsh-read.js +73 -0
  300. package/template/coding-task-manager/lib/usage-ledger/ingest.js +34 -6
  301. package/template/coding-task-manager/lib/usage-ledger/live.js +35 -8
  302. package/template/coding-task-manager/lib/usage-ledger/read.js +29 -20
  303. package/template/coding-task-manager/lib/usage-ledger/schema.js +61 -1
  304. package/template/coding-task-manager/lib/usage-ledger/task-coordinator.js +4 -2
  305. package/template/coding-task-manager/lib/wake-skip-reasons.js +1 -0
  306. package/template/coding-task-manager/lib/walle/turn.js +1 -1
  307. package/template/coding-task-manager/lib/walle-history-serve.js +838 -0
  308. package/template/coding-task-manager/lib/walle-kill-authority.js +2 -2
  309. package/template/coding-task-manager/lib/walle-log-rotation.js +164 -1
  310. package/template/coding-task-manager/lib/walle-mcp-auto-config.js +56 -2
  311. package/template/coding-task-manager/lib/walle-row-history.js +495 -0
  312. package/template/coding-task-manager/lib/walle-supervisor.js +33 -14
  313. package/template/coding-task-manager/lib/watch-worker-client.js +203 -0
  314. package/template/coding-task-manager/lib/worker-terminal-output.js +5 -2
  315. package/template/coding-task-manager/lib/ws/hub-bridge.js +35 -0
  316. package/template/coding-task-manager/lib/ws/lifecycle.js +6 -11
  317. package/template/coding-task-manager/lib/ws/misc.js +1 -1
  318. package/template/coding-task-manager/lib/ws/state-sync.js +106 -4
  319. package/template/coding-task-manager/lib/ws/terminal-attach.js +91 -0
  320. package/template/coding-task-manager/oauth-proxy.js +112 -115
  321. package/template/coding-task-manager/package-lock.json +10 -94
  322. package/template/coding-task-manager/package.json +4 -3
  323. package/template/coding-task-manager/protocol/message-table.json +10 -0
  324. package/template/coding-task-manager/protocol/messages.js +10 -0
  325. package/template/coding-task-manager/protocol/ws-schema.json +20 -0
  326. package/template/coding-task-manager/protocol/ws-schema.md +2 -0
  327. package/template/coding-task-manager/public/css/app.css +170 -0
  328. package/template/coding-task-manager/public/css/walle-session.css +4 -0
  329. package/template/coding-task-manager/public/index.html +473 -243
  330. package/template/coding-task-manager/public/js/agent-command.js +86 -0
  331. package/template/coding-task-manager/public/js/app/backups-panel.js +111 -5
  332. package/template/coding-task-manager/public/js/app/coding-agent-upgrade.js +103 -4
  333. package/template/coding-task-manager/public/js/app/machines-page.js +1328 -8
  334. package/template/coding-task-manager/public/js/app/models-tab.js +113 -38
  335. package/template/coding-task-manager/public/js/app/restart-banner.js +53 -0
  336. package/template/coding-task-manager/public/js/app/worktrees-panel.js +184 -2
  337. package/template/coding-task-manager/public/js/background-work.js +196 -27
  338. package/template/coding-task-manager/public/js/coding-session-model-presentation.js +138 -10
  339. package/template/coding-task-manager/public/js/core/active-session-groups.js +179 -0
  340. package/template/coding-task-manager/public/js/core/pending-create.js +238 -0
  341. package/template/coding-task-manager/public/js/core/recent-machine-source.js +228 -0
  342. package/template/coding-task-manager/public/js/core/session-commands.js +180 -14
  343. package/template/coding-task-manager/public/js/dashboard-client-function-order.json +8 -0
  344. package/template/coding-task-manager/public/js/document-review-links.js +9 -1
  345. package/template/coding-task-manager/public/js/file-resolution-client.js +52 -1
  346. package/template/coding-task-manager/public/js/history-app-seam.js +22 -2
  347. package/template/coding-task-manager/public/js/machine-pool-shared.js +558 -3
  348. package/template/coding-task-manager/public/js/message-renderer.js +4 -3
  349. package/template/coding-task-manager/public/js/model-picker.js +3 -1
  350. package/template/coding-task-manager/public/js/native-subagent-review.js +15 -2
  351. package/template/coding-task-manager/public/js/new-session-agent-grid.js +346 -0
  352. package/template/coding-task-manager/public/js/pasted-content.js +236 -0
  353. package/template/coding-task-manager/public/js/pe-private-content-lifetime.js +39 -7
  354. package/template/coding-task-manager/public/js/private-model-lifetime.js +6 -2
  355. package/template/coding-task-manager/public/js/prompts.js +11 -10
  356. package/template/coding-task-manager/public/js/protocol.js +10 -0
  357. package/template/coding-task-manager/public/js/restart-banner-policy.js +29 -1
  358. package/template/coding-task-manager/public/js/session-model-picker.js +447 -57
  359. package/template/coding-task-manager/public/js/session-search-utils.js +18 -2
  360. package/template/coding-task-manager/public/js/session-usage-view.js +2 -1
  361. package/template/coding-task-manager/public/js/session-workspace-tracking.js +51 -1
  362. package/template/coding-task-manager/public/js/state-sync-client.js +10 -1
  363. package/template/coding-task-manager/public/js/stream-view.js +6 -5
  364. package/template/coding-task-manager/public/js/terminal/conv-only-session.js +287 -0
  365. package/template/coding-task-manager/public/js/terminal/runtime-09.js +2 -0
  366. package/template/coding-task-manager/public/js/terminal/runtime-10.js +5 -5
  367. package/template/coding-task-manager/public/js/terminal/runtime-12.js +8 -1
  368. package/template/coding-task-manager/public/js/terminal/runtime-16.js +4 -0
  369. package/template/coding-task-manager/public/js/terminal/runtime-20.js +1 -1
  370. package/template/coding-task-manager/public/js/terminal-attachment-draft.js +67 -13
  371. package/template/coding-task-manager/public/js/terminal-image-orchestration.js +52 -6
  372. package/template/coding-task-manager/public/js/terminal-wheel.js +23 -3
  373. package/template/coding-task-manager/public/js/walle/chat-conversation.js +4 -4
  374. package/template/coding-task-manager/public/js/walle.js +6 -0
  375. package/template/coding-task-manager/public/js/ws/live-dispatch.js +10 -0
  376. package/template/coding-task-manager/public/m/app.css +140 -0
  377. package/template/coding-task-manager/public/m/app.js +507 -68
  378. package/template/coding-task-manager/public/m/index.html +17 -0
  379. package/template/coding-task-manager/public/m/machines.js +838 -4
  380. package/template/coding-task-manager/public/m/private-api.js +31 -1
  381. package/template/coding-task-manager/public/usage.html +1 -0
  382. package/template/coding-task-manager/scripts/dev-route-fixture/synthetic-route-upstream.js +104 -0
  383. package/template/coding-task-manager/scripts/dev-route-fixture.js +339 -0
  384. package/template/coding-task-manager/scripts/opencode-lead-route-probe.js +195 -0
  385. package/template/coding-task-manager/scripts/opencode-v2-contract-probe/fake-anthropic.js +168 -0
  386. package/template/coding-task-manager/scripts/opencode-v2-contract-probe/probe-plugin/server.js +95 -0
  387. package/template/coding-task-manager/scripts/opencode-v2-contract-probe.js +673 -0
  388. package/template/coding-task-manager/server.js +1950 -11679
  389. package/template/coding-task-manager/session-integrity.js +64 -4
  390. package/template/coding-task-manager/workers/db-owner-worker.js +19 -2
  391. package/template/coding-task-manager/workers/online-backup-worker.js +1 -0
  392. package/template/coding-task-manager/workers/read-pool-model-collector.js +51 -3
  393. package/template/coding-task-manager/workers/read-pool-worker.js +76 -6
  394. package/template/coding-task-manager/workers/session-host-pool-process.js +6 -6
  395. package/template/coding-task-manager/workers/session-host-process.js +5 -5
  396. package/template/coding-task-manager/workers/watch-worker.js +105 -0
  397. package/template/coding-task-manager/workers/worktree-worker.js +168 -33
  398. package/template/ctm-relay/lib/protocol.js +14 -1
  399. package/template/package-lock.json +2 -2
  400. package/template/package.json +1 -1
  401. package/template/shared/backup-retention.js +82 -0
  402. package/template/shared/conversation-admission.js +2 -0
  403. package/template/shared/conversation-provenance.js +3 -7
  404. package/template/shared/conversation-storage-v2-contract.js +5 -0
  405. package/template/shared/linked-worktree.js +51 -0
  406. package/template/shared/macos-fda-target.js +57 -0
  407. package/template/shared/orphan-parents.js +55 -0
  408. package/template/shared/release-schema.json +2 -2
  409. package/template/shared/storage/contract.js +3 -0
  410. package/template/wall-e/adapters/imessage.js +12 -0
  411. package/template/wall-e/agent.js +71 -4
  412. package/template/wall-e/api-walle.js +21 -1
  413. package/template/wall-e/bin/train-gemma-e4b-tooluse.js +2 -2
  414. package/template/wall-e/bin/walle-mcp-stdio.js +5 -5
  415. package/template/wall-e/brain/brain-join.js +859 -0
  416. package/template/wall-e/brain/brain-sync-membership.js +20 -0
  417. package/template/wall-e/brain/brain-sync-store.js +131 -77
  418. package/template/wall-e/brain/brain-sync-v2-schema.js +203 -0
  419. package/template/wall-e/brain/brain-sync-v2.js +578 -0
  420. package/template/wall-e/brain/delegate-model-prefs-store.js +13 -4
  421. package/template/wall-e/brain/deterministic-id.js +51 -0
  422. package/template/wall-e/brain/memories.js +2 -2
  423. package/template/wall-e/brain/migrations.js +40 -6
  424. package/template/wall-e/brain/model-identity-migration.js +1 -1
  425. package/template/wall-e/brain/owner-call-whitelist.js +23 -7
  426. package/template/wall-e/brain/schema.js +45 -85
  427. package/template/wall-e/brain.js +171 -35
  428. package/template/wall-e/chat.js +3 -3
  429. package/template/wall-e/coding/agent-shell-env.js +7 -2
  430. package/template/wall-e/coding/prompt-bundle.js +66 -21
  431. package/template/wall-e/config/main-loop-exemptions.json +2 -2
  432. package/template/wall-e/http/admin-api.js +37 -0
  433. package/template/wall-e/http/gws-api.js +4 -0
  434. package/template/wall-e/http/helper-rules-api.js +7 -1
  435. package/template/wall-e/http/model-admin.js +8 -1
  436. package/template/wall-e/http/model-delegate-pref-admin.js +3 -3
  437. package/template/wall-e/http/model-proxy-providers.js +174 -1
  438. package/template/wall-e/http/model-route-health.js +157 -0
  439. package/template/wall-e/http/sync-api.js +278 -464
  440. package/template/wall-e/jobs/job-runner.js +28 -1
  441. package/template/wall-e/lib/brain-owner-op-priority.js +9 -1
  442. package/template/wall-e/lib/ctm-data-dir.js +26 -0
  443. package/template/wall-e/lib/derivation-home.js +147 -0
  444. package/template/wall-e/lib/helper-rules/evaluate.js +50 -9
  445. package/template/wall-e/lib/helper-rules/grade.js +51 -8
  446. package/template/wall-e/lib/helper-rules/lead-call-hook.js +38 -5
  447. package/template/wall-e/lib/helper-rules/store.js +4 -2
  448. package/template/wall-e/lib/landing-mcp-tools.js +170 -0
  449. package/template/wall-e/lib/mcp-client-config.js +133 -14
  450. package/template/wall-e/lib/mcp-gateway-migrate.js +28 -64
  451. package/template/wall-e/lib/mcp-integration.js +58 -13
  452. package/template/wall-e/lib/parent-death.js +7 -1
  453. package/template/wall-e/lib/scheduler-worker-jobs.js +6 -4
  454. package/template/wall-e/llm/delegate-lead-prefs.js +16 -1
  455. package/template/wall-e/llm/delegate-vendor-default.js +12 -5
  456. package/template/wall-e/llm/model-roles.js +1 -1
  457. package/template/wall-e/llm/native-delegate-selection.js +56 -8
  458. package/template/wall-e/llm/worker-delegate-args.js +72 -7
  459. package/template/wall-e/llm/worker-mcp-routing.js +66 -17
  460. package/template/wall-e/loops/brain-custom-writes.js +19 -0
  461. package/template/wall-e/loops/ingest.js +17 -1
  462. package/template/wall-e/loops/think.js +19 -4
  463. package/template/wall-e/mcp-contract.js +222 -6
  464. package/template/wall-e/mcp-protocol-contract.js +40 -5
  465. package/template/wall-e/mcp-server.js +128 -29
  466. package/template/wall-e/mcp-worker-schemas.js +5 -5
  467. package/template/wall-e/memory/ctm-context-client.js +85 -0
  468. package/template/wall-e/memory/ctm-session-context.js +9 -3
  469. package/template/wall-e/memory/hub-bridge-client.js +388 -0
  470. package/template/wall-e/memory/replica-origin.js +56 -0
  471. package/template/wall-e/memory/session-pool.js +135 -0
  472. package/template/wall-e/runtime/live-capabilities.js +8 -3
  473. package/template/wall-e/server.js +4 -2
  474. package/template/wall-e/shared/release-schema.json +2 -2
  475. package/template/wall-e/shared/sqlite-file-set.js +39 -0
  476. package/template/wall-e/shared/storage/contract.js +3 -0
  477. package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
  478. package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +7 -1
  479. package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +4 -4
  480. package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +6 -6
  481. package/template/wall-e/skills/_templates/fleet-sync/VERSION +1 -0
  482. package/template/wall-e/skills/_templates/fleet-sync/released-sha256.json +16 -0
  483. package/template/wall-e/skills/_templates/fleet-sync/templates/claude/SKILL.md +64 -0
  484. package/template/wall-e/skills/_templates/fleet-sync/templates/codex/SKILL.md +64 -0
  485. package/template/wall-e/tools/chrome-automation.js +6 -0
  486. package/template/wall-e/training/setup-cuda-train-env.sh +2 -2
  487. package/template/wall-e/workers/brain-owner-worker.js +18 -4
  488. package/template/wall-e/workers/mcp-gateway-process.js +5 -5
@@ -123,11 +123,40 @@
123
123
  }).join('');
124
124
  }
125
125
 
126
+
127
+ // ── WP-3 models helpers, hoisted to module scope (box-owned sessions WP-3, docs/remote-session-
128
+ // child-transcript-model-truth-and-restart-2026-10-27.html §5 D-7): pure functions of the registry
129
+ // row, used by the card/sheet below AND exported as `modelsDelegateWarnings` so the shared
130
+ // Delegate pane (public/js/session-model-picker.js) words a box's reach verdict with THIS page's
131
+ // copy on the phone, exactly as it uses machinesPageModelsDelegateWarnings on the desktop.
132
+ function modelsBlock(machine) {
133
+ var block = machine && machine.models;
134
+ return block && typeof block === 'object' ? block : null;
135
+ }
136
+
137
+ function modelsDelegate(machine) {
138
+ var block = modelsBlock(machine);
139
+ return block && Array.isArray(block.delegate) ? block.delegate : [];
140
+ }
141
+
142
+ function modelsDelegateWarnings(machine) {
143
+ return modelsDelegate(machine).filter(function (entry) {
144
+ var reach = String((entry && entry.reach) || '');
145
+ return reach === 'unreachable' || reach === 'no_server';
146
+ }).map(function (entry) {
147
+ return '/delegate on ' + machineName(machine) + ' uses ' + String((entry && entry.model) || '')
148
+ + ' through a route ' + machineName(machine) + " can't reach.";
149
+ });
150
+ }
151
+
126
152
  function create(deps) {
127
153
  var d = deps || {};
128
154
  var $ = d.$ || function () { return null; };
129
155
  var esc = d.esc || defaultEsc;
130
156
  var api = d.api || function () { return Promise.reject(new Error('no api')); };
157
+ // hub bridge (R-13/AC-4d): the host re-mounts its per-card Access block after EVERY paint, because
158
+ // render() rebuilds the card markup (a load/refresh would otherwise wipe it).
159
+ var afterRender = typeof d.afterRender === 'function' ? d.afterRender : null;
131
160
  var toast = d.toast || function () {};
132
161
  var host = d.document || (typeof document !== 'undefined' ? document : null);
133
162
  var openNewSession = d.openNewSessionSheet || function () {};
@@ -209,14 +238,15 @@
209
238
  + '</div>';
210
239
  }
211
240
 
212
- function machineFactsHtml(sample, agents) {
241
+ function machineFactsHtml(sample, agents, history, presence) {
213
242
  return ''
214
243
  + factRow('Load', sample && sample.load1 != null ? esc(Number(sample.load1).toFixed(2)) : '')
215
244
  + factRow('Cores', sample && sample.cores != null ? esc(String(sample.cores)) : '')
216
245
  + factRow('Disk free', sample ? esc(machineFormatBytes(sample.diskFreeBytes)) : '')
217
246
  + factRow('GPU', esc(machineGpuText(sample && sample.gpu)))
247
+ + factRow('Power', esc(machinePowerText(sample, machineHistoryRows(history), at())))
218
248
  + factRow('Tasks', esc(machineTaskLine(sample && sample.tasks)))
219
- + factRow('Agents', esc(machineAgentText(agents)));
249
+ + factRow('Agents', esc(machineAgentText(agents, presence)));
220
250
  }
221
251
 
222
252
  // ── WP-3 sign-in: a coding agent ON a pooled machine, signed in from this phone ───────────────
@@ -642,6 +672,15 @@
642
672
  });
643
673
  }
644
674
 
675
+ // WP-3 (§8, docs/fleet-auto-update-pooled-machines-2026-10-26.html): the box's update status line
676
+ // — the phone twin of the desktop card's "mpc-fleet". The rule is the shared machineFleetLine;
677
+ // this is only its markup, so PHONE = DESKTOP PARITY holds byte for byte.
678
+ function fleetHtml(machine) {
679
+ var fleet = machineFleetLine(machine);
680
+ if (!fleet) return '';
681
+ return '<div class="mp-fleet" data-fleet-tone="' + esc(fleet.tone) + '" title="' + esc(fleet.title) + '">' + esc(fleet.text) + '</div>';
682
+ }
683
+
645
684
  function machineCardHtml(machine, history) {
646
685
  var m = machine || {};
647
686
  var id = normId(m.instanceId);
@@ -668,9 +707,12 @@
668
707
  + '<span class="mp-cred" data-cred="' + esc(badge.cred) + '" title="' + esc(badge.title) + '">' + esc(badge.text) + '</span>'
669
708
  + '</div>'
670
709
  + '<div class="mp-meta">' + esc(meta) + ' ' + staleNote + '</div>'
710
+ + fleetHtml(m)
671
711
  + sparkBlock(cpuSeries, memSeries, cpuNow, memNow)
672
- + '<div class="mp-facts">' + machineFactsHtml(sample, m.agents) + '</div>'
712
+ + '<div class="mp-facts">' + machineFactsHtml(sample, m.agents, history, m.agentPresence) + '</div>'
673
713
  + signInBlockHtml(m)
714
+ + skillsRowHtml(m)
715
+ + modelsRowHtml(m)
674
716
  + '<div class="mp-actions">'
675
717
  + '<button type="button" class="mp-btn mp-fav" aria-pressed="' + (m.favorite ? 'true' : 'false') + '"'
676
718
  + ' title="Favorite — shown first in the picker"'
@@ -704,7 +746,7 @@
704
746
  var poolNote = state.machines.length
705
747
  ? 'In a pool with ' + state.machines.length + ' other machine' + (state.machines.length === 1 ? '' : 's')
706
748
  : 'Not in a pool yet';
707
- var facts = machineFactsHtml(latest, null)
749
+ var facts = machineFactsHtml(latest, null, history)
708
750
  + factRow('Memory', latest ? esc(machineFormatBytes(latest.memUsedBytes) + ' / ' + machineFormatBytes(latest.memTotalBytes)) : '');
709
751
  return '<article class="mp-card mp-self" data-machine-id="' + esc(id) + '" data-health="reachable">'
710
752
  + '<div class="mp-head">'
@@ -780,6 +822,9 @@
780
822
  // the old card away) has only state.signInDrafts to be painted from.
781
823
  restoreSignInDraftsFromState(card, adoptedKeys);
782
824
  bind(card);
825
+ if (afterRender) {
826
+ try { afterRender(card); } catch (error) { if (typeof console !== 'undefined') console.warn('[phone-machines] afterRender failed', error); }
827
+ }
783
828
  return card;
784
829
  }
785
830
 
@@ -1127,6 +1172,715 @@
1127
1172
  });
1128
1173
  }
1129
1174
 
1175
+ // ── WP-9 (§9 S-9, AC-9a..AC-9d): the row's one Skills line and its Review sheet ─────────────
1176
+ // PHONE = DESKTOP PARITY: the same line, the same three sections, the same actions and the same
1177
+ // wording public/js/app/machines-page.js renders. AC-9c: a row with `skills: null` renders exactly
1178
+ // the card it rendered before this work — the line builder returns '' and nothing else moved.
1179
+ var SKILL_REASONS = {
1180
+ platform: 'macOS-only',
1181
+ hub_home_path: 'points at a path that exists only on this laptop',
1182
+ too_large: 'too large to send',
1183
+ unreadable: 'could not be read',
1184
+ };
1185
+ var SKILL_STATES = {
1186
+ new_on_box: 'new on this machine',
1187
+ same: 'same as the hub',
1188
+ shadows_hub: 'this machine’s copy wins',
1189
+ changed_on_box: 'changed on this machine',
1190
+ conflict: 'both sides changed',
1191
+ };
1192
+ var BOX_LOCAL_STATES = ['new_on_box', 'changed_on_box', 'conflict', 'shadows_hub'];
1193
+ var DIFF_LINE_CAP = 400;
1194
+ var review = { instanceId: '', skipped: {}, fromHub: null };
1195
+
1196
+ function skillsBlock(machine) {
1197
+ var block = machine && machine.skills;
1198
+ return block && typeof block === 'object' ? block : null;
1199
+ }
1200
+
1201
+ function skillReasonLabel(reason) {
1202
+ var key = String(reason || '');
1203
+ return SKILL_REASONS[key] || key || 'held back';
1204
+ }
1205
+
1206
+ function skillStateLabel(value) {
1207
+ var key = String(value || '');
1208
+ return SKILL_STATES[key] || key || 'differs';
1209
+ }
1210
+
1211
+ function skillsHeldBack(machine) {
1212
+ var block = skillsBlock(machine);
1213
+ return block && Array.isArray(block.heldBack) ? block.heldBack : [];
1214
+ }
1215
+
1216
+ function skillsOnBox(machine) {
1217
+ var block = skillsBlock(machine);
1218
+ return block && Array.isArray(block.onBox) ? block.onBox : [];
1219
+ }
1220
+
1221
+ function skillsPlugins(machine) {
1222
+ var block = skillsBlock(machine);
1223
+ return block && Array.isArray(block.plugins) ? block.plugins : [];
1224
+ }
1225
+
1226
+ function skillsExcluded(machine) {
1227
+ var block = skillsBlock(machine);
1228
+ return block && Array.isArray(block.excludePlugins) ? block.excludePlugins : [];
1229
+ }
1230
+
1231
+ function skillsBoxLocalCount(machine) {
1232
+ return skillsOnBox(machine).filter(function (item) {
1233
+ return item && BOX_LOCAL_STATES.indexOf(String(item.state)) !== -1;
1234
+ }).length;
1235
+ }
1236
+
1237
+ function skillsLineText(machine) {
1238
+ var block = skillsBlock(machine);
1239
+ if (!block) return '';
1240
+ var parts = [(Number(block.sentCount) || 0) + ' sent'];
1241
+ var heldBack = skillsHeldBack(machine).length;
1242
+ if (heldBack) parts.push(heldBack + ' held back');
1243
+ var local = skillsBoxLocalCount(machine);
1244
+ if (local) parts.push(local + ' new on ' + machineName(machine));
1245
+ var text = 'Skills: ' + parts.join(' · ');
1246
+ var error = block.pushError;
1247
+ if (error && (error.message || error.code)) text += ' · last push failed: ' + String(error.message || error.code);
1248
+ return text;
1249
+ }
1250
+
1251
+ function skillsNeedsReview(machine) {
1252
+ if (!skillsBlock(machine)) return false;
1253
+ if (skillsHeldBack(machine).length) return true;
1254
+ if (skillsBoxLocalCount(machine)) return true;
1255
+ var excluded = skillsExcluded(machine);
1256
+ return skillsPlugins(machine).some(function (plugin) { return excluded.indexOf(plugin) === -1; });
1257
+ }
1258
+
1259
+ function skillsRowHtml(machine) {
1260
+ if (!skillsBlock(machine)) return ''; // AC-9c: no block, no bytes
1261
+ var id = normId(machine && machine.instanceId);
1262
+ return '<div class="mp-skills"><span class="mp-skills-line">' + esc(skillsLineText(machine)) + '</span>'
1263
+ + (skillsNeedsReview(machine)
1264
+ ? '<button type="button" class="mp-btn mp-skills-review" data-machine-review="' + esc(id) + '">Review</button>'
1265
+ : '')
1266
+ + '</div>';
1267
+ }
1268
+
1269
+ // ── WP-3: the Models line + the Review sheet's Models section (§6.2, §8 WP-3) ────────────────
1270
+ // The phone twin of public/js/app/machines-page.js: the same states, the same strings, from the
1271
+ // same row block (lib/machine-registry.js `_modelsBlockFrom`, read on the 60 s probe tick).
1272
+ // `null` = never read, or a box that predates WP-1 — then this card renders exactly the bytes it
1273
+ // rendered before this work (AC-3a), so every builder below returns '' for null.
1274
+
1275
+ function modelsUnreachable(machine) {
1276
+ var block = modelsBlock(machine);
1277
+ return block && Array.isArray(block.unreachable) ? block.unreachable : [];
1278
+ }
1279
+
1280
+ function modelsUnreachableLabel(machine, item) {
1281
+ if (String((item && item.type) || '').toLowerCase() === 'ollama') return 'nothing is running there';
1282
+ return "can't be reached from " + machineName(machine);
1283
+ }
1284
+
1285
+ function modelsReachLabel(machine, reach) {
1286
+ return (String(reach || '') === 'reachable' ? 'can be reached from ' : "can't be reached from ")
1287
+ + machineName(machine);
1288
+ }
1289
+
1290
+ function modelsLineText(machine) {
1291
+ var block = modelsBlock(machine);
1292
+ if (!block) return '';
1293
+ var parts = [(Number(block.usable) || 0) + ' usable'];
1294
+ var unreachable = modelsUnreachable(machine).length;
1295
+ if (unreachable) parts.push(unreachable + " can't be reached from " + machineName(machine));
1296
+ return 'Models: ' + parts.join(' · ');
1297
+ }
1298
+
1299
+ function modelsNeedsReview(machine) {
1300
+ if (!modelsBlock(machine)) return false;
1301
+ if (modelsUnreachable(machine).length) return true;
1302
+ return modelsDelegateWarnings(machine).length > 0;
1303
+ }
1304
+
1305
+ function modelsRowHtml(machine) {
1306
+ if (!modelsBlock(machine)) return ''; // AC-3a: no block, no bytes
1307
+ var id = normId(machine && machine.instanceId);
1308
+ return '<div class="mp-models"><span class="mp-models-line">' + esc(modelsLineText(machine)) + '</span>'
1309
+ + (modelsNeedsReview(machine)
1310
+ ? '<span class="mp-models-sep">—</span>'
1311
+ + '<button type="button" class="mp-btn mp-models-review" data-machine-review="' + esc(id) + '">Review</button>'
1312
+ : '')
1313
+ + modelsDelegateWarnings(machine).map(function (text) {
1314
+ return '<span class="mp-models-warn">' + esc(text) + '</span>';
1315
+ }).join('')
1316
+ + '</div>';
1317
+ }
1318
+
1319
+ /** WP-9 title, extended by WP-3: the sheet names whichever of the two blocks the row has. */
1320
+ function reviewTitle(machine) {
1321
+ var name = machineName(machine);
1322
+ var hasSkills = !!skillsBlock(machine);
1323
+ var hasModels = !!modelsBlock(machine);
1324
+ if (hasSkills && hasModels) return 'Skills and models on ' + name;
1325
+ if (hasModels) return 'Models on ' + name;
1326
+ return 'Skills on ' + name;
1327
+ }
1328
+
1329
+ /** WP-3: a row is reviewable while EITHER block has something to show; an absent block renders
1330
+ * no section, so a models-only row opens a Models-only sheet. */
1331
+ function reviewOpenable(machine) {
1332
+ return !!skillsBlock(machine) || modelsNeedsReview(machine);
1333
+ }
1334
+
1335
+ /** WP-3 + WP-4 (desktop parity): the sheet's Models section. WP-4 puts ONE control on the
1336
+ * unreachable rows — a checkbox, because the route ids are the box's own — and the matching
1337
+ * "Remove from <name>" button under the list; never on the /delegate rows. Nothing unreachable →
1338
+ * no control at all, and the button stays disabled until a row is ticked. */
1339
+ function reviewModelsHtml(machine) {
1340
+ if (!modelsBlock(machine)) return ''; // no block, no section
1341
+ var name = machineName(machine);
1342
+ var unreachable = modelsUnreachable(machine);
1343
+ var rows = unreachable.map(function (item) {
1344
+ var id = String((item && item.id) || '');
1345
+ return '<li class="mp-review-item" data-route="' + esc(id) + '">'
1346
+ + '<label class="mp-review-pick"><input type="checkbox" data-role="models-pick" value="' + esc(id) + '"'
1347
+ + ' aria-label="' + esc('Remove ' + id + ' from ' + name) + '">'
1348
+ + '<span class="mp-review-name">' + esc(id) + '</span></label>'
1349
+ + '<span class="mp-review-meta">'
1350
+ + esc([String((item && item.type) || ''), String((item && item.origin) || '')].filter(Boolean).join(' · '))
1351
+ + '</span>'
1352
+ + '<span class="mp-review-where">' + esc(modelsUnreachableLabel(machine, item)) + '</span></li>';
1353
+ });
1354
+ if (!rows.length) rows.push('<li class="mp-review-empty">Every route on this machine can be reached.</li>');
1355
+ modelsDelegate(machine).forEach(function (entry) {
1356
+ var route = String((entry && entry.routeId) || '');
1357
+ var meta = [String((entry && entry.model) || ''), route ? 'route ' + route : ''].filter(Boolean).join(' · ');
1358
+ rows.push('<li class="mp-review-item" data-agent="' + esc(String((entry && entry.agent) || '')) + '">'
1359
+ + '<span class="mp-review-name">' + esc(String((entry && entry.agent) || '')) + '</span>'
1360
+ + '<span class="mp-review-meta">' + esc(meta) + '</span>'
1361
+ + '<span class="mp-review-where">' + esc(modelsReachLabel(machine, entry && entry.reach)) + '</span></li>');
1362
+ });
1363
+ var remove = unreachable.length
1364
+ ? '<div class="mp-review-models-remove"><button type="button" class="mp-btn mp-danger" data-role="models-remove"'
1365
+ + ' disabled>Remove from ' + esc(name) + '</button></div>'
1366
+ : '';
1367
+ return '<section class="mp-review-section" data-section="models"><h4>Models on ' + esc(name)
1368
+ + '</h4><ul>' + rows.join('') + '</ul>' + remove + fromHubHtml(machine) + '</section>';
1369
+ }
1370
+
1371
+ /** WP-5 (desktop parity): the "From this hub" subsection — the routes THIS hub holds that the
1372
+ * machine does not, one row each, each row with its OWN "Use on <name>" button. No checkbox and
1373
+ * no bulk control (AC-5c: nothing here can copy more than one route). The list is read ONCE per
1374
+ * open and read again after a copy or a remove. */
1375
+ function fromHubState(machine) {
1376
+ var id = normId(machine && machine.instanceId);
1377
+ var store = review && review.fromHub;
1378
+ if (!store || normId(store.instanceId) !== id) return { status: 'loading' };
1379
+ if (store.error) return { status: 'error', error: String(store.error) };
1380
+ return { status: 'ready', routes: Array.isArray(store.routes) ? store.routes : [] };
1381
+ }
1382
+
1383
+ function fromHubItemsHtml(machine) {
1384
+ if (!modelsBlock(machine)) return ''; // no block, no subsection
1385
+ var name = machineName(machine);
1386
+ var id = normId(machine && machine.instanceId);
1387
+ var view = fromHubState(machine);
1388
+ if (view.status === 'loading') {
1389
+ return '<li class="mp-review-empty">' + esc('Looking for routes this hub has that ' + name + ' lacks…') + '</li>';
1390
+ }
1391
+ if (view.status === 'error') return '<li class="mp-review-note">' + esc(view.error) + '</li>';
1392
+ if (!view.routes.length) {
1393
+ return '<li class="mp-review-empty">' + esc('This hub has no other routes ' + name + ' could use.') + '</li>';
1394
+ }
1395
+ return view.routes.map(function (route) {
1396
+ var routeId = String((route && route.id) || '');
1397
+ var label = String((route && route.name) || '') || routeId;
1398
+ var origin = String((route && route.origin) || '');
1399
+ var meta = [String((route && route.type) || ''), origin].filter(Boolean).join(' · ');
1400
+ return '<li class="mp-review-item" data-hub-route="' + esc(routeId) + '">'
1401
+ + '<span class="mp-review-name">' + esc(label) + '</span>'
1402
+ + '<span class="mp-review-meta">' + esc(meta) + '</span>'
1403
+ + '<span class="mp-review-actions"><button type="button" class="mp-btn mp-primary" data-role="models-copy"'
1404
+ + ' data-route-id="' + esc(routeId) + '" data-origin="' + esc(origin) + '">'
1405
+ + 'Use on ' + esc(name) + '</button></span></li>';
1406
+ }).join('');
1407
+ }
1408
+
1409
+ function fromHubHtml(machine) {
1410
+ if (!modelsBlock(machine)) return '';
1411
+ return '<div class="mp-review-from-hub" data-section="from-hub"><h5>From this hub</h5>'
1412
+ + '<ul class="mp-review-list" data-role="from-hub">' + fromHubItemsHtml(machine) + '</ul></div>';
1413
+ }
1414
+
1415
+ /** Repaint ONLY the list: the rest of the open sheet (and the note an action just wrote) stays. */
1416
+ function fromHubPaint() {
1417
+ var overlay = dialog && dialog.overlay;
1418
+ var machine = reviewMachine();
1419
+ if (!overlay || typeof overlay.querySelector !== 'function' || !modelsBlock(machine)) return false;
1420
+ var node = overlay.querySelector('[data-role="from-hub"]');
1421
+ if (!node) return false;
1422
+ node.innerHTML = fromHubItemsHtml(machine);
1423
+ return true;
1424
+ }
1425
+
1426
+ /** WP-5: ONE read of `models/copyable` per open, stored on the open sheet's own state. A refusal
1427
+ * is the server's own code and is shown, never retried — there is no timer here. */
1428
+ function fetchFromHub(instanceId) {
1429
+ var id = normId(instanceId);
1430
+ if (!id) return Promise.resolve(false);
1431
+ review.fromHub = { instanceId: id, loading: true };
1432
+ fromHubPaint();
1433
+ return api('/api/machines/' + encodeURIComponent(id) + '/models/copyable')
1434
+ .then(function (payload) {
1435
+ if (normId(review.instanceId) !== id) return false; // the sheet moved on meanwhile
1436
+ review.fromHub = {
1437
+ instanceId: id,
1438
+ routes: (payload && Array.isArray(payload.routes)) ? payload.routes : [],
1439
+ };
1440
+ fromHubPaint();
1441
+ return true;
1442
+ })
1443
+ .catch(function (error) {
1444
+ // REPORT: the subsection says why it is empty; the sheet stays open and nothing is retried.
1445
+ if (normId(review.instanceId) !== id) return false;
1446
+ review.fromHub = { instanceId: id, error: serverError(error) };
1447
+ fromHubPaint();
1448
+ return false;
1449
+ });
1450
+ }
1451
+
1452
+ /** The name a row was painted with — the success note says what travelled, in the user's words. */
1453
+ function fromHubRouteName(routeId) {
1454
+ var routes = (review && review.fromHub && Array.isArray(review.fromHub.routes)) ? review.fromHub.routes : [];
1455
+ var match = routes.filter(function (route) {
1456
+ return String((route && route.id) || '') === String(routeId == null ? '' : routeId);
1457
+ })[0];
1458
+ return String((match && match.name) || '') || String(routeId == null ? '' : routeId);
1459
+ }
1460
+
1461
+ /** WP-5: after a copy or a remove, re-read BOTH the list and what this hub could still offer. */
1462
+ function reloadReview() {
1463
+ var id = normId(review && review.instanceId);
1464
+ return reviewRefresh().then(function () {
1465
+ return fetchFromHub(id);
1466
+ });
1467
+ }
1468
+
1469
+ /** WP-5 / AC-5c (desktop parity): "Use on <name>" — ONE route and its key, and nothing else; the
1470
+ * confirm says so before anything is sent, and `expectOrigin` is the origin the row was painted
1471
+ * with, so a route that moved on the hub is refused instead of copied. */
1472
+ function copyModel(instanceId, button) {
1473
+ var id = normId(instanceId);
1474
+ var name = machineName(reviewMachine() || { instanceId: id });
1475
+ var attr = function (key) {
1476
+ return button && typeof button.getAttribute === 'function' ? String(button.getAttribute(key) || '') : '';
1477
+ };
1478
+ var routeId = attr('data-route-id');
1479
+ var origin = attr('data-origin');
1480
+ if (!id || !routeId) return Promise.resolve(false);
1481
+ var label = fromHubRouteName(routeId);
1482
+ if (confirmImpl && !confirmImpl('Copies this route and its key to ' + name + '. Only this route is copied.')) {
1483
+ return Promise.resolve(false);
1484
+ }
1485
+ busy(button, true);
1486
+ return api('/api/machines/' + encodeURIComponent(id) + '/models/copy', {
1487
+ method: 'POST',
1488
+ body: { routeId: routeId, expectOrigin: origin },
1489
+ }).then(function () {
1490
+ busy(button, false);
1491
+ // After the reload, which repaints the sheet (a note set first would be wiped).
1492
+ return reloadReview().then(function (reloaded) {
1493
+ reviewNote('Copied ' + label + ' to ' + name, 'ok');
1494
+ return reloaded;
1495
+ });
1496
+ }).catch(function (error) {
1497
+ // REPORT: the refusal (or the transport fault) stays on the sheet, which is still open.
1498
+ busy(button, false);
1499
+ reviewNote(serverError(error), 'bad');
1500
+ return false;
1501
+ });
1502
+ }
1503
+
1504
+ function reviewItemKey(agent, name) {
1505
+ return String(agent || '') + ':' + String(name || '');
1506
+ }
1507
+
1508
+ function reviewHeldBackHtml(machine) {
1509
+ if (!skillsBlock(machine)) return ''; // no block, no section (§8 WP-3)
1510
+ var items = skillsHeldBack(machine);
1511
+ var body = items.length
1512
+ ? items.map(function (item) {
1513
+ var where = item && item.file
1514
+ ? '<span class="mp-review-where">' + esc(String(item.file) + (item.line ? ':' + item.line : '')) + '</span>'
1515
+ : '';
1516
+ return '<li class="mp-review-item" data-key="' + esc(reviewItemKey(item && item.agent, item && item.name)) + '">'
1517
+ + '<span class="mp-review-name">' + esc(String((item && item.name) || '')) + '</span>'
1518
+ + '<span class="mp-review-meta">' + esc([String((item && item.agent) || ''), skillReasonLabel(item && item.reason)].filter(Boolean).join(' · ')) + '</span>'
1519
+ + where + '</li>';
1520
+ }).join('')
1521
+ : '<li class="mp-review-empty">Nothing was held back.</li>';
1522
+ return '<section class="mp-review-section" data-section="held-back"><h4>Held back</h4><ul>' + body + '</ul></section>';
1523
+ }
1524
+
1525
+ function reviewItemActionsHtml(instanceId, item) {
1526
+ var state = String((item && item.state) || '');
1527
+ var agent = String((item && item.agent) || '');
1528
+ var name = String((item && item.name) || '');
1529
+ var attrs = ' data-review-action data-review-id="' + esc(instanceId) + '" data-review-agent="' + esc(agent)
1530
+ + '" data-review-name="' + esc(name) + '" data-review-box-hash="' + esc(String((item && item.boxHash) || '')) + '"';
1531
+ var out = '';
1532
+ if (state !== 'shadows_hub') out += '<button type="button" class="mp-btn mp-primary"' + attrs + ' data-review-verb="import">Import</button>';
1533
+ if (state !== 'new_on_box') out += '<button type="button" class="mp-btn"' + attrs + ' data-review-verb="retire">Use the laptop’s copy</button>';
1534
+ out += '<button type="button" class="mp-btn"' + attrs + ' data-review-skip>Skip</button>';
1535
+ return out;
1536
+ }
1537
+
1538
+ function reviewOnBoxHtml(machine) {
1539
+ if (!skillsBlock(machine)) return ''; // no block, no section (§8 WP-3)
1540
+ var id = normId(machine && machine.instanceId);
1541
+ var items = skillsOnBox(machine).filter(function (item) { return item && String(item.state) !== 'same'; });
1542
+ var body = items.length
1543
+ ? items.map(function (item) {
1544
+ var name = String((item && item.name) || '');
1545
+ var key = reviewItemKey(item && item.agent, name);
1546
+ return '<li class="mp-review-item" data-key="' + esc(key) + '">'
1547
+ + '<span class="mp-review-name">' + esc(name) + '</span>'
1548
+ + '<span class="mp-review-meta">' + esc([String((item && item.agent) || ''), skillStateLabel(item && item.state)].filter(Boolean).join(' · ')) + '</span>'
1549
+ + '<span class="mp-review-actions">' + reviewItemActionsHtml(id, item)
1550
+ + '<button type="button" class="mp-btn" data-review-diff data-review-id="' + esc(id) + '"'
1551
+ + ' data-review-agent="' + esc(String((item && item.agent) || '')) + '" data-review-name="' + esc(name) + '">Show diff</button>'
1552
+ + '</span><div class="mp-review-diff" hidden></div></li>';
1553
+ }).join('')
1554
+ : '<li class="mp-review-empty">This machine has no skills of its own.</li>';
1555
+ return '<section class="mp-review-section" data-section="on-box"><h4>On ' + esc(machineName(machine)) + '</h4><ul>' + body + '</ul></section>';
1556
+ }
1557
+
1558
+ function reviewPluginsHtml(machine) {
1559
+ if (!skillsBlock(machine)) return ''; // no block, no section (§8 WP-3)
1560
+ var plugins = skillsPlugins(machine);
1561
+ var excluded = skillsExcluded(machine);
1562
+ var body = plugins.length
1563
+ ? plugins.map(function (plugin) {
1564
+ var id = String(plugin);
1565
+ return '<label class="mp-review-plugin"><input type="checkbox" value="' + esc(id) + '"'
1566
+ + (excluded.indexOf(id) !== -1 ? ' checked' : '') + '> ' + esc(id) + '</label>';
1567
+ }).join('')
1568
+ : '<p class="mp-review-empty">The hub sends no plugins to this machine.</p>';
1569
+ return '<section class="mp-review-section" data-section="plugins"><h4>Plugins</h4>'
1570
+ + '<p class="mp-review-note">Checked plugins are not sent to this machine.</p>'
1571
+ + '<div class="mp-review-plugins">' + body + '</div>'
1572
+ + '<div class="mp-review-actions"><button type="button" class="mp-btn mp-primary" data-review-save-plugins'
1573
+ + ' data-review-id="' + esc(normId(machine && machine.instanceId)) + '">Save</button></div></section>';
1574
+ }
1575
+
1576
+ function reviewSheetHtml(machine) {
1577
+ return '<section class="model-picker-sheet mp-modal mp-review-sheet" role="dialog" aria-modal="true" aria-labelledby="mp-review-title">'
1578
+ + '<div class="model-picker-head"><div><h2 id="mp-review-title">' + esc(reviewTitle(machine)) + '</h2>'
1579
+ + '<p>' + esc(skillsLineText(machine) || modelsLineText(machine)) + '</p></div>'
1580
+ + '<button type="button" class="model-picker-close" id="mp-review-close" aria-label="Close">&times;</button></div>'
1581
+ + '<p class="mp-review-note" data-role="review-note" role="status" aria-live="polite"></p>'
1582
+ + reviewHeldBackHtml(machine) + reviewOnBoxHtml(machine) + reviewPluginsHtml(machine)
1583
+ + reviewModelsHtml(machine)
1584
+ + '<div class="mp-modal-actions"><button type="button" class="mp-btn" id="mp-review-done">Close</button></div>'
1585
+ + '</section>';
1586
+ }
1587
+
1588
+ function reviewMachine() {
1589
+ var id = normId(review.instanceId);
1590
+ return state.machines.filter(function (m) { return normId(m && m.instanceId) === id; })[0] || null;
1591
+ }
1592
+
1593
+ function reviewNote(text, kind) {
1594
+ var node = (dialog && dialog.overlay && typeof dialog.overlay.querySelector === 'function')
1595
+ ? dialog.overlay.querySelector('[data-role="review-note"]') : null;
1596
+ if (!node) return;
1597
+ node.textContent = String(text || '');
1598
+ node.className = 'mp-review-note' + (kind === 'bad' ? ' bad' : '');
1599
+ }
1600
+
1601
+ /** Client-only Skip: the item leaves this sheet and stays gone across a repaint — no server call. */
1602
+ function reviewSkip(button) {
1603
+ if (!button || typeof button.getAttribute !== 'function') return false;
1604
+ review.skipped[reviewItemKey(button.getAttribute('data-review-agent'), button.getAttribute('data-review-name'))] = true;
1605
+ var item = typeof button.closest === 'function' ? button.closest('.mp-review-item') : null;
1606
+ if (item) item.hidden = true;
1607
+ return true;
1608
+ }
1609
+
1610
+ function applySkips() {
1611
+ var overlay = dialog && dialog.overlay;
1612
+ if (!overlay || typeof overlay.querySelectorAll !== 'function') return;
1613
+ Array.prototype.slice.call(overlay.querySelectorAll('.mp-review-item[data-key]') || []).forEach(function (item) {
1614
+ var key = typeof item.getAttribute === 'function' ? item.getAttribute('data-key') : null;
1615
+ if (key && review.skipped[key]) item.hidden = true;
1616
+ });
1617
+ }
1618
+
1619
+ /** AC-9a: re-read the list and repaint the OPEN sheet — never a page reload. */
1620
+ function reviewRefresh() {
1621
+ return load({ silent: true }).then(function () {
1622
+ var machine = reviewMachine();
1623
+ if (!(dialog && dialog.overlay && dialog.review)) return true; // the sheet was closed or replaced meanwhile
1624
+ if (machine && reviewOpenable(machine)) {
1625
+ dialog.overlay.innerHTML = reviewSheetHtml(machine);
1626
+ bindSheet();
1627
+ applySkips();
1628
+ } else {
1629
+ closeDialog(); // same as desktop machinesPageReviewPaint: nothing left to review → close
1630
+ }
1631
+ return true;
1632
+ });
1633
+ }
1634
+
1635
+ function openReviewSheet(instanceId) {
1636
+ var id = normId(instanceId);
1637
+ var machine = state.machines.filter(function (m) { return normId(m && m.instanceId) === id; })[0] || null;
1638
+ if (!reviewOpenable(machine)) return null;
1639
+ closeDialog();
1640
+ review = { instanceId: id, skipped: {}, fromHub: null };
1641
+ var overlay = mountDialog(reviewSheetHtml(machine));
1642
+ if (!overlay) return null;
1643
+ dialog = { overlay: overlay, review: true };
1644
+ bindSheet();
1645
+ // WP-5: one read of the hub's copyable routes per open — the sheet is already painted, so the
1646
+ // subsection shows "Looking for…" until this lands.
1647
+ fetchFromHub(id);
1648
+ return dialog;
1649
+ }
1650
+
1651
+ function bindSheet() {
1652
+ var close = $('mp-review-close');
1653
+ if (close && typeof close.addEventListener === 'function') close.addEventListener('click', closeDialog);
1654
+ var done = $('mp-review-done');
1655
+ if (done && typeof done.addEventListener === 'function') done.addEventListener('click', closeDialog);
1656
+ // The sheet mounts on document.body (mountDialog), OUTSIDE the card whose delegated click/change
1657
+ // listeners dispatch every data-review-* control — so a tap inside it never reached them.
1658
+ // Binding the overlay to the same dispatcher (bind() is idempotent per scope) makes the sheet's
1659
+ // Import / Use-the-laptop's-copy / Save / Remove-from-<box> controls work on the phone.
1660
+ if (dialog && dialog.overlay && dialog.overlay.dataset) bind(dialog.overlay);
1661
+ }
1662
+
1663
+ function busy(node, state_) {
1664
+ if (!node || typeof node !== 'object') return;
1665
+ node.disabled = !!state_;
1666
+ if (typeof node.setAttribute === 'function') node.setAttribute('aria-busy', state_ ? 'true' : 'false');
1667
+ }
1668
+
1669
+ function reviewActionFailed(error) {
1670
+ var payload = (error && error.payload) || null;
1671
+ var code = String((payload && (payload.error || payload.code)) || (error && (error.code || error.message)) || '');
1672
+ if (Number(error && error.status) === 409 || code.indexOf('agent_skill_changed') !== -1) {
1673
+ reviewNote('This skill changed on the box since you opened Review — reopen to see the new version.', 'bad');
1674
+ return reviewRefresh();
1675
+ }
1676
+ reviewNote(serverError(error), 'bad');
1677
+ return false;
1678
+ }
1679
+
1680
+ function reviewAction(instanceId, agent, name, boxHash, verb, button) {
1681
+ var action = String(verb) === 'retire' ? 'retire' : 'import';
1682
+ var body = { agent: String(agent || ''), name: String(name || '') };
1683
+ if (boxHash) body.expectBoxHash = String(boxHash);
1684
+ busy(button, true);
1685
+ return api('/api/machines/' + encodeURIComponent(normId(instanceId)) + '/skills/' + action, { method: 'POST', body: body })
1686
+ .then(function () {
1687
+ busy(button, false);
1688
+ setStatus(action === 'import'
1689
+ ? 'Imported ' + String(name || '') + ' from this machine.'
1690
+ : 'This machine now uses the laptop’s copy of ' + String(name || '') + '.');
1691
+ delete review.skipped[reviewItemKey(agent, name)];
1692
+ return reviewRefresh();
1693
+ })
1694
+ .catch(function (error) {
1695
+ // REPORT: the refusal (or the transport fault) stays on the sheet, which is still open.
1696
+ busy(button, false);
1697
+ return reviewActionFailed(error);
1698
+ });
1699
+ }
1700
+
1701
+ function savePlugins(instanceId, button) {
1702
+ var overlay = dialog && dialog.overlay;
1703
+ var excluded = [];
1704
+ if (overlay && typeof overlay.querySelectorAll === 'function') {
1705
+ Array.prototype.slice.call(overlay.querySelectorAll('.mp-review-plugins input') || []).forEach(function (box) {
1706
+ if (box && box.checked) excluded.push(String(box.value == null ? '' : box.value));
1707
+ });
1708
+ }
1709
+ busy(button, true);
1710
+ return api('/api/machines/' + encodeURIComponent(normId(instanceId)) + '/skills/exclude-plugins', {
1711
+ method: 'PUT',
1712
+ body: { plugins: excluded },
1713
+ }).then(function () {
1714
+ busy(button, false);
1715
+ setStatus(excluded.length
1716
+ ? 'Plugins saved — ' + excluded.length + ' not sent to this machine.'
1717
+ : 'Plugins saved — every plugin is sent to this machine.');
1718
+ return reviewRefresh();
1719
+ }).catch(function (error) {
1720
+ busy(button, false);
1721
+ reviewNote(serverError(error), 'bad');
1722
+ return false;
1723
+ });
1724
+ }
1725
+
1726
+ /** WP-4: the route ids ticked in the open sheet — the only truth a Remove POST may be built from. */
1727
+ function modelsChecked() {
1728
+ var overlay = dialog && dialog.overlay;
1729
+ var picked = [];
1730
+ if (overlay && typeof overlay.querySelectorAll === 'function') {
1731
+ Array.prototype.slice.call(overlay.querySelectorAll('[data-role="models-pick"]') || []).forEach(function (box) {
1732
+ if (box && box.checked) picked.push(String(box.value == null ? '' : box.value));
1733
+ });
1734
+ }
1735
+ return picked;
1736
+ }
1737
+
1738
+ /** WP-4: one tick or untick → the button is live exactly while something is ticked. Returns the count. */
1739
+ function modelsPicked() {
1740
+ var overlay = dialog && dialog.overlay;
1741
+ var count = modelsChecked().length;
1742
+ var node = overlay && typeof overlay.querySelector === 'function'
1743
+ ? overlay.querySelector('[data-role="models-remove"]') : null;
1744
+ if (node) node.disabled = count === 0;
1745
+ return count;
1746
+ }
1747
+
1748
+ /** WP-4: "Remove from <name>" — the box deletes these routes and the keys it holds for them; the
1749
+ * hub keeps its own copies, which is what the confirm says before anything is sent. A cancelled
1750
+ * confirm is not a request, only the TICKED ids travel, and the sheet is re-read after. */
1751
+ function removeModels(instanceId, button) {
1752
+ var id = normId(instanceId);
1753
+ var name = machineName(reviewMachine() || { instanceId: id });
1754
+ if (!modelsChecked().length) return Promise.resolve(false);
1755
+ if (confirmImpl && !confirmImpl('Deletes these routes and their keys from ' + name + '. This hub keeps its own copies.')) {
1756
+ return Promise.resolve(false);
1757
+ }
1758
+ var routeIds = modelsChecked();
1759
+ busy(button, true);
1760
+ return api('/api/machines/' + encodeURIComponent(id) + '/models/remove', {
1761
+ method: 'POST',
1762
+ body: { routeIds: routeIds },
1763
+ }).then(function (result) {
1764
+ busy(button, false);
1765
+ var data = (result && typeof result === 'object') ? result : {};
1766
+ var removed = Array.isArray(data.removed) ? data.removed.length : routeIds.length;
1767
+ var missing = Array.isArray(data.missing) ? data.missing.length : 0;
1768
+ // The note goes AFTER the reload, which repaints the sheet. WP-5: the removals also change
1769
+ // what this hub can offer, hence the reload of both lists.
1770
+ return reloadReview().then(function (reloaded) {
1771
+ reviewNote('Removed ' + removed + ' from ' + name + (missing ? ' · ' + missing + ' already gone' : ''), 'ok');
1772
+ return reloaded;
1773
+ });
1774
+ }).catch(function (error) {
1775
+ // REPORT: the refusal (or the transport fault) stays on the sheet, which is still open.
1776
+ busy(button, false);
1777
+ reviewNote(serverError(error), 'bad');
1778
+ return false;
1779
+ });
1780
+ }
1781
+
1782
+ // The diff ALGORITHM is public/js/prompt-diff.js — the same module the desktop page loads in
1783
+ // index.html and the phone does not, so the phone pulls the one copy in on first use (never a
1784
+ // second implementation of the LCS).
1785
+ var promptDiffPromise = null;
1786
+
1787
+ function promptDiffModule() {
1788
+ if (root.PromptDiff && typeof root.PromptDiff.diffLines === 'function') return Promise.resolve(root.PromptDiff);
1789
+ if (d.promptDiff && typeof d.promptDiff.diffLines === 'function') return Promise.resolve(d.promptDiff);
1790
+ if (promptDiffPromise) return promptDiffPromise;
1791
+ var doc = host || (typeof document !== 'undefined' ? document : null);
1792
+ if (!doc || typeof doc.createElement !== 'function') return Promise.resolve(null);
1793
+ var src = typeof d.promptDiffSrc === 'string' ? d.promptDiffSrc : '/js/prompt-diff.js';
1794
+ promptDiffPromise = new Promise(function (resolve) {
1795
+ var script = doc.createElement('script');
1796
+ script.src = src;
1797
+ script.onload = function () {
1798
+ resolve(root.PromptDiff && typeof root.PromptDiff.diffLines === 'function' ? root.PromptDiff : null);
1799
+ };
1800
+ script.onerror = function () { resolve(null); };
1801
+ var parent = doc.head || doc.body || doc.documentElement;
1802
+ if (parent && typeof parent.appendChild === 'function') parent.appendChild(script);
1803
+ else resolve(null);
1804
+ });
1805
+ return promptDiffPromise;
1806
+ }
1807
+
1808
+ function diffFileHtml(module, boxFile, hubFile) {
1809
+ var filePath = String((boxFile && boxFile.path) || (hubFile && hubFile.path) || '');
1810
+ var size = Number((boxFile && boxFile.size) || (hubFile && hubFile.size) || 0);
1811
+ var head = '<p class="mp-diff-file">' + esc(filePath) + '</p>';
1812
+ if ((boxFile && boxFile.binary) || (hubFile && hubFile.binary)) {
1813
+ return head + '<p class="mp-diff-binary">' + esc('binary file, ' + machineFormatBytes(size)) + '</p>';
1814
+ }
1815
+ var ops = module ? module.diffLines(hubFile ? String(hubFile.text || '') : '', boxFile ? String(boxFile.text || '') : '') : null;
1816
+ if (!Array.isArray(ops)) return head + '<p class="mp-diff-binary">' + esc('binary file, ' + machineFormatBytes(size)) + '</p>';
1817
+ var shown = ops.slice(0, DIFF_LINE_CAP);
1818
+ return head + '<div class="mp-diff">' + shown.map(function (op) {
1819
+ var cls = op.type === 'add' ? 'mp-diff-add' : (op.type === 'del' ? 'mp-diff-del' : 'mp-diff-eq');
1820
+ var sign = op.type === 'add' ? '+' : (op.type === 'del' ? '-' : ' ');
1821
+ return '<span class="' + cls + '">' + esc(sign + ' ' + op.text) + '</span>';
1822
+ }).join('') + '</div>'
1823
+ + (ops.length > shown.length ? '<p class="mp-review-note">' + esc('… ' + (ops.length - shown.length) + ' more lines') + '</p>' : '');
1824
+ }
1825
+
1826
+ /** A preview payload + a diff module → the per-file markup. Pure, so the sheet is provable. */
1827
+ function diffHtml(module, preview) {
1828
+ var boxFiles = (preview && preview.box && Array.isArray(preview.box.files)) ? preview.box.files : [];
1829
+ var hubFiles = (preview && preview.hub && Array.isArray(preview.hub.files)) ? preview.hub.files : [];
1830
+ var hubByName = {};
1831
+ hubFiles.forEach(function (file) { if (file && file.path) hubByName[String(file.path)] = file; });
1832
+ var seen = {};
1833
+ var out = boxFiles.map(function (file) {
1834
+ var key = String((file && file.path) || '');
1835
+ seen[key] = true;
1836
+ return diffFileHtml(module, file, hubByName[key] || null);
1837
+ });
1838
+ hubFiles.forEach(function (file) {
1839
+ var key = String((file && file.path) || '');
1840
+ if (seen[key]) return;
1841
+ out.push('<p class="mp-diff-file">' + esc(key) + '</p><p class="mp-diff-binary">'
1842
+ + esc('only on this hub — importing replaces it (a backup is kept)') + '</p>');
1843
+ });
1844
+ return out.join('') || '<p class="mp-review-empty">No files on either side.</p>';
1845
+ }
1846
+
1847
+ function diffNode(agent, name) {
1848
+ var overlay = dialog && dialog.overlay;
1849
+ if (!overlay || typeof overlay.querySelector !== 'function') return null;
1850
+ var key = reviewItemKey(agent, name).replace(/"/g, '\\"');
1851
+ var item = overlay.querySelector('.mp-review-item[data-key="' + key + '"]');
1852
+ return item && typeof item.querySelector === 'function' ? item.querySelector('.mp-review-diff') : null;
1853
+ }
1854
+
1855
+ /** AC-9b: the hub's copy vs the box's, through the ONE diff module (lazily loaded on the phone). */
1856
+ function showDiff(instanceId, agent, name, button) {
1857
+ var node = diffNode(agent, name);
1858
+ if (node && !node.hidden) { node.hidden = true; return Promise.resolve(true); }
1859
+ busy(button, true);
1860
+ return api('/api/machines/' + encodeURIComponent(normId(instanceId)) + '/skills/preview?agent='
1861
+ + encodeURIComponent(String(agent || '')) + '&name=' + encodeURIComponent(String(name || '')))
1862
+ .then(function (payload) {
1863
+ return promptDiffModule().then(function (module) {
1864
+ busy(button, false);
1865
+ node = diffNode(agent, name);
1866
+ if (!node) return false;
1867
+ node.hidden = false;
1868
+ node.innerHTML = diffHtml(module, payload);
1869
+ if (!module) node.innerHTML += '<p class="mp-review-note">' + esc('The diff module did not load — the file list above is still the truth.') + '</p>';
1870
+ return true;
1871
+ });
1872
+ })
1873
+ .catch(function (error) {
1874
+ busy(button, false);
1875
+ node = diffNode(agent, name);
1876
+ if (node) {
1877
+ node.hidden = false;
1878
+ node.innerHTML = '<p class="mp-review-note">' + esc(serverError(error)) + '</p>';
1879
+ }
1880
+ return false;
1881
+ });
1882
+ }
1883
+
1130
1884
  // ── wiring ─────────────────────────────────────────────────────────────────────────────────
1131
1885
  // ONE delegated click listener plus one change listener on the page, so a re-render never loses a
1132
1886
  // binding and a rename input only writes on commit (desktop parity: rename on `change`).
@@ -1196,6 +1950,51 @@
1196
1950
  if (join) { event.preventDefault(); openAddDialog({ url: join.getAttribute('data-machine-url') || '' }); return; }
1197
1951
  var signIn = closest('[data-machine-signin]');
1198
1952
  if (signIn) { event.preventDefault(); signInToo(signIn.getAttribute('data-machine-signin')); return; }
1953
+ // WP-9: the Review sheet's own actions (same verbs, same wording as the desktop dialog).
1954
+ var reviewOpen = closest('[data-machine-review]');
1955
+ if (reviewOpen) { event.preventDefault(); openReviewSheet(reviewOpen.getAttribute('data-machine-review')); return; }
1956
+ var skip = closest('[data-review-skip]');
1957
+ if (skip) { event.preventDefault(); reviewSkip(skip); return; }
1958
+ var diff = closest('[data-review-diff]');
1959
+ if (diff) {
1960
+ event.preventDefault();
1961
+ showDiff(diff.getAttribute('data-review-id'), diff.getAttribute('data-review-agent'), diff.getAttribute('data-review-name'), diff);
1962
+ return;
1963
+ }
1964
+ var savePluginsNode = closest('[data-review-save-plugins]');
1965
+ if (savePluginsNode) {
1966
+ event.preventDefault();
1967
+ savePlugins(savePluginsNode.getAttribute('data-review-id'), savePluginsNode);
1968
+ return;
1969
+ }
1970
+ // WP-4: "Remove from <name>" — the instance id is the sheet's own (review.instanceId), never
1971
+ // read back out of the markup.
1972
+ var removeModelsNode = closest('[data-role="models-remove"]');
1973
+ if (removeModelsNode) {
1974
+ event.preventDefault();
1975
+ removeModels(review.instanceId, removeModelsNode);
1976
+ return;
1977
+ }
1978
+ // WP-5: "Use on <name>" — one route per tap, the sheet's own id, the row's own route/origin.
1979
+ var copyModelNode = closest('[data-role="models-copy"]');
1980
+ if (copyModelNode) {
1981
+ event.preventDefault();
1982
+ copyModel(review.instanceId, copyModelNode);
1983
+ return;
1984
+ }
1985
+ var action = closest('[data-review-action]');
1986
+ if (action) {
1987
+ event.preventDefault();
1988
+ reviewAction(
1989
+ action.getAttribute('data-review-id'),
1990
+ action.getAttribute('data-review-agent'),
1991
+ action.getAttribute('data-review-name'),
1992
+ action.getAttribute('data-review-box-hash'),
1993
+ action.getAttribute('data-review-verb'),
1994
+ action,
1995
+ );
1996
+ return;
1997
+ }
1199
1998
  // WP-3: sign a coding agent in ON another machine (the passkey row above is the CTM LOGIN).
1200
1999
  var agentSignIn = closest('[data-machine-sign-in]');
1201
2000
  if (agentSignIn) {
@@ -1228,6 +2027,9 @@
1228
2027
  scope.addEventListener('change', function (event) {
1229
2028
  var target = event.target;
1230
2029
  var closest = target && typeof target.closest === 'function' ? target.closest.bind(target) : null;
2030
+ // WP-4: a tick on a route row is the only thing that arms "Remove from <name>".
2031
+ var pick = closest ? closest('[data-role="models-pick"]') : null;
2032
+ if (pick) { modelsPicked(); return; }
1231
2033
  var input = closest ? closest('[data-machine-rename]') : null;
1232
2034
  if (!input) return;
1233
2035
  rename(input.getAttribute('data-machine-rename'), input.value);
@@ -1286,6 +2088,35 @@
1286
2088
  registerLegacy: registerLegacy,
1287
2089
  openShowCodeDialog: openShowCodeDialog,
1288
2090
  closeDialog: closeDialog,
2091
+ // WP-9 (§9 S-9): the row line + Review sheet, exposed like every other presenter here.
2092
+ skillsRowHtml: skillsRowHtml,
2093
+ skillsLineText: skillsLineText,
2094
+ skillsNeedsReview: skillsNeedsReview,
2095
+ // WP-3 (§8 WP-3): the row line + Review sheet's Models half, exposed like the skills twins.
2096
+ modelsRowHtml: modelsRowHtml,
2097
+ modelsLineText: modelsLineText,
2098
+ modelsNeedsReview: modelsNeedsReview,
2099
+ reviewModelsHtml: reviewModelsHtml,
2100
+ reviewSheetHtml: reviewSheetHtml,
2101
+ reviewHeldBackHtml: reviewHeldBackHtml,
2102
+ reviewOnBoxHtml: reviewOnBoxHtml,
2103
+ reviewPluginsHtml: reviewPluginsHtml,
2104
+ openReviewSheet: openReviewSheet,
2105
+ reviewRefresh: reviewRefresh,
2106
+ reviewAction: reviewAction,
2107
+ reviewSkip: reviewSkip,
2108
+ savePlugins: savePlugins,
2109
+ // WP-4 (§8 WP-4): the Models section's one control — the ids, the arming, and the write.
2110
+ modelsChecked: modelsChecked,
2111
+ modelsPicked: modelsPicked,
2112
+ removeModels: removeModels,
2113
+ // WP-5 (§8 WP-5): the "From this hub" subsection — the rows, the one read, and the write.
2114
+ fromHubHtml: fromHubHtml,
2115
+ fromHubItemsHtml: fromHubItemsHtml,
2116
+ fetchFromHub: fetchFromHub,
2117
+ copyModel: copyModel,
2118
+ showDiff: showDiff,
2119
+ diffHtml: diffHtml,
1289
2120
  preview: function () { return dialog ? previewFields() : null; },
1290
2121
  dialog: function () { return dialog; },
1291
2122
  readOrigins: poolOrigins,
@@ -1307,6 +2138,9 @@
1307
2138
  sheetRowHtml: sheetRowHtml,
1308
2139
  sheetListHtml: sheetListHtml,
1309
2140
  credentialBadge: credentialBadge,
2141
+ // WP-3 (box-owned sessions): the one sentence for a /delegate pick the box cannot reach, read by
2142
+ // the shared Delegate pane on the phone (PHONE = DESKTOP PARITY).
2143
+ modelsDelegateWarnings: modelsDelegateWarnings,
1310
2144
  };
1311
2145
  if (typeof module === 'object' && module.exports) module.exports = out;
1312
2146
  else root.CtmPhoneMachines = out;