create-walle 0.10.1 → 0.10.2

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 (341) hide show
  1. package/README.md +4 -0
  2. package/lib/owner-write-safety.js +135 -0
  3. package/lib/public-runtime.js +13 -5
  4. package/lib/release-schema.json +3 -2
  5. package/lib/runtime-closure-policy.json +6 -4
  6. package/package.json +1 -1
  7. package/template/bin/deps-doctor.js +1 -1
  8. package/template/bin/node-bin.sh +11 -0
  9. package/template/coding-task-manager/api-prompts.js +10 -22
  10. package/template/coding-task-manager/bin/claude-hook.js +7 -3
  11. package/template/coding-task-manager/bin/claude-table-parity.js +307 -0
  12. package/template/coding-task-manager/bin/conversation-storage-auto-migration-lib.js +16 -1
  13. package/template/coding-task-manager/bin/conversation-storage-generation-evidence.js +9 -2
  14. package/template/coding-task-manager/bin/conversation-storage-service-entry-lib.js +16 -1
  15. package/template/coding-task-manager/bin/conversation-storage-walle-owner.js +247 -0
  16. package/template/coding-task-manager/bin/ctm-machine-pair.js +160 -0
  17. package/template/coding-task-manager/bin/lead-wait-census.js +1348 -0
  18. package/template/coding-task-manager/bin/repair-foreign-clone.js +564 -0
  19. package/template/coding-task-manager/bin/repair-resume-echo-titles.js +248 -0
  20. package/template/coding-task-manager/bin/write-lock-bench.js +111 -0
  21. package/template/coding-task-manager/db.js +705 -286
  22. package/template/coding-task-manager/git-utils.js +44 -0
  23. package/template/coding-task-manager/lib/adopted-host-redial.js +4 -0
  24. package/template/coding-task-manager/lib/agent-presets.js +15 -0
  25. package/template/coding-task-manager/lib/agent-title-apply.js +88 -0
  26. package/template/coding-task-manager/lib/agent-title-backfill-read.js +92 -0
  27. package/template/coding-task-manager/lib/api/host-stats.js +98 -0
  28. package/template/coding-task-manager/lib/api/ingress.js +35 -12
  29. package/template/coding-task-manager/lib/api/machine-pairing.js +285 -0
  30. package/template/coding-task-manager/lib/api/machine-pool.js +307 -0
  31. package/template/coding-task-manager/lib/api/machines.js +461 -0
  32. package/template/coding-task-manager/lib/api/misc-ops.js +20 -5
  33. package/template/coding-task-manager/lib/api/providers.js +53 -6
  34. package/template/coding-task-manager/lib/api/resume-from-store.js +25 -0
  35. package/template/coding-task-manager/lib/api/sessions.js +160 -8
  36. package/template/coding-task-manager/lib/api/transcript-history.js +17 -4
  37. package/template/coding-task-manager/lib/api/worktrees.js +38 -37
  38. package/template/coding-task-manager/lib/approval-drift.js +16 -1
  39. package/template/coding-task-manager/lib/approval-logical-frame.js +5 -1
  40. package/template/coding-task-manager/lib/approval-scope.js +68 -4
  41. package/template/coding-task-manager/lib/auth-context.js +32 -2
  42. package/template/coding-task-manager/lib/auth-rate-limit.js +41 -1
  43. package/template/coding-task-manager/lib/auth-rules.js +186 -45
  44. package/template/coding-task-manager/lib/broadcast-serialize.js +2 -1
  45. package/template/coding-task-manager/lib/browser-session-broker.js +9 -1
  46. package/template/coding-task-manager/lib/browser-use-adapter.js +33 -2
  47. package/template/coding-task-manager/lib/claude-markdown-ansi.js +27 -85
  48. package/template/coding-task-manager/lib/claude-session-title.js +6 -30
  49. package/template/coding-task-manager/lib/claude-table-layout.js +322 -0
  50. package/template/coding-task-manager/lib/client-diag-handler.js +5 -0
  51. package/template/coding-task-manager/lib/codex-config-guard.js +33 -3
  52. package/template/coding-task-manager/lib/codex-harness-qualification.js +12 -0
  53. package/template/coding-task-manager/lib/codex-launch-health.js +29 -2
  54. package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
  55. package/template/coding-task-manager/lib/codex-paths.js +2 -1
  56. package/template/coding-task-manager/lib/coding-agent-models.js +21 -30
  57. package/template/coding-task-manager/lib/coding-session-model-adapters.js +27 -2
  58. package/template/coding-task-manager/lib/coding-session-model-projection.js +5 -0
  59. package/template/coding-task-manager/lib/coding-session-model-runtime.js +1 -2
  60. package/template/coding-task-manager/lib/coding-session-model-state.js +10 -0
  61. package/template/coding-task-manager/lib/coding-session-model-sync-state.js +0 -1
  62. package/template/coding-task-manager/lib/conversation-import/cursor-guard-owner.js +10 -0
  63. package/template/coding-task-manager/lib/create/resolve-launch-params.js +8 -1
  64. package/template/coding-task-manager/lib/ctm-boot-lifecycle.js +17 -1
  65. package/template/coding-task-manager/lib/db-domain-tables.js +7 -0
  66. package/template/coding-task-manager/lib/db-domains.js +21 -2
  67. package/template/coding-task-manager/lib/db-owner-op-registry.js +7 -0
  68. package/template/coding-task-manager/lib/db-schema-source-files.js +1 -0
  69. package/template/coding-task-manager/lib/feature-manifest.js +1 -1
  70. package/template/coding-task-manager/lib/fs-watcher.js +3 -2
  71. package/template/coding-task-manager/lib/harness-history-transition.js +3 -3
  72. package/template/coding-task-manager/lib/headless-inflight-ops.js +203 -0
  73. package/template/coding-task-manager/lib/headless-missing-reply.js +42 -0
  74. package/template/coding-task-manager/lib/headless-term-service.js +55 -17
  75. package/template/coding-task-manager/lib/host-control-dial.js +167 -0
  76. package/template/coding-task-manager/lib/host-handoff-orchestrator.js +16 -2
  77. package/template/coding-task-manager/lib/host-handoff.js +51 -13
  78. package/template/coding-task-manager/lib/host-health-ledger.js +164 -12
  79. package/template/coding-task-manager/lib/host-self-truth-watchdog.js +4 -0
  80. package/template/coding-task-manager/lib/host-stats.js +328 -0
  81. package/template/coding-task-manager/lib/host-threadpool-evacuation.js +160 -0
  82. package/template/coding-task-manager/lib/host-threadpool-monitor.js +14 -1
  83. package/template/coding-task-manager/lib/hosted-input-dispatch.js +203 -0
  84. package/template/coding-task-manager/lib/hosted-sync-worker.js +113 -5
  85. package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +4 -0
  86. package/template/coding-task-manager/lib/instance-manifest-runtime.js +3 -0
  87. package/template/coding-task-manager/lib/instance-manifest.js +13 -1
  88. package/template/coding-task-manager/lib/instance-sync-status.js +15 -0
  89. package/template/coding-task-manager/lib/launch-model-override.js +160 -1
  90. package/template/coding-task-manager/lib/launch-plan-worker.js +59 -2
  91. package/template/coding-task-manager/lib/lead-model-switch-runtime.js +57 -4
  92. package/template/coding-task-manager/lib/lead-model-switch.js +3 -2
  93. package/template/coding-task-manager/lib/lead-model-transition-store.js +61 -3
  94. package/template/coding-task-manager/lib/legacy-draft-cancellation-recovery.js +111 -0
  95. package/template/coding-task-manager/lib/login-tripwire.js +1 -0
  96. package/template/coding-task-manager/lib/machine-pairing.js +238 -0
  97. package/template/coding-task-manager/lib/machine-pool.js +409 -0
  98. package/template/coding-task-manager/lib/machine-registry.js +749 -0
  99. package/template/coding-task-manager/lib/machine-self.js +66 -0
  100. package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +52 -4
  101. package/template/coding-task-manager/lib/media-journal-control-reader.js +473 -0
  102. package/template/coding-task-manager/lib/media-journal.js +84 -27
  103. package/template/coding-task-manager/lib/media-store-options.js +43 -15
  104. package/template/coding-task-manager/lib/migrations/v56-host-samples-and-machine-pool.js +76 -0
  105. package/template/coding-task-manager/lib/mirror-feed-sanitize.js +12 -3
  106. package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
  107. package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
  108. package/template/coding-task-manager/lib/model-proxy/handoff.js +494 -0
  109. package/template/coding-task-manager/lib/model-proxy/lead-worker-picks.js +25 -0
  110. package/template/coding-task-manager/lib/model-proxy/native-lead-listener.js +61 -7
  111. package/template/coding-task-manager/lib/model-proxy/proxy-cli.js +200 -0
  112. package/template/coding-task-manager/lib/model-proxy/proxy-instance-record.js +257 -0
  113. package/template/coding-task-manager/lib/model-proxy/stream-accounting.js +205 -0
  114. package/template/coding-task-manager/lib/model-proxy/tokens.js +78 -0
  115. package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
  116. package/template/coding-task-manager/lib/native-history-control.js +2 -2
  117. package/template/coding-task-manager/lib/native-lead-settings-client.js +2 -1
  118. package/template/coding-task-manager/lib/native-lead-tls.js +18 -4
  119. package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +628 -64
  120. package/template/coding-task-manager/lib/owner-action-manifest-data.json +14 -0
  121. package/template/coding-task-manager/lib/painted-row-recorder.js +9 -1
  122. package/template/coding-task-manager/lib/prompt-submission-contract.js +21 -1
  123. package/template/coding-task-manager/lib/provider-login-broker.js +192 -9
  124. package/template/coding-task-manager/lib/provider-login-capture.js +153 -0
  125. package/template/coding-task-manager/lib/queue-draft-status-overlay.js +36 -0
  126. package/template/coding-task-manager/lib/raw-codex-history-window.js +5 -3
  127. package/template/coding-task-manager/lib/read-pool-client.js +5 -2
  128. package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
  129. package/template/coding-task-manager/lib/recent-catalog-query.js +26 -0
  130. package/template/coding-task-manager/lib/recent-session-catalog-response.js +9 -0
  131. package/template/coding-task-manager/lib/remote-command-context.js +12 -0
  132. package/template/coding-task-manager/lib/remote-command-runtime.js +13 -1
  133. package/template/coding-task-manager/lib/remote-machine-catalog.js +1062 -0
  134. package/template/coding-task-manager/lib/remote-machine-create.js +693 -0
  135. package/template/coding-task-manager/lib/remote-machine-link.js +939 -0
  136. package/template/coding-task-manager/lib/remote-machine-proxy.js +520 -0
  137. package/template/coding-task-manager/lib/remote-session-forward.js +306 -0
  138. package/template/coding-task-manager/lib/remote-submission-status.js +6 -0
  139. package/template/coding-task-manager/lib/restart-outcome-truth.js +20 -3
  140. package/template/coding-task-manager/lib/restart-preflight.js +9 -2
  141. package/template/coding-task-manager/lib/restore-backoff-gate.js +10 -2
  142. package/template/coding-task-manager/lib/restore-prepare-retry.js +12 -5
  143. package/template/coding-task-manager/lib/restore-resume-batch.js +22 -1
  144. package/template/coding-task-manager/lib/resume-blocked-wire.js +62 -1
  145. package/template/coding-task-manager/lib/resume-cwd.js +13 -0
  146. package/template/coding-task-manager/lib/resume-from-store.js +12 -1
  147. package/template/coding-task-manager/lib/resume-origin-facts.js +38 -1
  148. package/template/coding-task-manager/lib/resume-preflight.js +21 -16
  149. package/template/coding-task-manager/lib/review-repo-admission.js +39 -6
  150. package/template/coding-task-manager/lib/review-repo-registry.js +17 -0
  151. package/template/coding-task-manager/lib/root-delivery-runtime.js +72 -15
  152. package/template/coding-task-manager/lib/root-delivery-selection.js +37 -1
  153. package/template/coding-task-manager/lib/root-prompt-admission.js +33 -2
  154. package/template/coding-task-manager/lib/root-prompt-flow.js +167 -33
  155. package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
  156. package/template/coding-task-manager/lib/session-analysis-memory.js +2 -0
  157. package/template/coding-task-manager/lib/session-create/codex-native-provider-gate.js +218 -0
  158. package/template/coding-task-manager/lib/session-create/codex-resume-exit-classify.js +52 -0
  159. package/template/coding-task-manager/lib/session-create/delegate-worker.js +95 -6
  160. package/template/coding-task-manager/lib/session-create/identity.js +20 -1
  161. package/template/coding-task-manager/lib/session-create/prepared.js +69 -1
  162. package/template/coding-task-manager/lib/session-create/provider-launch-context.js +77 -0
  163. package/template/coding-task-manager/lib/session-create/provider-process.js +34 -37
  164. package/template/coding-task-manager/lib/session-create/response.js +30 -12
  165. package/template/coding-task-manager/lib/session-create/transcript.js +13 -4
  166. package/template/coding-task-manager/lib/session-create/worker-continuation.js +43 -1
  167. package/template/coding-task-manager/lib/session-create/worker-notices.js +94 -5
  168. package/template/coding-task-manager/lib/session-create/worker-observability.js +99 -7
  169. package/template/coding-task-manager/lib/session-create/worker-stop.js +11 -3
  170. package/template/coding-task-manager/lib/session-create/worker-wait-watcher.js +315 -0
  171. package/template/coding-task-manager/lib/session-geometry-authority-handler.js +10 -1
  172. package/template/coding-task-manager/lib/session-host-manager.js +152 -206
  173. package/template/coding-task-manager/lib/session-jobs.js +17 -9
  174. package/template/coding-task-manager/lib/session-lifecycle.js +18 -0
  175. package/template/coding-task-manager/lib/session-model-action-preconditions.js +9 -0
  176. package/template/coding-task-manager/lib/session-resource-sampler.js +19 -9
  177. package/template/coding-task-manager/lib/session-standup.js +67 -4
  178. package/template/coding-task-manager/lib/session-status-ledger.js +42 -3
  179. package/template/coding-task-manager/lib/session-status-runtime.js +45 -1
  180. package/template/coding-task-manager/lib/session-title-mirror.js +25 -57
  181. package/template/coding-task-manager/lib/session-transcript-trash.js +8 -1
  182. package/template/coding-task-manager/lib/startup-pointer-write.js +57 -0
  183. package/template/coding-task-manager/lib/state-sync/painted-frame-field.js +11 -0
  184. package/template/coding-task-manager/lib/storage/coding-session-model-store.js +21 -22
  185. package/template/coding-task-manager/lib/surviving-owner-bootstrap.js +7 -2
  186. package/template/coding-task-manager/lib/surviving-owner-recovery.js +2 -1
  187. package/template/coding-task-manager/lib/terminal-draft-http-status.js +25 -0
  188. package/template/coding-task-manager/lib/transcript-archiver.js +229 -229
  189. package/template/coding-task-manager/lib/transcript-frame-cache.js +12 -2
  190. package/template/coding-task-manager/lib/transcript-guard-job.js +55 -4
  191. package/template/coding-task-manager/lib/transcript-link.js +77 -0
  192. package/template/coding-task-manager/lib/transcript-manifest-store.js +8 -4
  193. package/template/coding-task-manager/lib/transcript-source.js +8 -3
  194. package/template/coding-task-manager/lib/transcript-store.js +40 -0
  195. package/template/coding-task-manager/lib/tunnel-lane-governor.js +6 -3
  196. package/template/coding-task-manager/lib/typing-interaction-trace.js +92 -0
  197. package/template/coding-task-manager/lib/typing-worker-stages.js +60 -0
  198. package/template/coding-task-manager/lib/visible-revision-watchdog.js +8 -12
  199. package/template/coding-task-manager/lib/wake-skip-reasons.js +46 -0
  200. package/template/coding-task-manager/lib/walle-supervisor.js +98 -14
  201. package/template/coding-task-manager/lib/worker-failure-classify.js +13 -1
  202. package/template/coding-task-manager/lib/ws/state-sync.js +19 -2
  203. package/template/coding-task-manager/lib/ws/terminal-attach.js +32 -1
  204. package/template/coding-task-manager/lib/ws-router.js +68 -1
  205. package/template/coding-task-manager/lib/ws-send-admission.js +22 -4
  206. package/template/coding-task-manager/oauth-proxy.js +99 -55
  207. package/template/coding-task-manager/package-lock.json +24 -0
  208. package/template/coding-task-manager/package.json +3 -0
  209. package/template/coding-task-manager/providers/codex-mcp-extract.js +121 -19
  210. package/template/coding-task-manager/providers/codex-mcp.js +14 -5
  211. package/template/coding-task-manager/public/css/app.css +279 -1
  212. package/template/coding-task-manager/public/css/reviews.css +4 -0
  213. package/template/coding-task-manager/public/index.html +294 -223
  214. package/template/coding-task-manager/public/js/app/machines-page.js +1252 -0
  215. package/template/coding-task-manager/public/js/app/models-tab.js +546 -36
  216. package/template/coding-task-manager/public/js/app/worktrees-panel.js +7 -5
  217. package/template/coding-task-manager/public/js/background-work.js +13 -7
  218. package/template/coding-task-manager/public/js/coding-session-model-presentation.js +44 -4
  219. package/template/coding-task-manager/public/js/core/session-commands.js +676 -0
  220. package/template/coding-task-manager/public/js/core/session-selectors.js +5 -1
  221. package/template/coding-task-manager/public/js/history-app-seam.js +26 -2
  222. package/template/coding-task-manager/public/js/history-painted-rows.js +17 -2
  223. package/template/coding-task-manager/public/js/history-reader-park.js +13 -1
  224. package/template/coding-task-manager/public/js/history-scroller.js +187 -9
  225. package/template/coding-task-manager/public/js/history-transcript-pages.js +5 -5
  226. package/template/coding-task-manager/public/js/machine-pool-shared.js +436 -0
  227. package/template/coding-task-manager/public/js/private-state-scope.js +31 -3
  228. package/template/coding-task-manager/public/js/prompt-submission-client.js +173 -8
  229. package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
  230. package/template/coding-task-manager/public/js/queue-panel-submission-reconcile.js +174 -1
  231. package/template/coding-task-manager/public/js/reviews.js +7 -3
  232. package/template/coding-task-manager/public/js/root-draft-lifetime.js +139 -2
  233. package/template/coding-task-manager/public/js/root-queue-builder-lifetime.js +16 -0
  234. package/template/coding-task-manager/public/js/row-join.js +4 -2
  235. package/template/coding-task-manager/public/js/session-model-picker.js +1 -1
  236. package/template/coding-task-manager/public/js/session-search-utils.js +46 -19
  237. package/template/coding-task-manager/public/js/state-sync-client.js +4 -0
  238. package/template/coding-task-manager/public/js/terminal/runtime-02.js +16 -7
  239. package/template/coding-task-manager/public/js/terminal/runtime-03.js +41 -0
  240. package/template/coding-task-manager/public/js/terminal/runtime-04.js +41 -4
  241. package/template/coding-task-manager/public/js/terminal/runtime-12.js +52 -2
  242. package/template/coding-task-manager/public/js/terminal/runtime-16.js +12 -12
  243. package/template/coding-task-manager/public/js/terminal/runtime-18.js +14 -0
  244. package/template/coding-task-manager/public/js/terminal/runtime-19.js +8 -4
  245. package/template/coding-task-manager/public/js/terminal/runtime-20.js +210 -73
  246. package/template/coding-task-manager/public/js/terminal/runtime-21.js +19 -6
  247. package/template/coding-task-manager/public/js/terminal/runtime-22.js +86 -0
  248. package/template/coding-task-manager/public/js/terminal-history-viewport.js +1 -1
  249. package/template/coding-task-manager/public/js/terminal-wheel.js +39 -2
  250. package/template/coding-task-manager/public/js/walle/chat-conversation.js +11 -3
  251. package/template/coding-task-manager/public/js/walle-session.js +30 -7
  252. package/template/coding-task-manager/public/m/app.css +226 -8
  253. package/template/coding-task-manager/public/m/app.js +502 -9
  254. package/template/coding-task-manager/public/m/index.html +38 -1
  255. package/template/coding-task-manager/public/m/machines.js +1313 -0
  256. package/template/coding-task-manager/public/m/pool-failover.js +391 -0
  257. package/template/coding-task-manager/public/m/private-queue-actions.js +26 -7
  258. package/template/coding-task-manager/public/m/sw.js +5 -0
  259. package/template/coding-task-manager/queue-engine.js +13 -0
  260. package/template/coding-task-manager/server/approvals.js +5 -6
  261. package/template/coding-task-manager/server/boot.js +136 -17
  262. package/template/coding-task-manager/server.js +902 -239
  263. package/template/coding-task-manager/session-integrity.js +7 -9
  264. package/template/coding-task-manager/workers/db-owner-worker.js +53 -7
  265. package/template/coding-task-manager/workers/read-pool-worker.js +17 -6
  266. package/template/coding-task-manager/workers/session-host-pool-process.js +18 -4
  267. package/template/coding-task-manager/workers/session-host-process.js +14 -3
  268. package/template/coding-task-manager/workers/transcript-archive-worker.js +1 -1
  269. package/template/coding-task-manager/workers/worktree-worker.js +34 -0
  270. package/template/package-lock.json +2 -2
  271. package/template/package.json +1 -1
  272. package/template/scripts/conversation-storage-v2-migration.js +78 -11
  273. package/template/shared/conversation-storage-v2-contract.js +42 -6
  274. package/template/shared/fixtures/worker-status-payloads.json +392 -0
  275. package/template/shared/git-remote-key.js +83 -0
  276. package/template/shared/host-identity.js +98 -2
  277. package/template/shared/owner-write-safety.js +135 -0
  278. package/template/shared/public-runtime.js +13 -5
  279. package/template/shared/release-schema.json +3 -2
  280. package/template/shared/report-citations.js +14 -0
  281. package/template/shared/sqlite-write-lock.js +361 -223
  282. package/template/shared/storage/contract.js +35 -3
  283. package/template/wall-e/agent.js +21 -0
  284. package/template/wall-e/api-walle.js +5 -0
  285. package/template/wall-e/bin/brain-purge-legacy-ctm.js +3 -2
  286. package/template/wall-e/bin/walle-mcp-stdio.js +12 -6
  287. package/template/wall-e/brain/helper-rules-schema.js +129 -0
  288. package/template/wall-e/brain/migrations.js +51 -22
  289. package/template/wall-e/brain/model-deployment-facts.js +31 -1
  290. package/template/wall-e/brain/owner-call-whitelist.js +16 -0
  291. package/template/wall-e/brain/schema-ensures.js +22 -0
  292. package/template/wall-e/brain.js +1 -1
  293. package/template/wall-e/coding/journal-dispatcher.js +51 -0
  294. package/template/wall-e/coding/session-journal.js +28 -40
  295. package/template/wall-e/coding-orchestrator.js +23 -26
  296. package/template/wall-e/http/auth.js +18 -1
  297. package/template/wall-e/http/blog-views-api.js +318 -0
  298. package/template/wall-e/http/helper-rules-api.js +321 -0
  299. package/template/wall-e/http/model-proxy-providers.js +3 -1
  300. package/template/wall-e/http/telemetry-api.js +12 -0
  301. package/template/wall-e/lib/blog-view-policy.js +88 -0
  302. package/template/wall-e/lib/blog-view-store.js +169 -0
  303. package/template/wall-e/lib/client-config-lock.js +2 -1
  304. package/template/wall-e/lib/coding-journal-writer-child-client.js +67 -18
  305. package/template/wall-e/lib/error-kinds.js +10 -1
  306. package/template/wall-e/lib/feature-manifest.js +1 -1
  307. package/template/wall-e/lib/helper-rules/cell-state.js +123 -0
  308. package/template/wall-e/lib/helper-rules/evaluate.js +357 -0
  309. package/template/wall-e/lib/helper-rules/explore-rubric.md +12 -0
  310. package/template/wall-e/lib/helper-rules/grade.js +277 -0
  311. package/template/wall-e/lib/helper-rules/label.js +247 -0
  312. package/template/wall-e/lib/helper-rules/lead-call-hook.js +522 -0
  313. package/template/wall-e/lib/helper-rules/store.js +667 -0
  314. package/template/wall-e/lib/mcp-integration.js +5 -6
  315. package/template/wall-e/lib/runtime-worker-pool.js +1 -0
  316. package/template/wall-e/lib/scheduler-worker-jobs.js +6 -0
  317. package/template/wall-e/lib/telemetry-db-schema.js +37 -0
  318. package/template/wall-e/lib/telemetry-process-client.js +76 -1
  319. package/template/wall-e/llm/default-fallback.js +4 -0
  320. package/template/wall-e/llm/model-catalog.js +13 -4
  321. package/template/wall-e/llm/model-roles.js +6 -0
  322. package/template/wall-e/llm/portkey-http.js +150 -0
  323. package/template/wall-e/llm/portkey-sync.js +15 -145
  324. package/template/wall-e/llm/worker-mcp-routing.js +61 -0
  325. package/template/wall-e/llm/worker-route.js +43 -1
  326. package/template/wall-e/loops/brain-custom-writes.js +45 -0
  327. package/template/wall-e/mcp-protocol-contract.js +41 -1
  328. package/template/wall-e/mcp-server.js +13 -2
  329. package/template/wall-e/mcp-worker-schemas.js +20 -2
  330. package/template/wall-e/memory/ctm-context-client.js +25 -5
  331. package/template/wall-e/shared/release-schema.json +3 -2
  332. package/template/wall-e/shared/sqlite-write-lock.js +361 -223
  333. package/template/wall-e/shared/storage/contract.js +35 -3
  334. package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
  335. package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +6 -0
  336. package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +7 -2
  337. package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +7 -2
  338. package/template/wall-e/tools/local-tools.js +6 -4
  339. package/template/wall-e/workers/brain-owner-worker.js +9 -0
  340. package/template/wall-e/workers/coding-journal-writer-child.js +8 -1
  341. package/template/wall-e/workers/telemetry-worker.js +8 -0
@@ -6,6 +6,7 @@ const http = require('http');
6
6
  const path = require('path');
7
7
  const { spawn } = require('child_process');
8
8
  const { resolveRealNode } = require('./real-node');
9
+ const { isWritableOnlyByOwner } = require('../../shared/owner-write-safety');
9
10
  const {
10
11
  assertNotReserved,
11
12
  withClientConfigLock,
@@ -1339,16 +1340,15 @@ function _ensurePrivateDirectory(dirPath, homeDir) {
1339
1340
  stat = fs.lstatSync(current, { bigint: true });
1340
1341
  }
1341
1342
  if (stat.isSymbolicLink() || !stat.isDirectory()) throw new ClientConfigError('CLIENT_CONFIG_OWNERSHIP_LOST');
1342
- const mode = Number(stat.mode & 0o777n);
1343
1343
  const owned = current === ownedRoot || current.startsWith(`${ownedRoot}${path.sep}`);
1344
1344
  if (owned) {
1345
- if (stat.uid !== BigInt(process.getuid()) || (mode & 0o022) !== 0) {
1345
+ if (stat.uid !== BigInt(process.getuid()) || !isWritableOnlyByOwner(stat)) {
1346
1346
  throw new ClientConfigError('CLIENT_CONFIG_OWNERSHIP_LOST');
1347
1347
  }
1348
1348
  continue;
1349
1349
  }
1350
1350
  if (stat.uid === 0n) continue; // system ancestor above the owned boundary
1351
- if (stat.uid === BigInt(process.getuid()) && (mode & 0o022) === 0) continue;
1351
+ if (stat.uid === BigInt(process.getuid()) && isWritableOnlyByOwner(stat)) continue;
1352
1352
  throw new ClientConfigError('CLIENT_CONFIG_OWNERSHIP_LOST');
1353
1353
  }
1354
1354
  return dirPath;
@@ -1418,15 +1418,14 @@ function _admitHistoryDirectoryChain(dirPath, homeDir) {
1418
1418
  throw new ClientConfigError('CLIENT_CONFIG_OWNERSHIP_LOST');
1419
1419
  }
1420
1420
  if (stat.isSymbolicLink() || !stat.isDirectory()) throw new ClientConfigError('CLIENT_CONFIG_OWNERSHIP_LOST');
1421
- const mode = Number(stat.mode & 0o777n);
1422
1421
  if (current === ownedRoot || current.startsWith(`${ownedRoot}${path.sep}`)) {
1423
- if (stat.uid !== BigInt(process.getuid()) || (mode & 0o022) !== 0) {
1422
+ if (stat.uid !== BigInt(process.getuid()) || !isWritableOnlyByOwner(stat)) {
1424
1423
  throw new ClientConfigError('CLIENT_CONFIG_OWNERSHIP_LOST');
1425
1424
  }
1426
1425
  continue;
1427
1426
  }
1428
1427
  if (stat.uid === 0n) continue; // system ancestor above the owned boundary
1429
- if (stat.uid === BigInt(process.getuid()) && (mode & 0o022) === 0) continue;
1428
+ if (stat.uid === BigInt(process.getuid()) && isWritableOnlyByOwner(stat)) continue;
1430
1429
  throw new ClientConfigError('CLIENT_CONFIG_OWNERSHIP_LOST');
1431
1430
  }
1432
1431
  return true;
@@ -48,6 +48,7 @@ const SCHEDULER_JOB_LANES = Object.freeze({
48
48
  'telemetry:cleanup': 'maintenance',
49
49
  'agent-benchmark': 'llm_background',
50
50
  'weekly-eval-loop': 'llm_background',
51
+ 'helper-rules-probe': 'maintenance', // WP-5b §6.8: brain-write-only daily tick (same class as brain-retention)
51
52
  'deferred:embeddings-init': 'embedding',
52
53
  'deferred:temporal-backfill': 'maintenance',
53
54
  'deferred:skill-reference-sync': 'io',
@@ -244,6 +244,11 @@ async function runSchedulerJob(jobName, payload = {}) {
244
244
  result = await _measurePhase(phases, 'weeklyEval.run', () => runWeeklyEval(brain, { runAgentLoop }));
245
245
  break;
246
246
  }
247
+ case 'helper-rules-probe':
248
+ // WP-5b §6.8: this worker's brain handle is read-only, so the helper-rules record's
249
+ // daily probe crosses to the owner through _brainWrite (the same split brain-retention uses).
250
+ result = await _measurePhase(phases, 'helperRules.dailyTick', () => _brainWrite('helperRulesDailyTick'));
251
+ break;
247
252
  case 'deferred:embeddings-init':
248
253
  result = await _measurePhase(phases, 'embeddings.init', async () => {
249
254
  await require('../embeddings').init();
@@ -347,6 +352,7 @@ function supportedSchedulerWorkerJobs() {
347
352
  'telemetry:cleanup',
348
353
  'agent-benchmark',
349
354
  'weekly-eval-loop',
355
+ 'helper-rules-probe',
350
356
  'deferred:embeddings-init',
351
357
  'deferred:temporal-backfill',
352
358
  'deferred:skill-reference-sync',
@@ -14,6 +14,33 @@ const { createMigrationRunner, pragmaVersionStore } = require('../shared/db-migr
14
14
 
15
15
  const TELEMETRY_DB_FILENAME = 'telemetry.db';
16
16
 
17
+ // WP-1 (blog-view-counts, design §5 D-2, lead review correction): the two
18
+ // additive blog-view tables (a running total per article and a bounded set of
19
+ // short-lived per-tab receipts) are DDL, so the walle-telemetry migration owner
20
+ // declares them HERE — the baseline creates them and migration 2 adds them to a
21
+ // v1 database, so a fresh install and an upgrade land on the same schema. The
22
+ // read/write path (lib/blog-view-store.js) owns only queries, never DDL.
23
+ const BLOG_VIEW_SCHEMA_SQL = `
24
+ CREATE TABLE IF NOT EXISTS blog_view_counts (
25
+ slug TEXT PRIMARY KEY,
26
+ views INTEGER NOT NULL DEFAULT 0 CHECK (views >= 0)
27
+ );
28
+ CREATE TABLE IF NOT EXISTS blog_view_receipts (
29
+ slug TEXT NOT NULL,
30
+ token TEXT NOT NULL,
31
+ expires_at_ms INTEGER NOT NULL,
32
+ PRIMARY KEY (slug, token)
33
+ );
34
+ CREATE INDEX IF NOT EXISTS idx_blog_view_expiry ON blog_view_receipts (expires_at_ms);
35
+ `;
36
+
37
+ // Idempotent: every statement is IF NOT EXISTS, so re-running the baseline or
38
+ // migration 2 on an already-migrated database is a no-op. Called only from the
39
+ // two runner-owned spec entries below — never from a request path.
40
+ function createBlogViewTables(db) {
41
+ db.exec(BLOG_VIEW_SCHEMA_SQL);
42
+ }
43
+
17
44
  function _telemetryColumnExists(db, table, column) {
18
45
  return db.prepare(`PRAGMA table_info(${table})`).all().some((row) => row.name === column);
19
46
  }
@@ -157,6 +184,9 @@ function telemetryBaseline(db) {
157
184
  PRIMARY KEY(identity_key, content_hash)
158
185
  );
159
186
  `);
187
+ // WP-1 (blog-view-counts): durable per-article view counters live in this same
188
+ // database (design §5 D-2 — reuse the existing volume, no new open, no new job).
189
+ createBlogViewTables(db);
160
190
  }
161
191
 
162
192
  const TELEMETRY_MIGRATIONS = {
@@ -173,6 +203,11 @@ const TELEMETRY_MIGRATIONS = {
173
203
  _telemetryAddColumn(d, 'feedback_reports', 'ip_first_seen', 'TEXT');
174
204
  },
175
205
  },
206
+ 2: {
207
+ label: 'blog-view-tables',
208
+ rerunSafe: true, // CREATE TABLE/INDEX IF NOT EXISTS, safe to re-execute
209
+ up: (d) => createBlogViewTables(d),
210
+ },
176
211
  };
177
212
 
178
213
  /** Builds a fresh createMigrationRunner() instance for walle-telemetry. Callers that need
@@ -188,8 +223,10 @@ function createTelemetryMigrationRunner() {
188
223
  }
189
224
 
190
225
  module.exports = {
226
+ BLOG_VIEW_SCHEMA_SQL,
191
227
  TELEMETRY_DB_FILENAME,
192
228
  telemetryBaseline,
193
229
  TELEMETRY_MIGRATIONS,
230
+ createBlogViewTables,
194
231
  createTelemetryMigrationRunner,
195
232
  };
@@ -3,6 +3,11 @@
3
3
  const path = require('node:path');
4
4
  const { fork } = require('node:child_process');
5
5
  const { decodeIpcResult } = require('./ipc-result-codec');
6
+ // WP-1 (blog-view-counts): the ONE public counter path and its body cap have to
7
+ // be recognised on THIS side of the IPC boundary — the 1024-byte limit is
8
+ // enforced before a byte crosses it, and the counter's peer identity travels in
9
+ // its own field. Pure data/predicates, no env read at require time.
10
+ const { BLOG_VIEW_MAX_BODY_BYTES, isBlogViewsPath } = require('./blog-view-policy');
6
11
 
7
12
  const MAX_REQUEST_BYTES = 2 * 1024 * 1024;
8
13
  const MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
@@ -15,6 +20,38 @@ function codedError(code, message) {
15
20
  return error;
16
21
  }
17
22
 
23
+ // The TCP peer, as THIS process sees it — a transport fact, never a header. Only
24
+ // the parent can observe it (the worker's own socket is a pipe), so it is carried
25
+ // to the worker in its own IPC field and re-attached there as
26
+ // req.socket.remoteAddress. Absent/blank is passed through as '' so the worker
27
+ // can leave the request exactly as it was before this feature.
28
+ function readClientAddress(req) {
29
+ const address = req && req.socket ? req.socket.remoteAddress : undefined;
30
+ return typeof address === 'string' ? address : '';
31
+ }
32
+
33
+ // The counter's own refusal shape, written by this process when it refuses a
34
+ // request BEFORE the IPC hop (oversized body) or because the hop failed. Same
35
+ // headers and the same four generic messages the handler in the worker uses, so a
36
+ // caller cannot tell where the refusal came from, and no internal detail (a driver
37
+ // message, a timeout code, a stack) ever reaches the wire.
38
+ //
39
+ // `extraHeaders` exists for the one refusal that answers while the client may
40
+ // still be uploading (the 413): it asks for the connection to be closed by the
41
+ // normal HTTP means instead of tearing the socket down mid-body.
42
+ function sendCounterFailure(res, status, error, extraHeaders) {
43
+ if (res.headersSent) return;
44
+ const payload = JSON.stringify({ error });
45
+ res.writeHead(status, {
46
+ 'Content-Type': 'application/json',
47
+ 'Content-Length': Buffer.byteLength(payload),
48
+ 'Cache-Control': 'no-store',
49
+ 'Vary': 'Origin',
50
+ ...(extraHeaders || {}),
51
+ });
52
+ res.end(payload);
53
+ }
54
+
18
55
  class TelemetryProcessClient {
19
56
  constructor(options = {}) {
20
57
  this.workerPath = options.workerPath || path.join(__dirname, '..', 'workers', 'telemetry-worker.js');
@@ -155,6 +192,11 @@ class TelemetryProcessClient {
155
192
  }
156
193
 
157
194
  proxy(req, res, url) {
195
+ // The public blog counter shares this transport but not its limits: its body
196
+ // cap (1024 B) is enforced here, on the parent side, BEFORE any IPC work —
197
+ // the generic telemetry routes keep the 2 MiB bound unchanged.
198
+ const counterRoute = isBlogViewsPath(url.pathname);
199
+ const maxRequestBytes = counterRoute ? BLOG_VIEW_MAX_BODY_BYTES : MAX_REQUEST_BYTES;
158
200
  const chunks = [];
159
201
  let bytes = 0;
160
202
  let rejected = false;
@@ -171,12 +213,27 @@ class TelemetryProcessClient {
171
213
  'x-forwarded-for': req.headers['x-forwarded-for'],
172
214
  'x-real-ip': req.headers['x-real-ip'],
173
215
  origin: req.headers.origin,
216
+ // WP-1: the counter's bot filter reads the UA and its limiter prefers
217
+ // Fly-Client-IP when (and only when) the child really is a Fly app.
218
+ // Forwarded for this one route only, so the existing telemetry routes
219
+ // keep answering from exactly the headers they saw before.
220
+ ...(counterRoute ? {
221
+ 'user-agent': req.headers['user-agent'],
222
+ 'fly-client-ip': req.headers['fly-client-ip'],
223
+ } : {}),
174
224
  },
225
+ ...(counterRoute ? { clientAddress: readClientAddress(req) } : {}),
175
226
  body: chunks.length ? Buffer.concat(chunks).toString('base64') : '',
176
227
  });
177
228
  res.writeHead(response.status || 200, response.headers || {});
178
229
  res.end(response.body || '');
179
230
  } catch (error) {
231
+ if (counterRoute) {
232
+ // A transport/queue/timeout failure is a 503 with the counter's generic
233
+ // body — never the internal error code or message.
234
+ sendCounterFailure(res, 503, 'View count unavailable');
235
+ return;
236
+ }
180
237
  const status = error.code === 'WALL_E_TELEMETRY_REQUEST_TOO_LARGE' ? 413 : 503;
181
238
  res.writeHead(status, { 'Content-Type': 'application/json' });
182
239
  res.end(JSON.stringify({ error: error.message, code: error.code, retry_after_ms: 500 }));
@@ -185,8 +242,20 @@ class TelemetryProcessClient {
185
242
  req.on('data', chunk => {
186
243
  if (rejected) return;
187
244
  bytes += chunk.length;
188
- if (bytes > MAX_REQUEST_BYTES) {
245
+ if (bytes > maxRequestBytes) {
189
246
  rejected = true;
247
+ if (counterRoute) {
248
+ // Over-cap counter body: the refusal is written and the connection is
249
+ // ended by the protocol, NEVER by destroying the socket out from under
250
+ // the response. `req.destroy()` here raced the JSON write — a client on
251
+ // a real connection could see a TCP reset instead of the complete
252
+ // 'payload_too_large' document. Connection: close is enough: the
253
+ // `rejected` flag above already stops every further chunk from being
254
+ // buffered, so the unread remainder cannot grow this process, and Node
255
+ // tears the socket down itself once the response is flushed.
256
+ sendCounterFailure(res, 413, 'payload_too_large', { Connection: 'close' });
257
+ return;
258
+ }
190
259
  res.writeHead(413, { 'Content-Type': 'application/json' });
191
260
  res.end(JSON.stringify({ error: 'Telemetry request exceeds 2 MiB' }));
192
261
  req.destroy();
@@ -198,6 +267,12 @@ class TelemetryProcessClient {
198
267
  req.on('error', error => {
199
268
  if (rejected) return;
200
269
  rejected = true;
270
+ if (counterRoute) {
271
+ // An aborted upload is a client problem: the counter's malformed-payload
272
+ // answer, with no detail about the transport failure.
273
+ sendCounterFailure(res, 400, 'invalid_payload');
274
+ return;
275
+ }
201
276
  res.writeHead(400, { 'Content-Type': 'application/json' });
202
277
  res.end(JSON.stringify({ error: error.message }));
203
278
  });
@@ -523,6 +523,10 @@ module.exports = {
523
523
  shouldFallThroughClass,
524
524
  _configFromRow,
525
525
  _hasEnvKey,
526
+ // The provider-type -> credential-env-var map _hasEnvKey reads. Exported so a fixture can
527
+ // clear exactly the keys that decide whether an env-only provider candidate exists (the D-2
528
+ // seed reads the same map through routing-policy.js envCandidate).
529
+ _ENV_KEY_BY_TYPE,
526
530
  TRANSIENT_CLASSES,
527
531
  FALLTHROUGH_CLASSES,
528
532
  },
@@ -19,7 +19,7 @@ const modelFactsCore = require('../lib/model-facts-core');
19
19
  const { resolveReasoningControl, intersectReasoningControls } = require('./reasoning-control');
20
20
  const { policyRankFor: routeRankPolicyStage } = require('./route-rank');
21
21
  // D-5: llm/worker-route.js is a cycle-free leaf (node builtins + ./portkey + ../../shared/*).
22
- const { reasoningParamsForRoute, routeReasoningLevels } = require('./worker-route');
22
+ const { reasoningParamsForRoute, routeReasoningLevels, routeModelReasoningLevels } = require('./worker-route');
23
23
  const {
24
24
  KNOWN_MAKER_TYPES,
25
25
  canonicalModelId,
@@ -273,8 +273,15 @@ function groupSessionRows(models, providerById, live) {
273
273
  // the registry row; a row without it (join absent, non-identity row)
274
274
  // carries ''.
275
275
  region: String(row.identity_region || ''),
276
- // D-5 (§6.3): the route's CLI effort levels, off the one definition.
277
- reasoning_levels: Array.isArray(provider.reasoning_levels) ? provider.reasoning_levels.slice() : [],
276
+ // D-1/D-2 (§6): the route's CLI effort levels for THIS route's model id,
277
+ // off the one producer (llm/worker-route.js routeModelReasoningLevels) --
278
+ // an anthropic-messages passthrough route publishes the Claude matrix for
279
+ // `modelId` instead of the empty chat-translate fragment map.
280
+ reasoning_levels: routeModelReasoningLevels({
281
+ type: String(provider.type || '').toLowerCase(),
282
+ upstream_apis: Array.isArray(provider.upstream_apis) ? provider.upstream_apis : [],
283
+ model_id: modelId,
284
+ }),
278
285
  reasoning_control: resolveReasoningControl({ providerType, modelId }),
279
286
  };
280
287
 
@@ -442,8 +449,10 @@ function normalizeSessionProvider(provider = {}) {
442
449
  // is routable at all. getProviderWithMeta (http/model-admin.js) stamps
443
450
  // both on every provider row before it reaches the catalog build.
444
451
  upstream_apis: Array.isArray(provider.upstream_apis) ? provider.upstream_apis.slice() : [],
445
- // D-5 (§6.3): the CLI effort levels this route can carry upstream — the one
452
+ // D-5 (§6.3): the CLI effort levels this provider can carry upstream — the one
446
453
  // definition (llm/worker-route.js routeReasoningLevels) over its upstream APIs.
454
+ // Route-wide and model-less on purpose (D-3): the per-model value the picker
455
+ // reads lives on each catalog route instead (groupSessionRows above).
447
456
  reasoning_levels: routeReasoningLevels(reasoningParamsForRoute(String(provider.type || '').toLowerCase(), Array.isArray(provider.upstream_apis) ? provider.upstream_apis : [])),
448
457
  routable: provider.routable !== false,
449
458
  // D-7 (§6.3): why a routable:false provider cannot serve a worker — the
@@ -35,6 +35,8 @@ const TASK_ROLE = Object.freeze({
35
35
  'approval-review': 'background',
36
36
  'approval-ai-refinement': 'background',
37
37
  'approval-scenario-classify': 'background',
38
+ 'lead-wait-watch': 'background', // WP-1 worker-wait watcher tick (pinned to the worker's own route:model; the role is never consulted)
39
+ 'explore-handoff-grade': 'background', // WP-6 spot-check grader (pinned to helper_rules.grader_model; the role is never consulted)
38
40
  // user-triggered, single completion
39
41
  'meeting-notes': 'chat',
40
42
  'dictation-refine': 'chat',
@@ -261,6 +263,10 @@ async function completeForRole({
261
263
  const role = resolution.role;
262
264
  const ledgerWrite = _writeLedger(brain, {
263
265
  source, feature, requestId, providerType, providerId, modelId, usage: response?.usage, latencyMs,
266
+ // WP-4b (D-9, S-19): the caller's sessionId (CTM sends the watched worker's ctm_session_id for
267
+ // a `lead-wait-watch` tick) is stored on the row, so watcher spend is readable per cell. A
268
+ // caller that passes nothing keeps today's NULL.
269
+ sessionId,
264
270
  stopReason: response?.stopReason || null,
265
271
  metadata: { taskType: task, role, tier, pinSource: resolution.source, fallbackFrom },
266
272
  });
@@ -0,0 +1,150 @@
1
+ 'use strict';
2
+ // Portkey HTTP transport: one bounded, redirect-safe, retrying JSON fetch for every
3
+ // request in the Portkey path (sync, discovery, catalog, resolver). Extracted verbatim
4
+ // from llm/portkey-sync.js (WP-0 of docs/codex-routed-fork-cold-restore-guard-2026-10-22.html)
5
+ // so that file stays under its size ceiling; the contract (D-1 transient classes,
6
+ // D-2 retry engine + test seam, manual redirects) is unchanged.
7
+ const { redactSecrets } = require('./provider-error');
8
+ const { createRetryPolicy } = require('../lib/kernel/retry-policy');
9
+
10
+ // D-2: TOTAL attempts for a transient failure, with the backoff between them.
11
+ const PORTKEY_REQUEST_ATTEMPTS = 3;
12
+ const PORTKEY_RETRY_DELAYS_MS = [1000, 3000];
13
+ const PORTKEY_RETRY_AFTER_MAX_MS = 30 * 1000;
14
+
15
+ // WP-1 review R6: this is the ONLY request budget in the Portkey path, and it
16
+ // is per REQUEST, not per host — callers that must bound a whole host (the
17
+ // resolver) also cap the number of pages. There is deliberately no
18
+ // AbortController+setTimeout fallback when `AbortSignal.timeout` is missing:
19
+ // that would be a recovery timer, which `stabilization-lint` requires to be
20
+ // registered in release/ownership-registry.json and which this feature's
21
+ // design forbids. `AbortSignal.timeout` is Node >= 17.3 and this repo pins
22
+ // v25.2.1, so the fallback branch below is unreachable in practice; if it is
23
+ // ever reached the request is unbounded and the caller's page cap is what
24
+ // stops it.
25
+ function withTimeout(timeoutMs) {
26
+ if (typeof AbortSignal !== 'undefined' && AbortSignal.timeout && Number.isFinite(timeoutMs)) {
27
+ return { signal: AbortSignal.timeout(timeoutMs) };
28
+ }
29
+ return {};
30
+ }
31
+
32
+ async function safeJson(response) {
33
+ try { return await response.json(); } catch { return null; /* expected: non-JSON or empty error body; callers fall back to a generic message */ }
34
+ }
35
+
36
+ // D-2 test seam: when set, createRetryPolicy uses it instead of the kernel's
37
+ // own registered timer. null (default) means "use the kernel sleep".
38
+ let _retrySleepOverride = null;
39
+
40
+ function _setRetrySleepForTest(fn) {
41
+ _retrySleepOverride = typeof fn === 'function' ? fn : null;
42
+ }
43
+
44
+ // D-1: TRANSIENT iff no response at all (status 0) or 408/429/5xx; every other
45
+ // status (3xx/401/403/AB03/033/other 4xx) is DEFINITIVE and never retried.
46
+ function isTransientStatus(status) {
47
+ if (status === 0) return true;
48
+ const code = Number(status);
49
+ return code === 408 || code === 429 || (code >= 500 && code <= 599);
50
+ }
51
+
52
+ // D-2: honour Retry-After only as integer seconds ≤ 30 s; else fixed backoff.
53
+ function retryAfterMs(response) {
54
+ if (!response || typeof response.headers?.get !== 'function') return null;
55
+ const raw = response.headers.get('retry-after');
56
+ if (raw == null) return null;
57
+ const seconds = Number(String(raw).trim());
58
+ if (!Number.isFinite(seconds) || seconds < 0) return null;
59
+ const ms = seconds * 1000;
60
+ return ms <= PORTKEY_RETRY_AFTER_MAX_MS ? ms : null;
61
+ }
62
+
63
+ function errorCodeFromBody(body) {
64
+ if (!body || typeof body !== 'object') return '';
65
+ if (body.data && body.data.errorCode) return String(body.data.errorCode);
66
+ if (body.error && body.error.code) return String(body.error.code);
67
+ return '';
68
+ }
69
+
70
+ function errorMessageFromBody(body, fallback) {
71
+ if (body && typeof body === 'object') {
72
+ if (body.data && body.data.message) return String(body.data.message);
73
+ if (body.message) return String(body.message);
74
+ }
75
+ return fallback;
76
+ }
77
+
78
+ // `redirect: 'manual'` is the DEFAULT here, for EVERY request in the Portkey
79
+ // path — not only the resolver's probe (final review, finding #3). Every one
80
+ // carries the full gateway key in `x-portkey-api-key`, and undici keeps that
81
+ // header across a cross-origin redirect, so following a 3xx would hand the key
82
+ // to a host the user never named (the axios GHSA-jr5f-v2jv-69x6 class). With
83
+ // `manual` a 3xx is not followed and `response.ok` is false, so it surfaces as
84
+ // `{ok:false, status:3xx}` — a verdict, never a second request.
85
+ // `fetchInit` (WP-1): extra init the CALLER owns — today the resolver's
86
+ // per-host AbortController signal. Spread last so it replaces withTimeout's.
87
+ // D-2: ONE attempt; returns today's ok/failure shape plus the response's
88
+ // Retry-After (null when the fetch threw — a transport failure has no headers).
89
+ async function fetchJsonOnce(fetchFn, url, { headers, timeoutMs, fallbackMessage, fetchInit } = {}) {
90
+ try {
91
+ const response = await fetchFn(url, { headers, redirect: 'manual', ...withTimeout(timeoutMs), ...(fetchInit || {}) });
92
+ const body = await safeJson(response);
93
+ if (response.ok) return { result: { ok: true, status: response.status, body }, retryAfter: null };
94
+ return {
95
+ result: {
96
+ ok: false,
97
+ status: response.status,
98
+ code: errorCodeFromBody(body),
99
+ message: redactSecrets(errorMessageFromBody(body, fallbackMessage(response.status))),
100
+ },
101
+ retryAfter: retryAfterMs(response),
102
+ };
103
+ } catch (err) {
104
+ // D-1: no HTTP response at all (status 0 — transport/timeout) is transient.
105
+ const message = err && err.message ? err.message : String(err);
106
+ return { result: { ok: false, status: 0, code: '', message: redactSecrets(message) }, retryAfter: null };
107
+ }
108
+ }
109
+
110
+ // D-2: transient failures are retried through wall-e's ONE retry engine
111
+ // (lib/kernel/retry-policy.js); a caller owning its own abort signal (the
112
+ // resolver's single probe) is single-shot and never retries.
113
+ async function fetchJsonOrError(fetchFn, url, opts = {}) {
114
+ if (opts.fetchInit) {
115
+ const { result } = await fetchJsonOnce(fetchFn, url, opts);
116
+ if (result.ok) return result;
117
+ const transient = isTransientStatus(result.status);
118
+ return transient ? { ...result, transient, attempts: 1 } : { ...result, transient };
119
+ }
120
+ let nextDelay = null;
121
+ let lastAttempt = 1;
122
+ const policyOptions = {
123
+ maxAttempts: PORTKEY_REQUEST_ATTEMPTS,
124
+ jitter: 0,
125
+ delayFn: (attempt) => nextDelay ?? PORTKEY_RETRY_DELAYS_MS[attempt - 1],
126
+ classify: (err) => (err && err.portkeyTransient ? 'transient' : 'permanent'),
127
+ layer: 'portkey-sync',
128
+ operation: 'fetch',
129
+ };
130
+ // Only override the kernel's own registered timer when a test seam is set.
131
+ if (_retrySleepOverride) policyOptions.sleep = (ms) => _retrySleepOverride(ms);
132
+ const policy = createRetryPolicy(policyOptions);
133
+ try {
134
+ return await policy.run(async (attempt) => {
135
+ const { result, retryAfter } = await fetchJsonOnce(fetchFn, url, opts);
136
+ if (result.ok || !isTransientStatus(result.status)) {
137
+ return result.ok ? result : { ...result, transient: false };
138
+ }
139
+ nextDelay = retryAfter;
140
+ lastAttempt = attempt;
141
+ throw Object.assign(new Error(result.message), { portkeyTransient: true, result });
142
+ });
143
+ } catch (err) {
144
+ // Only our transient sentinel is converted; anything else propagates.
145
+ if (!err || !err.portkeyTransient) throw err;
146
+ return { ...err.result, transient: true, attempts: lastAttempt };
147
+ }
148
+ }
149
+
150
+ module.exports = { fetchJsonOrError, fetchJsonOnce, safeJson, isTransientStatus, _setRetrySleepForTest };