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
@@ -136,6 +136,10 @@
136
136
  health: null,
137
137
  healthTest: null,
138
138
  },
139
+ // WP-2 (docs/external-causality-kill-session-loss-2026-10-27.html §5 D-5, §6, §8 WP-2): the
140
+ // last `host-dead` frame, or null. Mirrors the desktop banner's fact shape so both surfaces
141
+ // render the same policy text/actions.
142
+ hostDead: null,
139
143
  detailScrollLock: {
140
144
  locked: false,
141
145
  scrollY: 0,
@@ -159,7 +163,7 @@
159
163
  itemsContext: null,
160
164
  itemElements: [],
161
165
  },
162
- projects: [],
166
+ projects: [], projectsError: false, // projectsError (D-6): this device's /api/projects read FAILED — never "no projects"
163
167
  // WP-5 (S-16/AC-5c): the machine registry, mirrored field for field from the desktop client —
164
168
  // same GET /api/machines rows, same reachable/unknown/unhealthy card states, same chip facts.
165
169
  // Empty until the hub answers; with zero machines nothing machine-shaped renders at all.
@@ -805,6 +809,70 @@
805
809
  banner.hidden = false;
806
810
  }
807
811
 
812
+ // WP-2 (docs/external-causality-kill-session-loss-2026-10-27.html §5 D-5, §6, §8 WP-2, AC-2a/2e):
813
+ // the phone half of the dead-adopted-host notice. Text and button come from the SAME policy module
814
+ // the desktop banner uses (public/js/restart-banner-policy.js), so the two surfaces can never
815
+ // disagree; the notice reuses the app-reload-banner styling rather than a second system.
816
+ function hostDeadCopy(fact) {
817
+ const policy = window.RestartBannerPolicy;
818
+ if (policy && typeof policy.decideRestartBanner === 'function') {
819
+ const decision = policy.decideRestartBanner({ hostDead: fact });
820
+ return { text: decision.text, showButton: decision.actions.includes('resume') };
821
+ }
822
+ // Fail soft to the same strings if the policy script did not load (offline shell).
823
+ const total = Math.max(0, Number(fact.sessionCount) || 0);
824
+ const remaining = Math.max(0, Number(fact.remaining) || 0);
825
+ return {
826
+ text: remaining > 0
827
+ ? `${total} sessions lost with their host. Resume ${remaining} sessions`
828
+ : `${total} sessions lost with their host, ${total} resumed`,
829
+ showButton: remaining > 0,
830
+ };
831
+ }
832
+
833
+ function renderHostDeadBanner() {
834
+ const banner = $('host-dead-banner');
835
+ if (!banner) return null;
836
+ const fact = state.hostDead;
837
+ if (!fact || !fact.hostId) { banner.hidden = true; return null; }
838
+ const copy = hostDeadCopy(fact);
839
+ const msg = $('host-dead-msg');
840
+ if (msg) { msg.textContent = copy.text; msg.title = copy.text; }
841
+ const btn = $('host-dead-resume-btn');
842
+ if (btn) btn.hidden = !copy.showButton;
843
+ banner.hidden = false;
844
+ return copy;
845
+ }
846
+
847
+ function applyHostDead(msg) {
848
+ const hostId = String((msg && msg.hostId) || '');
849
+ if (!hostId) { state.hostDead = null; renderHostDeadBanner(); return null; }
850
+ const sessionCount = Math.max(0, Number(msg.sessionCount) || 0);
851
+ const resumedCount = Array.isArray(msg.resumed) ? msg.resumed.length : 0;
852
+ state.hostDead = {
853
+ hostId,
854
+ at: Number(msg.at) || 0,
855
+ reason: String((msg && msg.reason) || ''),
856
+ sessionCount,
857
+ remaining: Math.max(0, sessionCount - resumedCount),
858
+ fingerprint: `${hostId}|${Number(msg.at) || 0}`,
859
+ };
860
+ renderHostDeadBanner();
861
+ return state.hostDead;
862
+ }
863
+
864
+ function resumeHostDeadSessions() {
865
+ const fact = state.hostDead;
866
+ if (!fact || !fact.hostId) return Promise.resolve(null);
867
+ return api('/api/sessions/resume-deferred', { method: 'POST', body: { hostId: fact.hostId } })
868
+ .then((data) => {
869
+ // The server re-broadcasts `host-dead` to every viewer after the drain; applyHostDead
870
+ // renders that frame, so nothing is cleared locally (phone = desktop parity).
871
+ return data;
872
+ })
873
+ .catch((err) => { toast(err?.message || 'Could not resume the lost sessions.', 'error'); return null; });
874
+ }
875
+
808
876
  function requestInstalledUpdateReload(opts = {}) {
809
877
  const server = normalizeAppVersionInfo(opts.server) || state.appRuntime.latest;
810
878
  state.appRuntime.latest = server || state.appRuntime.latest;
@@ -1305,9 +1373,21 @@
1305
1373
  handleMobileAuthError: (err, lease) => { if (lease && !privateLeaseCurrent(lease)) throw privateScopeChangedError(); handleMobileAuthError(err); },
1306
1374
  privateScopeChangedError,
1307
1375
  });
1308
- return mobilePrivateApi.request(path, options);
1376
+ try {
1377
+ return await mobilePrivateApi.request(path, options);
1378
+ } catch (err) {
1379
+ // WP-1b (§8 step 3): a failed api() already carries `status` and a body-derived `code`, but the
1380
+ // parsed JSON is named `payload`; the child viewer's safeErrorText reads `body`. Attach the ONE
1381
+ // parsed object under that name too, and change nothing else about the error.
1382
+ if (err && typeof err === 'object' && err.body === undefined && err.payload !== undefined) err.body = err.payload;
1383
+ throw err;
1384
+ }
1309
1385
  }
1310
1386
 
1387
+ // §6.4 operator stop (phone half): the shared module owns the sheet and the per-task Stop control; this
1388
+ // app supplies the detail view's own card and its ONE authenticated request path (never a raw fetch).
1389
+ window.BackgroundWork?.installPhoneStop?.({ request: (path, options) => api(path, options), currentCard: () => (state.activeSession ? displayStatusCard(state.activeSession) : null) });
1390
+
1311
1391
  function privateScopeChangedError() {
1312
1392
  return Object.assign(new Error('Private phone data is paused. Verify this phone and CTM identity, then retry.'), { code: 'private_scope_changed' });
1313
1393
  }
@@ -1791,6 +1871,18 @@
1791
1871
  .trim();
1792
1872
  }
1793
1873
 
1874
+ // WP-3 (codex-native-subagent-pick-enforcement-2026-10-28.html §5 D-6, §6, §9 S-8): the phone half of
1875
+ // the PASSIVE native-subagents-off chip — the SAME shared pure rule the desktop lead row reads,
1876
+ // gated to Codex cards. Static label: no click handler, no new WS message.
1877
+ function nativeSubagentChipHtml(card) {
1878
+ const shared = window.CTMCodingModelPresentation;
1879
+ if (!shared || typeof shared.nativeSubagentChip !== 'function') return '';
1880
+ if (workflowRoutingAgent(card) !== 'codex') return '';
1881
+ const pick = (card && (card.subagent_pick || (card.meta && card.meta.subagent_pick))) || null;
1882
+ const chip = shared.nativeSubagentChip(pick);
1883
+ return chip ? `<span class="model-chip model-chip-native-off" title="${esc(chip.title)}">${esc(chip.text)}</span>` : '';
1884
+ }
1885
+
1794
1886
  // WP1C: a generic card's model chip is the SHARED owner/ledger-qualified label (the same presenter
1795
1887
  // the header and the sheet read) — never the raw card.model. Without the shared resolver a carrier
1796
1888
  // declaring ANY authority field fails closed; a legacy card keeps its raw label.
@@ -1799,12 +1891,12 @@
1799
1891
  const shared = window.CTMCodingModelPresentation;
1800
1892
  if (shared && typeof shared.cardModelLabel === 'function') {
1801
1893
  const qualified = shared.cardModelLabel(card, raw, modelDisplayLabel);
1802
- return qualified ? `<span class="model-chip">${esc(qualified)}</span>` : '';
1894
+ return (qualified ? `<span class="model-chip">${esc(qualified)}</span>` : '') + nativeSubagentChipHtml(card);
1803
1895
  }
1804
1896
  const declares = (source) => !!source
1805
1897
  && ['model_owner', 'model_observation_health', 'model_switch_event'].some((key) => Object.hasOwn(source, key));
1806
- if (declares(card) || declares(card && card.meta)) return '';
1807
- return raw ? `<span class="model-chip">${esc(raw)}</span>` : '';
1898
+ if (declares(card) || declares(card && card.meta)) return nativeSubagentChipHtml(card);
1899
+ return (raw ? `<span class="model-chip">${esc(raw)}</span>` : '') + nativeSubagentChipHtml(card);
1808
1900
  }
1809
1901
 
1810
1902
  function modelProviderLabel(provider) {
@@ -2058,13 +2150,37 @@
2058
2150
  .join('');
2059
2151
  }
2060
2152
 
2153
+ // WP-3b (§8 WP-3): the landing queue's chip TEXT for the phone. This must produce byte-identical
2154
+ // strings to public/index.html's landingChipText for the same input — that is the phone parity
2155
+ // rule — so the two formatters change together. Nothing here is HTML; the callers escape.
2156
+ function landingEtaText(etaSeconds) {
2157
+ return typeof etaSeconds === 'number' && Number.isFinite(etaSeconds)
2158
+ ? `${Math.round(etaSeconds / 60)}m` : '';
2159
+ }
2160
+ function landingQueueText(landing) {
2161
+ if (!landing || typeof landing !== 'object') return '';
2162
+ if (typeof landing.position === 'number' && Number.isFinite(landing.position)) {
2163
+ const total = Number.isFinite(Number(landing.total)) ? Number(landing.total) : 0;
2164
+ const eta = landingEtaText(landing.eta_s);
2165
+ return `⇡#${landing.position}/${total}${eta ? ` · ${eta}` : ''}`;
2166
+ }
2167
+ const state = typeof landing.state === 'string' ? landing.state.trim() : '';
2168
+ return state ? `⇡${state}` : '';
2169
+ }
2170
+ // The conflict radar is not on the row, same as desktop: it lives in the details Radar row.
2171
+ function landingChipText(card) {
2172
+ return landingQueueText(card && card.workspaceTracking ? card.workspaceTracking.landing : null);
2173
+ }
2174
+
2061
2175
  function sessionDiffTags(card, opts = {}) {
2062
2176
  const diff = sessionWorktreeDiff(card);
2063
- if (!diff) return '';
2177
+ const landingText = landingChipText(card);
2178
+ if (!diff && !landingText) return '';
2064
2179
  const chips = [];
2065
2180
  const interactive = !!opts.interactive;
2066
- const actionTarget = interactive ? worktreeFinishTarget(card, diff) : null;
2067
- if (diff.dirtyFiles > 0) {
2181
+ // A landing-only chip has no worktree diff to finish, so every `diff.` read below is guarded.
2182
+ const actionTarget = interactive && diff ? worktreeFinishTarget(card, diff) : null;
2183
+ if (diff && diff.dirtyFiles > 0) {
2068
2184
  const label = pluralize(diff.dirtyFiles, 'file');
2069
2185
  const title = diff.isMain
2070
2186
  ? `${diff.branch}: ${pluralize(diff.dirtyFiles, 'uncommitted file')} on main`
@@ -2074,7 +2190,7 @@
2074
2190
  ? `<button class="session-diff-chip session-diff-chip-action files" type="button"${worktreeFinishDataset(actionTarget)} title="${esc(actionTitle)}" aria-label="${esc(actionTitle)}">${esc(label)}</button>`
2075
2191
  : `<span class="session-diff-chip files" title="${esc(title)}" aria-label="${esc(title)}">${esc(label)}</span>`);
2076
2192
  }
2077
- if (diff.unmergedCommits > 0) {
2193
+ if (diff && diff.unmergedCommits > 0) {
2078
2194
  const label = pluralize(diff.unmergedCommits, 'commit');
2079
2195
  // netContentDiffers === false → commits exist but merging changes nothing (integrated/
2080
2196
  // superseded). Dim the chip + clarify the title so it's distinct from real unmerged work.
@@ -2090,7 +2206,13 @@
2090
2206
  ? `<button class="session-diff-chip session-diff-chip-action ${commitsCls}" type="button"${worktreeFinishDataset(actionTarget)} title="${esc(actionTitle)}" aria-label="${esc(actionTitle)}">${esc(label)}</button>`
2091
2207
  : `<span class="session-diff-chip ${commitsCls}" title="${esc(title)}" aria-label="${esc(title)}">${esc(label)}</span>`);
2092
2208
  }
2093
- const label = diff.isMain ? `Main branch changes for ${diff.branch}` : `Branch diff for ${diff.branch}`;
2209
+ if (landingText) {
2210
+ // Same text as the desktop queue part.
2211
+ chips.push(`<span class="session-diff-chip landing" title="${esc(landingText)}" aria-label="${esc(landingText)}">${esc(landingText)}</span>`);
2212
+ }
2213
+ const label = diff
2214
+ ? (diff.isMain ? `Main branch changes for ${diff.branch}` : `Branch diff for ${diff.branch}`)
2215
+ : `Landing queue${landingText ? `: ${landingText}` : ''}`;
2094
2216
  return `<span class="session-diff-tags" aria-label="${esc(label)}">${chips.join('')}</span>`;
2095
2217
  }
2096
2218
 
@@ -2233,7 +2355,18 @@
2233
2355
  async function retireWorkers(body) {
2234
2356
  const calls = [api('/api/sessions/workers/retire', { method: 'POST', body })];
2235
2357
  if (body && body.lead) calls.push(api('/api/sessions/' + encodeURIComponent(body.lead) + '/native-subagents/clear', { method: 'POST' }));
2236
- await Promise.all(calls);
2358
+ try {
2359
+ await Promise.all(calls);
2360
+ } catch (err) {
2361
+ // WP-1b (§8 step 5): a box-owned lead's clear is forwarded by the hub; an unreachable box names
2362
+ // itself through the phone's existing transient-message toast. Any other failure is rethrown
2363
+ // unchanged for the caller's own .catch.
2364
+ if (err?.code === 'machine_unreachable' || err?.body?.code === 'machine_unreachable') {
2365
+ toast(`${err?.body?.machineName || 'The machine that owns this session'} is unreachable. Retry when ready.`);
2366
+ return;
2367
+ }
2368
+ throw err;
2369
+ }
2237
2370
  scheduleRefresh(0);
2238
2371
  }
2239
2372
 
@@ -2433,6 +2566,15 @@
2433
2566
  return `<span class="row-diff-mark ${cls}" title="${esc(titles.join('; '))}">${esc(sessionDiffCountText(diff))}</span>`;
2434
2567
  }
2435
2568
 
2569
+ // WP-3b phone parity: the landing queue also rides the SESSION ROW mark, not just the worktree
2570
+ // sheet chip, matching the desktop badge on the session list row. Text is byte-identical to
2571
+ // `landingChipText` (and therefore to the desktop `landingChipText`).
2572
+ function sessionLandingMark(card) {
2573
+ const landingText = landingChipText(card);
2574
+ if (!landingText) return '';
2575
+ return `<span class="row-diff-mark landing" title="${esc(landingText)}">${esc(landingText)}</span>`;
2576
+ }
2577
+
2436
2578
  // A session row (home + Agents): title and ONE pill, one meta line, at most one button (Wake, parked/deferred only).
2437
2579
  function sessionRow(card, opts = {}) {
2438
2580
  const bucket = cardBucket(card);
@@ -2464,11 +2606,11 @@
2464
2606
  <span class="session-main">
2465
2607
  <span class="session-title-line">
2466
2608
  <span class="session-title">${esc(card.title || card.id || 'Session')}</span>
2467
- ${opts.worker === true ? cardModelChip(card) : ''}
2609
+ ${opts.worker === true ? cardModelChip(card) : nativeSubagentChipHtml(card)}
2468
2610
  ${phoneMachineChip(card)}
2469
2611
  ${pill}
2470
2612
  </span>
2471
- <span class="session-meta"><span class="session-meta-text">${esc(meta)}</span>${sessionDiffMark(card)}</span>
2613
+ <span class="session-meta"><span class="session-meta-text">${esc(meta)}</span>${sessionDiffMark(card)}${sessionLandingMark(card)}</span>
2472
2614
  ${searchMatch}
2473
2615
  </span>
2474
2616
  ${action ? `<span class="row-actions">${action}</span>` : ''}
@@ -4961,8 +5103,8 @@
4961
5103
 
4962
5104
  async function loadProjects() {
4963
5105
  const lease = mobilePrivateLease; if (!mobilePrivateReady || !privateLeaseCurrent(lease)) return;
4964
- try { const data = await api('/api/projects'); if (privateLeaseCurrent(lease)) state.projects = Array.isArray(data) ? data : (Array.isArray(data.projects) ? data.projects : []); }
4965
- catch { if (privateLeaseCurrent(lease)) state.projects = []; }
5106
+ try { const data = await api('/api/projects'); if (privateLeaseCurrent(lease)) { state.projects = Array.isArray(data) ? data : (Array.isArray(data.projects) ? data.projects : []); state.projectsError = false; } }
5107
+ catch { if (privateLeaseCurrent(lease)) { state.projects = []; state.projectsError = true; } }
4966
5108
  }
4967
5109
 
4968
5110
  function pushSupported() {
@@ -5641,6 +5783,10 @@
5641
5783
  }
5642
5784
 
5643
5785
  function dispatchMobileWsMessage(msg) {
5786
+ // WP-2 (docs/external-causality-kill-session-loss-2026-10-27.html §5 D-5, §6, §8 WP-2, AC-2a):
5787
+ // an adopted host died with sessions still attached — the phone shows the SAME notice and the
5788
+ // SAME Resume button as the desktop banner (PHONE = DESKTOP parity).
5789
+ if (msg.type === 'host-dead') { applyHostDead(msg); return; }
5644
5790
  if (msg.type === 'error') {
5645
5791
  // A machine create refused at the hub names the box and the page that fixes it; show it and
5646
5792
  // stop. 'hello'/'pong'/… are unaffected, and no other frame type is touched.
@@ -5727,6 +5873,12 @@
5727
5873
  phoneModelPicker?.delegatePrefsChanged();
5728
5874
  return;
5729
5875
  }
5876
+ // WP-4 (R-5/R-13, AC-2g): a box session's gated op was refused, or its ask was answered. The
5877
+ // phone shows the same banner the desktop shows (phone = desktop parity), in the session detail.
5878
+ if (msg.type === 'hub-bridge-pending' || msg.type === 'hub-bridge-pending-cleared') {
5879
+ phoneAccessOnFrame(msg);
5880
+ return;
5881
+ }
5730
5882
  if (msg.type === 'remote-submission-status') {
5731
5883
  applyRemoteSubmissionStatus(msg);
5732
5884
  return;
@@ -7094,6 +7246,8 @@
7094
7246
  setDetailSessionChromeVisible(true);
7095
7247
  state.activeSession = card;
7096
7248
  state.activeStreamSessionId = card.id;
7249
+ // WP-4 AC-2g: an ask already open for this session is shown before any frame arrives.
7250
+ phoneAccessSeedBanners().catch(() => {});
7097
7251
  phonePresence.onDetailOpened(); // D-9: a new detail view starts its own idle clock
7098
7252
  render();
7099
7253
  resetDetailTimeline(card.id);
@@ -7145,6 +7299,7 @@
7145
7299
  if (state.approvalSheet && state.approvalSheet.sessionId !== '') closeApprovalRequestSheet();
7146
7300
  state.activeSession = null;
7147
7301
  state.activeStreamSessionId = null;
7302
+ phoneAccessPaintBanner(); // AC-2g: an ask's banner belongs to the session the detail view showed
7148
7303
  setDetailSessionChromeVisible(true);
7149
7304
  resetDetailTimeline(null);
7150
7305
  clearChoiceStillWaitingTimer();
@@ -7571,6 +7726,11 @@
7571
7726
  return card?.agentCapabilities || card?.capabilities || {};
7572
7727
  }
7573
7728
 
7729
+ // WP-11 phone parity (AC-15c): an image attach is a CAPABILITY gate — a dsh card whose live child
7730
+ // did not advertise image prompt support refuses; a card with no capability record is unchanged.
7731
+ const MOBILE_IMAGE_ATTACH_UNAVAILABLE = 'Images are unavailable: this DeepSeek Harness agent did not advertise image prompt support.';
7732
+ function sessionImageAttachAllowed(card) { return cardCapabilities(card).input?.attachments !== false; }
7733
+
7574
7734
  function walleIdentityText(card) {
7575
7735
  return [
7576
7736
  card?.agent,
@@ -7722,6 +7882,8 @@
7722
7882
  if (/codex|openai|gpt-|o[1345]/.test(value)) return 'codex';
7723
7883
  if (/gemini|google/.test(value)) return 'gemini';
7724
7884
  if (/cursor/.test(value)) return 'cursor';
7885
+ // WP-7 (S-59): DeepSeek Harness (dsh) — the phone chip must read the same agent as desktop.
7886
+ if (/deepseek|(^|[-_/])dsh($|[-_/\s])/.test(value)) return 'dsh';
7725
7887
  }
7726
7888
  return '';
7727
7889
  }
@@ -7885,34 +8047,6 @@
7885
8047
  `;
7886
8048
  }
7887
8049
 
7888
- function normalizeSessionMessagesPayload(data) {
7889
- if (Array.isArray(data)) {
7890
- return {
7891
- messages: data,
7892
- hasMore: false,
7893
- nextOffset: data.length,
7894
- total: data.length,
7895
- pageKind: 'messages',
7896
- conversationBudget: null,
7897
- };
7898
- }
7899
- const messages = Array.isArray(data?.messages)
7900
- ? data.messages
7901
- : (Array.isArray(data?.data) ? data.data : (Array.isArray(data?.items) ? data.items : []));
7902
- return {
7903
- messages,
7904
- hasMore: !!(data?.has_more || data?.hasMore),
7905
- nextOffset: Number.isFinite(Number(data?.next_offset ?? data?.nextOffset))
7906
- ? Number(data?.next_offset ?? data?.nextOffset)
7907
- : messages.length,
7908
- total: Number.isFinite(Number(data?.total)) ? Number(data.total) : messages.length,
7909
- pageKind: String(data?.page_kind || data?.pageKind || ''),
7910
- conversationBudget: data?.conversationBudget && typeof data.conversationBudget === 'object'
7911
- ? data.conversationBudget
7912
- : null,
7913
- };
7914
- }
7915
-
7916
8050
  function stripToolTokensForClassification(text) {
7917
8051
  return String(text || '').replace(/\[Tool(?: result)?:\s*[^\]]+\]/g, '').trim();
7918
8052
  }
@@ -7970,7 +8104,7 @@
7970
8104
  timeoutMs: MOBILE_DETAIL_REQUEST_TIMEOUT_MS,
7971
8105
  });
7972
8106
  if (!privateLeaseCurrent(lease)) throw privateScopeChangedError();
7973
- return normalizeSessionMessagesPayload(data);
8107
+ return window.CTMMobilePrivateApi.normalizeSessionMessagesPayload(data);
7974
8108
  }
7975
8109
 
7976
8110
  async function loadSessionMessagesWindow(card, opts = {}) {
@@ -11311,6 +11445,7 @@
11311
11445
  agentOf: phoneModelAgent,
11312
11446
  currentModelOf: (card) => (card ? String(card.model_id || card.model || '') : ''),
11313
11447
  modelSourceOf: (card) => String(card?.model_source || ''),
11448
+ modelProviderOf: (card) => String(card?.model_provider || card?.meta?.model_provider || ''),
11314
11449
  leadEffortOf: (card) => String(card?.model_effort || card?.meta?.model_effort || ''),
11315
11450
  modelStateOf: (card) => card?.model_state || card?.modelState || card?.meta?.model_state || null,
11316
11451
  isOnScreen: (id) => state.activeSession?.id === id,
@@ -11780,21 +11915,23 @@
11780
11915
  if (!mobilePrivateReady || !privateLeaseCurrent(lease)) return;
11781
11916
  const list = Array.from(files || []).filter(Boolean);
11782
11917
  if (!list.length) return;
11918
+ // WP-11 phone parity (AC-15c): refuse the image attach BEFORE any upload, and say why.
11919
+ if (requestedKind === 'image' && !sessionImageAttachAllowed(state.activeSession)) {
11920
+ setAttachmentComposerStatus(MOBILE_IMAGE_ATTACH_UNAVAILABLE, 'error', composerElement('input'));
11921
+ return;
11922
+ }
11783
11923
  const sessionId = state.activeSession?.id || '';
11784
11924
  const statusInput = composerElement('input');
11785
11925
  setAttachmentComposerStatus(`Attaching ${list.length} item${list.length === 1 ? '' : 's'}...`, '', statusInput);
11786
11926
  const pending = list.map((file) => ({
11787
11927
  id: `att-${++state.attachmentSeq}`,
11788
11928
  kind: attachmentKindForFile(file, requestedKind),
11789
- originalName: sanitizeAttachmentFilename(file.name,
11790
- requestedKind === 'image' ? 'phone-image.png' : 'phone-attachment.bin'),
11929
+ originalName: sanitizeAttachmentFilename(file.name, requestedKind === 'image' ? 'phone-image.png' : 'phone-attachment.bin'),
11791
11930
  size: file.size || 0,
11792
11931
  status: 'queued',
11793
11932
  progress: 0,
11794
11933
  error: '',
11795
- requestId: globalThis.crypto && typeof globalThis.crypto.randomUUID === 'function'
11796
- ? globalThis.crypto.randomUUID()
11797
- : `mobile-upload-${Date.now()}-${Math.random().toString(16).slice(2)}`,
11934
+ requestId: globalThis.crypto && typeof globalThis.crypto.randomUUID === 'function' ? globalThis.crypto.randomUUID() : `mobile-upload-${Date.now()}-${Math.random().toString(16).slice(2)}`,
11798
11935
  _uploadFile: file,
11799
11936
  previewUrl: attachmentKindForFile(file, requestedKind) === 'image' ? URL.createObjectURL(file) : '',
11800
11937
  }));
@@ -11817,23 +11954,15 @@
11817
11954
  }));
11818
11955
  if (!privateLeaseCurrent(lease)) return;
11819
11956
  if (state.activeSession?.id !== sessionId) {
11820
- const staleIds = new Set(pending.map((item) => item.id));
11821
- state.composerAttachments = state.composerAttachments.filter((item) => !staleIds.has(item.id));
11822
- for (const item of pending) {
11823
- if (item.previewUrl) {
11824
- try { URL.revokeObjectURL(item.previewUrl); } catch { /* expected: preview URLs may already be revoked */ }
11825
- }
11826
- }
11827
- renderComposerAttachments();
11828
- updateComposerState();
11829
- setComposerStatus('', '', statusInput);
11957
+ const staleIds = new Set(pending.map((item) => item.id)); state.composerAttachments = state.composerAttachments.filter((item) => !staleIds.has(item.id));
11958
+ for (const item of pending) if (item.previewUrl) { try { URL.revokeObjectURL(item.previewUrl); } catch { /* expected: preview URLs may already be revoked */ } }
11959
+ renderComposerAttachments(); updateComposerState(); setComposerStatus('', '', statusInput);
11830
11960
  return;
11831
11961
  }
11832
11962
  renderComposerAttachments();
11833
11963
  updateComposerState();
11834
- if (failures.length) {
11835
- setAttachmentComposerStatus(failures[0], 'error', statusInput);
11836
- } else {
11964
+ if (failures.length) setAttachmentComposerStatus(failures[0], 'error', statusInput);
11965
+ else {
11837
11966
  const names = pending.map((item) => item.originalName).join(', ');
11838
11967
  setAttachmentComposerStatus(`Attached ${names}.`, 'ok', statusInput);
11839
11968
  }
@@ -11978,7 +12107,8 @@
11978
12107
  option.disabled = state.composerSending || !state.activeSession;
11979
12108
  });
11980
12109
  document.querySelectorAll('[data-send-menu-attach]').forEach((option) => {
11981
- option.disabled = state.composerSending || !state.activeSession;
12110
+ const attachAllowed = option.dataset.sendMenuAttach !== 'image' || sessionImageAttachAllowed(state.activeSession); // WP-11/AC-15c: the image option is capability-gated
12111
+ option.disabled = state.composerSending || !state.activeSession || !attachAllowed; if (option.dataset.sendMenuAttach === 'image') option.title = attachAllowed ? 'Attach image' : MOBILE_IMAGE_ATTACH_UNAVAILABLE;
11982
12112
  });
11983
12113
  document.querySelectorAll('[data-walle-attach]').forEach((option) => {
11984
12114
  option.disabled = state.composerSending || !state.activeSession;
@@ -11992,6 +12122,8 @@
11992
12122
  if (/codex/.test(text)) return 'codex';
11993
12123
  if (/gemini/.test(text)) return 'gemini';
11994
12124
  if (/opencode|open-code/.test(text)) return 'opencode';
12125
+ // WP-7 (S-59): D-3 command word (never a substring such as `/x/dshell`).
12126
+ if (/^(?:\S*\/)?dsh(?:\s|$)/.test(text) || /deepseek/.test(text)) return 'dsh';
11995
12127
  if (/wall[- ]?e|walle/.test(text)) return 'walle';
11996
12128
  return text.trim();
11997
12129
  }
@@ -12046,6 +12178,8 @@
12046
12178
  const cwd = card.cwd || card.project || card.projectPath || '';
12047
12179
  if (agent) params.set('agent', agent);
12048
12180
  if (cwd) params.set('cwd', cwd);
12181
+ // remote-agent-skills WP-9 (S-9): a session on another machine gets that machine's skill list.
12182
+ if (card.id) params.set('sessionId', String(card.id));
12049
12183
  return `/api/skills/autocomplete?${params.toString()}`;
12050
12184
  }
12051
12185
 
@@ -13453,6 +13587,7 @@
13453
13587
  api,
13454
13588
  toast,
13455
13589
  loadMachines: (options) => loadMachines(options),
13590
+ afterRender: () => { phoneAccessMount(); phoneAccessTrustAllMount(); },
13456
13591
  openNewSessionSheet: (instanceId) => openNewSessionMachine(instanceId),
13457
13592
  confirm: (message) => window.confirm(message),
13458
13593
  now: () => Date.now(),
@@ -13463,7 +13598,305 @@
13463
13598
  // Renders (or re-renders) the Machines section inside #more-content. more.js owns that container and
13464
13599
  // rewrites it wholesale, so this runs again after each of its renders and the section node is reused.
13465
13600
  function renderPhoneMachinesCard() {
13466
- return phoneMachines()?.render($('more-content')) || null;
13601
+ const node = phoneMachines()?.render($('more-content')) || null;
13602
+ // WP-4 (AC-4d): PHONE = DESKTOP PARITY — the same Access block the desktop card renders (trust
13603
+ // select, active grants + Revoke, open asks + the 4-way grant card / Deny, the last 20 audit lines). The card
13604
+ // markup itself is public/m/machines.js's; this mounts the access section into each rendered card.
13605
+ phoneAccessBind();
13606
+ phoneAccessTrustAllMount();
13607
+ loadPhoneAccess(state.machines).then(() => { phoneAccessMount(); phoneAccessTrustAllMount(); }).catch(() => {});
13608
+ return node;
13609
+ }
13610
+
13611
+ // ── WP-4 phone access block (R-11 trust, R-4 grants, R-5 pending, R-9 audit, R-13/AC-4d) ───────
13612
+ // The phone twin of the desktop Machines page's Access block: the SAME shared markup, texts and
13613
+ // request bodies (public/js/machine-pool-shared.js — loaded by public/m/index.html before this
13614
+ // file), driven through the same routes, so a phone card and a desktop card can never disagree
13615
+ // about who may use this Mac. Only the class prefix and the delegated wiring are the phone's own.
13616
+ const PHONE_ACCESS_OPTS = { esc, classes: MACHINE_ACCESS_CLASSES_PHONE };
13617
+ const phoneAccessState = { trust: null, grants: [], pending: [], audit: {}, pendingIndex: {} };
13618
+
13619
+ /** R-11: a machine's effective policy — its own row, else the file default (shared rule). */
13620
+ function phoneAccessTrustValueFor(instanceId) {
13621
+ return machineEffectiveTrust(phoneAccessState.trust, instanceId);
13622
+ }
13623
+
13624
+ function phoneAccessGrantsFor(instanceId) {
13625
+ return machineAccessRowsFor(phoneAccessState.grants, instanceId);
13626
+ }
13627
+
13628
+ function phoneAccessPendingFor(instanceId) {
13629
+ return machineAccessRowsFor(phoneAccessState.pending, instanceId);
13630
+ }
13631
+
13632
+ function phoneAccessApply(trust, grants) {
13633
+ phoneAccessState.trust = trust && typeof trust === 'object'
13634
+ ? {
13635
+ v: 1,
13636
+ default: MACHINES_PAGE_TRUST_VALUES.indexOf(String(trust.default || '')) !== -1 ? String(trust.default) : MACHINES_PAGE_TRUST_DEFAULT,
13637
+ machines: (trust.machines && typeof trust.machines === 'object') ? trust.machines : {},
13638
+ }
13639
+ : null;
13640
+ const rows = (grants && Array.isArray(grants.grants)) ? grants.grants : [];
13641
+ const pending = (grants && Array.isArray(grants.pending)) ? grants.pending : [];
13642
+ phoneAccessState.grants = rows;
13643
+ phoneAccessState.pending = pending;
13644
+ phoneAccessState.pendingIndex = {};
13645
+ for (const row of pending) {
13646
+ if (row && row.id) phoneAccessState.pendingIndex[String(row.id)] = row;
13647
+ }
13648
+ return true;
13649
+ }
13650
+
13651
+ /** Trust + grants + one audit read per member. A hub without the routes leaves the block empty. */
13652
+ async function loadPhoneAccess(machines) {
13653
+ try {
13654
+ const [trust, grants] = await Promise.all([api('/api/hub-bridge/trust'), api('/api/hub-bridge/grants')]);
13655
+ phoneAccessApply(trust, grants);
13656
+ } catch {
13657
+ phoneAccessApply(null, null);
13658
+ }
13659
+ const list = Array.isArray(machines) ? machines : (Array.isArray(state.machines) ? state.machines : []);
13660
+ await Promise.all(list.filter((m) => m && m.instanceId).map(async (machine) => {
13661
+ const id = String(machine.instanceId);
13662
+ try {
13663
+ const out = await api(`/api/hub-bridge/audit?machineId=${encodeURIComponent(id)}&limit=${MACHINES_PAGE_AUDIT_LIMIT}`);
13664
+ phoneAccessState.audit[id] = (out && Array.isArray(out.entries)) ? out.entries : [];
13665
+ } catch {
13666
+ phoneAccessState.audit[id] = [];
13667
+ }
13668
+ }));
13669
+ return phoneAccessState;
13670
+ }
13671
+
13672
+ function phoneAccessTrustAllHtml() {
13673
+ return machineTrustAllHtml(phoneAccessState.trust, PHONE_ACCESS_OPTS);
13674
+ }
13675
+
13676
+ function phoneAccessBlockHtml(machine) {
13677
+ const id = String((machine && machine.instanceId) || '');
13678
+ if (!id) return '';
13679
+ return machineAccessBlockHtml(id, {
13680
+ trust: phoneAccessState.trust,
13681
+ grants: phoneAccessGrantsFor(id),
13682
+ pending: phoneAccessPendingFor(id),
13683
+ audit: Array.isArray(phoneAccessState.audit[id]) ? phoneAccessState.audit[id] : [],
13684
+ now: phoneMachinesNow(),
13685
+ }, PHONE_ACCESS_OPTS);
13686
+ }
13687
+
13688
+ function phoneAccessPendingRow(pendingId) {
13689
+ return phoneAccessState.pendingIndex[String(pendingId || '')] || null;
13690
+ }
13691
+
13692
+ /** Answer the ask: POST the grant its data-grant key names — the ask's own tier, scope and duration. */
13693
+ async function phoneAccessAllow(instanceId, pendingId, grant) {
13694
+ const row = phoneAccessPendingRow(pendingId);
13695
+ if (!row) { toast('That request is gone.'); return false; }
13696
+ try {
13697
+ await api(...hubBridgeAnswerCall(row, instanceId, grant));
13698
+ } catch {
13699
+ toast('Could not allow that request.');
13700
+ return false;
13701
+ }
13702
+ await phoneAccessRefresh();
13703
+ return true;
13704
+ }
13705
+
13706
+ async function phoneAccessDeny(pendingId) {
13707
+ const key = String(pendingId || '');
13708
+ if (!key) return false;
13709
+ try {
13710
+ await api(hubBridgeDenyPath(key), { method: 'POST', body: {} });
13711
+ } catch {
13712
+ toast('Could not deny that request.');
13713
+ return false;
13714
+ }
13715
+ await phoneAccessRefresh();
13716
+ return true;
13717
+ }
13718
+
13719
+ async function phoneAccessRevoke(instanceId, callerSessionId, tier) {
13720
+ try {
13721
+ await api('/api/hub-bridge/grants', { method: 'DELETE', body: hubBridgeRevokeBody(instanceId, callerSessionId, tier) });
13722
+ } catch {
13723
+ toast('Could not revoke that grant.');
13724
+ return false;
13725
+ }
13726
+ await phoneAccessRefresh();
13727
+ return true;
13728
+ }
13729
+
13730
+ async function phoneAccessTrustChange(instanceId, value) {
13731
+ try {
13732
+ await api('/api/hub-bridge/trust', { method: 'PUT', body: hubBridgeTrustBody(instanceId, value) });
13733
+ } catch {
13734
+ toast('Could not save that setting.');
13735
+ return false;
13736
+ }
13737
+ await phoneAccessRefresh();
13738
+ return true;
13739
+ }
13740
+
13741
+ async function phoneAccessTrustAll(on) {
13742
+ try {
13743
+ await api('/api/hub-bridge/trust', { method: 'PUT', body: hubBridgeTrustAllBody(on) });
13744
+ } catch {
13745
+ toast('Could not save that setting.');
13746
+ return false;
13747
+ }
13748
+ await phoneAccessRefresh();
13749
+ return true;
13750
+ }
13751
+
13752
+ async function phoneAccessRefresh() {
13753
+ await loadPhoneAccess(state.machines);
13754
+ phoneAccessMount();
13755
+ return true;
13756
+ }
13757
+
13758
+ /** Mount (or repaint) the access section on every member card the phone Machines page rendered. */
13759
+ function phoneAccessMount() {
13760
+ const content = $('more-content');
13761
+ if (!content || typeof content.querySelectorAll !== 'function') return 0;
13762
+ const cards = content.querySelectorAll('.mp-card[data-machine-id]') || [];
13763
+ let mounted = 0;
13764
+ for (let i = 0; i < cards.length; i += 1) {
13765
+ const card = cards[i];
13766
+ // This Mac is not a pool member: R-11's policy is per OTHER machine. The phone's self card
13767
+ // (`mp-self`, public/m/machines.js) DOES carry an instance id, so the skip is by class — the
13768
+ // same rule as the desktop page, whose self card has no trust select at all.
13769
+ if (String(card.className || '').split(/\s+/).includes('mp-self')) continue;
13770
+ const id = String((card.getAttribute && card.getAttribute('data-machine-id')) || card.dataset?.machineId || '');
13771
+ if (!id) continue;
13772
+ // The shared template IS the `.mp-access` element (machineAccessBlockHtml), so it goes inside a
13773
+ // plain mount node that a repaint rewrites — never inside another `.mp-access`.
13774
+ let node = typeof card.querySelector === 'function' ? card.querySelector('.mp-access-mount') : null;
13775
+ if (!node) {
13776
+ node = document.createElement('div');
13777
+ node.className = 'mp-access-mount';
13778
+ card.appendChild(node);
13779
+ }
13780
+ node.innerHTML = phoneAccessBlockHtml({ instanceId: id });
13781
+ mounted += 1;
13782
+ }
13783
+ return mounted;
13784
+ }
13785
+
13786
+ // ONE delegated pair of listeners for the whole phone surface: the More container is rewritten
13787
+ // wholesale and the session detail is a different subtree, so per-node listeners would be lost. The
13788
+ // hooks are the shared ones (public/js/machine-pool-shared.js), so the phone and the desktop bind
13789
+ // the same markup. Capture phase, so a control inside a card never also opens the card.
13790
+ function phoneAccessBind() {
13791
+ if (typeof document === 'undefined' || typeof document.addEventListener !== 'function') return false;
13792
+ if (document.__ctmPhoneAccessBound) return false;
13793
+ document.__ctmPhoneAccessBound = true;
13794
+ document.addEventListener('change', (event) => {
13795
+ const node = event.target && typeof event.target.closest === 'function'
13796
+ ? event.target.closest(HUB_BRIDGE_TRUST_HOOK) : null;
13797
+ if (!node || typeof node.getAttribute !== 'function') return;
13798
+ if (node.hasAttribute && node.hasAttribute('data-hub-bridge-trust-all')) { phoneAccessTrustAll(!!event.target.checked); return; }
13799
+ phoneAccessTrustChange(node.getAttribute('data-hub-bridge-trust'), event.target.value);
13800
+ }, true);
13801
+ document.addEventListener('click', (event) => {
13802
+ const node = event.target && typeof event.target.closest === 'function'
13803
+ ? event.target.closest(HUB_BRIDGE_ACTION_HOOK) : null;
13804
+ if (!node || typeof node.getAttribute !== 'function') return;
13805
+ if (typeof event.stopPropagation === 'function') event.stopPropagation();
13806
+ const action = node.getAttribute('data-hub-bridge');
13807
+ if (action === 'allow') phoneAccessAllow(node.getAttribute('data-machine-id'), node.getAttribute('data-pending-id'), node.getAttribute('data-grant'));
13808
+ else if (action === 'deny') phoneAccessDeny(node.getAttribute('data-pending-id'));
13809
+ else if (action === 'revoke') phoneAccessRevoke(node.getAttribute('data-machine-id'), node.getAttribute('data-session-id'), node.getAttribute('data-tier'));
13810
+ }, true);
13811
+ return true;
13812
+ }
13813
+
13814
+ // ── AC-2g on the phone: the same banner sentence, in the session detail ─────────────────────────
13815
+ /** The open ask that belongs to the session the detail view is showing, if there is one. */
13816
+ function phoneAccessBannerRow() {
13817
+ const sid = String(state.activeSession?.id || '');
13818
+ if (!sid) return null;
13819
+ for (const key of Object.keys(phoneAccessState.pendingIndex)) {
13820
+ const row = phoneAccessState.pendingIndex[key];
13821
+ if (row && String(row.callerSessionId || '') === sid) return row;
13822
+ }
13823
+ return null;
13824
+ }
13825
+
13826
+ /** The detail view's banner slot: beside #detail-alert, above the message list. */
13827
+ function phoneAccessBannerHost() {
13828
+ const alert = $('detail-alert');
13829
+ const messages = $('detail-messages');
13830
+ const host = (alert && alert.parentNode) || (messages && messages.parentNode) || null;
13831
+ return { host, before: (messages && messages.parentNode === host) ? messages : null };
13832
+ }
13833
+
13834
+ function phoneAccessPaintBanner() {
13835
+ const slot = phoneAccessBannerHost();
13836
+ if (!slot.host || typeof document === 'undefined' || typeof document.createElement !== 'function') return 0;
13837
+ const row = phoneAccessBannerRow();
13838
+ const existing = typeof slot.host.querySelector === 'function' ? slot.host.querySelector('.mp-approval-banner') : null;
13839
+ if (!row) {
13840
+ if (existing && existing.parentNode) existing.parentNode.removeChild(existing);
13841
+ return 0;
13842
+ }
13843
+ let node = existing;
13844
+ if (!node) {
13845
+ node = document.createElement('section');
13846
+ node.className = 'detail-alert mp-approval-banner';
13847
+ if (typeof slot.host.insertBefore === 'function' && slot.before) slot.host.insertBefore(node, slot.before);
13848
+ else if (typeof slot.host.appendChild === 'function') slot.host.appendChild(node);
13849
+ }
13850
+ node.hidden = false;
13851
+ node.setAttribute('data-pending-id', String(row.id || ''));
13852
+ node.innerHTML = hubBridgeBannerHtml(row, PHONE_ACCESS_OPTS);
13853
+ return 1;
13854
+ }
13855
+
13856
+ /** Re-read the open asks when the detail view opens, so a banner is there before any frame. */
13857
+ async function phoneAccessSeedBanners() {
13858
+ try {
13859
+ const grants = await api('/api/hub-bridge/grants');
13860
+ phoneAccessApply(phoneAccessState.trust, grants);
13861
+ } catch {
13862
+ // HANDLE: an unreadable hub seeds nothing; the next frame or card open re-reads.
13863
+ }
13864
+ phoneAccessPaintBanner();
13865
+ return true;
13866
+ }
13867
+
13868
+ /** The phone's half of the R-5 fan-out: repaint the banner, and the Access block when it is on screen. */
13869
+ function phoneAccessOnFrame(msg) {
13870
+ const type = String((msg && msg.type) || '');
13871
+ if (type === 'hub-bridge-pending') {
13872
+ const row = msg && msg.pending;
13873
+ if (row && row.id) phoneAccessState.pendingIndex[String(row.id)] = row;
13874
+ } else if (type === 'hub-bridge-pending-cleared') {
13875
+ for (const id of ((msg && Array.isArray(msg.ids)) ? msg.ids : [])) delete phoneAccessState.pendingIndex[String(id || '')];
13876
+ } else {
13877
+ return false;
13878
+ }
13879
+ phoneAccessPaintBanner();
13880
+ const content = $('more-content');
13881
+ if (content && typeof content.querySelectorAll === 'function' && content.querySelectorAll('.mp-card').length) {
13882
+ phoneAccessRefresh().catch(() => {});
13883
+ }
13884
+ return true;
13885
+ }
13886
+
13887
+ // The page-level "Trust all my machines" switch lives in the Machines header, which m/machines.js
13888
+ // renders; app.js has no header hook, so the switch is mounted beside the section card. Kept in the
13889
+ // module scope so the phone twin of the desktop header control has ONE home.
13890
+ function phoneAccessTrustAllMount() {
13891
+ const card = $('phone-machines-card');
13892
+ if (!card || typeof card.appendChild !== 'function') return false;
13893
+ if (typeof card.querySelector === 'function' && card.querySelector('.mp-access-trust-all')) return true;
13894
+ if (typeof document === 'undefined' || typeof document.createElement !== 'function') return false;
13895
+ const wrap = document.createElement('div');
13896
+ wrap.className = 'mp-toolbar';
13897
+ wrap.innerHTML = phoneAccessTrustAllHtml();
13898
+ card.appendChild(wrap);
13899
+ return true;
13467
13900
  }
13468
13901
 
13469
13902
  // ── WP-6 item 3: the picker — <select> at N <= 4, the search sheet above it ─────────────────────
@@ -13684,7 +14117,7 @@
13684
14117
  return;
13685
14118
  }
13686
14119
  if (String($('new-session-machine')?.value || '').trim() !== machine) return;
13687
- select.innerHTML = phoneProjectOptionsHtml(rows);
14120
+ select.innerHTML = phoneProjectOptionsHtml(rows); if (state.projectsError && (await loadProjects(), state.projectsError)) return phoneMachineSetHint(`Couldn't read the project lists right now — pick the folder on ${name}.`, true); // D-6: retry this device's list; a failed read is UNKNOWN, never "no checkout"
13688
14121
  const match = phoneSameRepoProject(state.projects, state.newSessionLocalCwd, rows);
13689
14122
  if (match) {
13690
14123
  select.value = match;
@@ -13704,6 +14137,9 @@
13704
14137
  const agentLabel = agentSelect.selectedOptions?.[0]?.textContent?.trim() || agent;
13705
14138
  const prompt = $('new-session-prompt').value.trim();
13706
14139
  const isWalle = agent === 'walle';
14140
+ // WP-7 (S-60): the DeepSeek Harness is a conv-only ACP agent — same create frame as desktop
14141
+ // (`cmd:'dsh'`, `agentType:'dsh'`, no CLI args). Its first turn is typed in the composer.
14142
+ const isDsh = agent === 'dsh';
13707
14143
  const cmd = agent === 'codex'
13708
14144
  ? 'codex'
13709
14145
  : agent === 'gemini'
@@ -13712,12 +14148,12 @@
13712
14148
  ? undefined
13713
14149
  : isWalle
13714
14150
  ? 'walle'
13715
- : 'claude';
13716
- const args = prompt && !isWalle ? [prompt] : [];
14151
+ : isDsh ? 'dsh' : 'claude';
14152
+ const args = prompt && !isWalle && !isDsh ? [prompt] : [];
13717
14153
  // WP-5 (S-12/S-16): the machine chosen in the sheet travels with the create frame as `machine`;
13718
14154
  // '' is this Mac and the field is omitted, so a local create frame is unchanged.
13719
14155
  const machine = ($('new-session-machine')?.value || '').trim();
13720
- const request = Object.freeze({ cwd, agent, agentLabel, prompt, isWalle, cmd, args: Object.freeze(args.slice()), machine });
14156
+ const request = Object.freeze({ cwd, agent, agentLabel, prompt, isWalle, isDsh, cmd, args: Object.freeze(args.slice()), machine });
13721
14157
  const action = async () => {
13722
14158
  if (!privateLeaseCurrent(lease) || state.pendingStepUp?.action !== action) throw privateScopeChangedError();
13723
14159
  const id = randomId();
@@ -13730,6 +14166,7 @@
13730
14166
  });
13731
14167
  if (request.prompt) payload.initialMessage = request.prompt;
13732
14168
  }
14169
+ if (request.isDsh) Object.assign(payload, { label: request.agentLabel || 'DeepSeek Harness', agentType: 'dsh' });
13733
14170
  await sendWs(payload);
13734
14171
  if (!privateLeaseCurrent(lease) || state.pendingStepUp?.action !== action) throw privateScopeChangedError();
13735
14172
  $('new-session-sheet').hidden = true;
@@ -13892,6 +14329,8 @@
13892
14329
  });
13893
14330
  $('refresh-btn').addEventListener('click', refresh);
13894
14331
  $('app-reload-now-btn')?.addEventListener('click', () => reloadForInstalledUpdate('banner'));
14332
+ // WP-2 (AC-2b): the same one action the desktop banner's Resume button posts.
14333
+ $('host-dead-resume-btn')?.addEventListener('click', () => { void resumeHostDeadSessions(); });
13895
14334
  $('detail-back').addEventListener('click', (event) => {
13896
14335
  event.preventDefault();
13897
14336
  event.stopPropagation();