@edgehero/pi-dispatch 4.3.1 → 5.0.0

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 (256) hide show
  1. package/.env.example +84 -3
  2. package/README.md +20 -7
  3. package/deploy/console.service +52 -0
  4. package/deploy/docker-compose.yml +3 -1
  5. package/deploy/pi-dispatch-valkey.container +4 -2
  6. package/deploy/sandbox-gateway.service +23 -0
  7. package/deploy/worker-env-wrapper.cmd +6 -0
  8. package/deploy/worker-env-wrapper.sh +10 -1
  9. package/package.json +117 -19
  10. package/src/agent-snapshot.mjs +49 -0
  11. package/src/answerer-runtime.mjs +194 -0
  12. package/src/answerer.mjs +178 -0
  13. package/src/approval-policy.mjs +76 -0
  14. package/src/approval-runtime.mjs +188 -0
  15. package/src/approval-session.mjs +137 -0
  16. package/src/approval-state.mjs +170 -0
  17. package/src/auth-cli.mjs +86 -0
  18. package/src/auth-connections.mjs +145 -0
  19. package/src/auth-policy.mjs +32 -0
  20. package/src/auth-store.mjs +97 -0
  21. package/src/azure-host.mjs +66 -4
  22. package/src/azure-prompt.mjs +5 -4
  23. package/src/backend-conformance.mjs +34 -2
  24. package/src/backend-local.mjs +103 -34
  25. package/src/backend-openshell.mjs +1540 -0
  26. package/src/backend-podman.mjs +63 -2
  27. package/src/backend-registry.mjs +61 -4
  28. package/src/backends.mjs +395 -6
  29. package/src/bullmq-internals.mjs +208 -0
  30. package/src/canary-cli.mjs +28 -0
  31. package/src/canary-lock.mjs +41 -0
  32. package/src/canary-runtime.mjs +237 -0
  33. package/src/canary.mjs +53 -0
  34. package/src/cancel-cli.mjs +47 -34
  35. package/src/cancel-state.mjs +213 -15
  36. package/src/capabilities.mjs +36 -1
  37. package/src/capacity-cli.mjs +28 -8
  38. package/src/capacity-records.mjs +75 -16
  39. package/src/capacity.mjs +65 -0
  40. package/src/chat-cli.mjs +288 -0
  41. package/src/chat-fork.mjs +547 -0
  42. package/src/chat-mode.mjs +156 -0
  43. package/src/chat-view.mjs +720 -0
  44. package/src/checkpoint-paths.mjs +113 -0
  45. package/src/cli.mjs +128 -41
  46. package/src/comment-tables.mjs +322 -0
  47. package/src/community-auth-adapters.mjs +87 -0
  48. package/src/community-auth-helper.mjs +76 -0
  49. package/src/community-auth-metadata.mjs +6 -0
  50. package/src/community-auth-output-pins.mjs +38 -0
  51. package/src/community-auth-patches.mjs +168 -0
  52. package/src/community-auth-pins.mjs +38 -0
  53. package/src/config.mjs +190 -29
  54. package/src/connection.mjs +83 -7
  55. package/src/console-answer.mjs +113 -0
  56. package/src/console-audit.mjs +604 -0
  57. package/src/console-bootstrap.mjs +159 -0
  58. package/src/console-client.mjs +848 -0
  59. package/src/console-probe.mjs +106 -0
  60. package/src/console-tokens.mjs +459 -0
  61. package/src/container-names.mjs +51 -0
  62. package/src/container-spec.mjs +20 -1
  63. package/src/cpu-reserve.mjs +4 -1
  64. package/src/defer-reasons.mjs +200 -0
  65. package/src/defer-write.mjs +208 -0
  66. package/src/deployment-venue.mjs +23 -6
  67. package/src/docker-run.mjs +11 -0
  68. package/src/doctor-bundles.mjs +130 -0
  69. package/src/doctor-model-proposals.mjs +109 -0
  70. package/src/doctor-openshell.mjs +238 -0
  71. package/src/doctor-results.mjs +602 -0
  72. package/src/doctor-spawner.mjs +215 -0
  73. package/src/doctor.mjs +1639 -196
  74. package/src/dollar-budget.mjs +212 -3
  75. package/src/endpoint-status.mjs +100 -0
  76. package/src/env-allowlist.mjs +96 -6
  77. package/src/env-file.mjs +6 -4
  78. package/src/file-expect.mjs +47 -0
  79. package/src/forgejo-host.mjs +42 -5
  80. package/src/forgejo-prompt.mjs +5 -4
  81. package/src/forges.mjs +33 -3
  82. package/src/fork-job.mjs +58 -0
  83. package/src/fork-policy.mjs +23 -0
  84. package/src/fork-store.mjs +338 -0
  85. package/src/get-token.mjs +1 -1
  86. package/src/git-dirty.mjs +119 -14
  87. package/src/git-hardening.mjs +85 -0
  88. package/src/github-host.mjs +31 -6
  89. package/src/github-prompt.mjs +7 -16
  90. package/src/gitlab-host.mjs +42 -3
  91. package/src/gitlab-prompt.mjs +5 -4
  92. package/src/held-document.mjs +23 -0
  93. package/src/held-replies.mjs +325 -0
  94. package/src/held-settle.mjs +266 -0
  95. package/src/hook-runs.mjs +188 -0
  96. package/src/host-budget.mjs +8 -5
  97. package/src/host-clone.mjs +72 -0
  98. package/src/host-loads.mjs +557 -0
  99. package/src/host-registry.mjs +66 -6
  100. package/src/host-telemetry.mjs +92 -0
  101. package/src/http-body.mjs +78 -0
  102. package/src/image-preflight.mjs +156 -13
  103. package/src/import-pi.mjs +60 -172
  104. package/src/inbox-relay.mjs +482 -0
  105. package/src/inbox-settle.mjs +196 -0
  106. package/src/index.mjs +734 -129
  107. package/src/init.mjs +35 -5
  108. package/src/job-data-version.mjs +75 -0
  109. package/src/job-id.mjs +30 -15
  110. package/src/job-layout.mjs +22 -0
  111. package/src/job-size.mjs +16 -0
  112. package/src/live-jobs.mjs +14 -6
  113. package/src/live-probes.mjs +6 -2
  114. package/src/live-transcript.mjs +872 -0
  115. package/src/loaded-record.mjs +609 -0
  116. package/src/mcp-config.mjs +1 -1
  117. package/src/mcp-preflight.mjs +36 -5
  118. package/src/model-attempts.mjs +84 -0
  119. package/src/model-catalog.mjs +83 -0
  120. package/src/model-cooldowns.mjs +22 -0
  121. package/src/model-failover-protocol.mjs +11 -0
  122. package/src/model-failover.mjs +134 -0
  123. package/src/model-observation.mjs +50 -0
  124. package/src/model-proposal-protocol.mjs +21 -0
  125. package/src/model-proposals.mjs +75 -0
  126. package/src/model-ref.mjs +67 -10
  127. package/src/model-roles-file.mjs +24 -0
  128. package/src/model-roles.mjs +73 -0
  129. package/src/model-status.mjs +34 -0
  130. package/src/models-json.mjs +4 -1
  131. package/src/money.mjs +1 -1
  132. package/src/oauth-openai.mjs +121 -0
  133. package/src/older-state.mjs +249 -0
  134. package/src/on-failure.mjs +17 -13
  135. package/src/open-browser.mjs +31 -2
  136. package/src/opener-cli.mjs +135 -0
  137. package/src/opener-protocol.mjs +27 -0
  138. package/src/opener-terminal.mjs +128 -0
  139. package/src/openshell-cli.mjs +155 -0
  140. package/src/openshell-policy.mjs +163 -0
  141. package/src/outbox.mjs +14 -0
  142. package/src/output-cap.mjs +1 -0
  143. package/src/package-canary-record.mjs +26 -0
  144. package/src/package-canary.mjs +135 -0
  145. package/src/package-names.mjs +9 -0
  146. package/src/package-stage.mjs +418 -0
  147. package/src/packages.mjs +24 -16
  148. package/src/parked.mjs +2 -1
  149. package/src/pause-windows.mjs +136 -17
  150. package/src/podman-stack.mjs +40 -14
  151. package/src/portfolio-snapshot.mjs +26 -7
  152. package/src/prepare-github.mjs +41 -77
  153. package/src/prepare-local.mjs +32 -2
  154. package/src/prepare.mjs +45 -11
  155. package/src/private-store-boundary.mjs +70 -0
  156. package/src/processor.mjs +1550 -173
  157. package/src/projects.mjs +23 -0
  158. package/src/prompt-fence.mjs +18 -0
  159. package/src/provider-key.mjs +7 -0
  160. package/src/provider-steering.mjs +1 -0
  161. package/src/question-context.mjs +35 -0
  162. package/src/question-group-runtime.mjs +91 -0
  163. package/src/question-groups.mjs +171 -0
  164. package/src/queue.mjs +264 -55
  165. package/src/reason-catalog.mjs +448 -0
  166. package/src/receiver-status.mjs +230 -0
  167. package/src/record-reason.mjs +37 -0
  168. package/src/recovery-queue.mjs +566 -0
  169. package/src/redact-secrets.mjs +219 -0
  170. package/src/reserve-estimate.mjs +101 -0
  171. package/src/reserved-env.mjs +36 -1
  172. package/src/retention.mjs +67 -0
  173. package/src/run-budget-ledger.mjs +283 -0
  174. package/src/run-checkpoint.mjs +627 -0
  175. package/src/run-container.mjs +749 -33
  176. package/src/run-history.mjs +791 -45
  177. package/src/run-mirror.mjs +75 -7
  178. package/src/run-progress.mjs +751 -0
  179. package/src/runtime-observations.mjs +3 -1
  180. package/src/runtime-settings.mjs +272 -37
  181. package/src/sandbox-cli.mjs +78 -2
  182. package/src/sandbox-deployment.mjs +51 -0
  183. package/src/sandbox-gateway-protocol.mjs +378 -0
  184. package/src/sandbox-hosts.mjs +100 -0
  185. package/src/sandbox-inventory.mjs +83 -0
  186. package/src/sandbox-policy.mjs +225 -0
  187. package/src/sandbox-prepare-requests.mjs +145 -0
  188. package/src/sandbox-prepare.mjs +285 -0
  189. package/src/sandbox-refs.mjs +70 -0
  190. package/src/sandbox-session-monitor.mjs +4 -0
  191. package/src/sandbox-session-watch.mjs +124 -0
  192. package/src/sandbox-sessions.mjs +161 -0
  193. package/src/sandbox-store.mjs +63 -6
  194. package/src/sandbox-workspace-guard.mjs +39 -0
  195. package/src/sandbox.mjs +27 -200
  196. package/src/scheduler-stall-guard.mjs +14 -8
  197. package/src/schedules.mjs +19 -6
  198. package/src/secret-bundles.mjs +245 -0
  199. package/src/secret-profiles.mjs +103 -3
  200. package/src/secrets.mjs +19 -9
  201. package/src/service-env.mjs +38 -5
  202. package/src/service.mjs +638 -106
  203. package/src/session-key.mjs +60 -0
  204. package/src/session-store.mjs +168 -10
  205. package/src/setup-contract.mjs +40 -0
  206. package/src/setup-egress.mjs +113 -0
  207. package/src/setup-host-entry.mjs +33 -0
  208. package/src/setup-host.mjs +242 -0
  209. package/src/setup-lock.mjs +41 -0
  210. package/src/size-records.mjs +15 -3
  211. package/src/start.mjs +1353 -135
  212. package/src/status-comment.mjs +415 -0
  213. package/src/stderr-transcript.mjs +318 -0
  214. package/src/steer-ack.mjs +122 -0
  215. package/src/steer-cli.mjs +213 -0
  216. package/src/steer-framing.mjs +185 -0
  217. package/src/steer-inbox.mjs +693 -0
  218. package/src/store-version.mjs +139 -0
  219. package/src/subscription-config.mjs +56 -0
  220. package/src/subscription-limits.mjs +3 -0
  221. package/src/subscription-quota.mjs +259 -0
  222. package/src/subscription-runtime.mjs +123 -0
  223. package/src/subscription-status.mjs +42 -0
  224. package/src/subscription-wait.mjs +11 -0
  225. package/src/subscriptions.mjs +7 -1
  226. package/src/thinking-level.mjs +19 -0
  227. package/src/touch-outbox.mjs +61 -0
  228. package/src/touch-state.mjs +133 -0
  229. package/src/triggers-file.mjs +299 -39
  230. package/src/triggers.mjs +780 -38
  231. package/src/trust-paths.mjs +171 -0
  232. package/src/up.mjs +398 -51
  233. package/src/updates-cli.mjs +61 -0
  234. package/src/updates-look.mjs +127 -0
  235. package/src/updates-reconcile.mjs +182 -0
  236. package/src/updates-subscription-adapters.mjs +8 -0
  237. package/src/updates.mjs +252 -0
  238. package/src/valkey-auth.mjs +32 -9
  239. package/src/valkey-endpoint.mjs +14 -4
  240. package/src/wait-for.mjs +1 -1
  241. package/src/wait-state.mjs +62 -3
  242. package/src/watch-board.mjs +38 -0
  243. package/src/watch-closer.mjs +1 -1
  244. package/src/watch-contract.mjs +102 -0
  245. package/src/watch-decision.mjs +78 -0
  246. package/src/watch-drainer.mjs +14 -0
  247. package/src/watch-issue-adapter.mjs +44 -0
  248. package/src/watch-issue.mjs +111 -0
  249. package/src/watch-record.mjs +10 -0
  250. package/src/watch-runtime.mjs +167 -0
  251. package/src/watch-store.mjs +183 -0
  252. package/src/watch-trace.mjs +34 -0
  253. package/src/workspace-checkpoint.mjs +1750 -0
  254. package/src/zero-touch-fixes.mjs +99 -0
  255. package/src/zero-touch.mjs +225 -0
  256. package/src/repeat-slot.mjs +0 -13
package/.env.example CHANGED
@@ -26,15 +26,23 @@
26
26
  # OpenAI: OPENAI_API_KEY Google: GEMINI_API_KEY Groq: GROQ_API_KEY ... etc.
27
27
  # You can LEAVE THIS BLANK if you are already logged into pi: when the env has no key, the worker reads the
28
28
  # API key from ~/.pi/agent/auth.json (host-side) and env-injects it -- on by default, nothing to set.
29
- # API-key logins only; an OAuth/subscription login is refused (it expires; use an API key for a service).
29
+ # Stored pi logins remain API-key only. For host-owned subscription authentication use docs/subscription-auth.md.
30
30
  ANTHROPIC_API_KEY=
31
31
  # uncomment to force env-only (fail loudly on a missing env key instead of using your pi login)
32
32
  # PI_AUTH_FROM_PI=0
33
+ # Explicit subscription bindings own their providers, with no API-key fallback.
34
+ # PI_AUTH_CONNECTIONS_FILE=/absolute/path/auth-connections.json
35
+ # Host-private credentials, never under the global overlay or job/sandbox directories.
36
+ # PI_AUTH_STORE_DIR=/absolute/private/path/subscription-auth
33
37
 
34
38
  # --- Which provider/model to run by default (override per job with --provider / --model) ---
39
+ # A provider id: 1 to 64 letters, digits, dots, dashes and underscores, starting with a letter or digit.
40
+ # Leave it out for the default; a value outside this shape, or an empty one, stops the worker at boot.
35
41
  PI_PROVIDER=anthropic
36
42
  # Name an exact model version (claude-sonnet-5-5, or a dated id such as claude-sonnet-4-5-20250929); an alias
37
43
  # that moves to a newer model can change cost.
44
+ # A model id: 1 to 64 letters, digits and . _ - : / @, starting with a letter or digit (or one ~ or @ then
45
+ # one), no spaces. Case is kept. A value outside this shape, or an empty one, stops the worker at boot.
38
46
  PI_MODEL=claude-sonnet-5-5
39
47
  # optional: the only models a job may call, comma-separated provider/model (at most 16). A trigger's own
40
48
  # run.models replaces this list for its jobs. The default model must be on it, or every job that uses the
@@ -161,8 +169,24 @@ PI_JOB_IMAGE=pi-job:latest
161
169
  # PI_LOGS_DIR=
162
170
  # default 0; set 1 to ALSO write raw container output to logs/<jobId>.log -- PII-bearing (issue/comment text), host-only (never mounted into the container), off by default (opt-in)
163
171
  # PI_CAPTURE_JOB_LOGS=
172
+ # default on; set 0 to stop publishing each running job's transcript to Valkey (issue #643), where the console and the /dispatch panel on any host read it live
173
+ # What leaves the host is the transcript only (never the raw log), redacted of this job's own secrets and known token shapes, capped at 2,000 entries per job, and removed 24h after the run ends (7 days if a worker dies mid-run)
174
+ # PII: it carries issue text and tool output, so anyone who can read this Valkey (and every console operator) can read it; a secret the agent re-encodes is not caught. Any value but 0, 1 or empty refuses to boot
175
+ # PI_LIVE_TRANSCRIPTS=
176
+ # default on; set 0 to stop reading each running job's progress from its transcript (issue #644): turn, steps, failed steps and the tool running, to Valkey for the console and the /dispatch panel, plus a summary of integers in the run record
177
+ # A trigger with run.progress also reports named phases (shown to console operators only, redacted); one with run.statusComment gets a forge comment edited as it runs (numbers only, never model text)
178
+ # Independent of PI_LIVE_TRANSCRIPTS: with that at 0 and this on, no transcript text is published. 0 also stops the record field and the status comment. Any value but 0, 1 or empty refuses to boot
179
+ # PI_RUN_PROGRESS=
164
180
  # default 30; prunes logs older than N days at boot AND on the retention timer below; 0 = keep forever
165
181
  # PI_LOG_RETENTION_DAYS=
182
+ # default: PI_LOG_RETENTION_DAYS's value; prunes the console audit trail's month files (console-audit/ in PI_LOGS_DIR) older than N days, at boot and on the retention timer below; 0 = keep forever
183
+ # Its own setting so that shortening the run history for privacy does not shorten the security trail. The newest month file is never pruned (it holds the chain's head)
184
+ # Read by the worker, which runs the sweep (the console never prunes); set the same value on every host that shares PI_LOGS_DIR, where the shortest one wins. A value that is not a non-negative integer refuses to boot
185
+ # A worker from before this setting that shares PI_LOGS_DIR prunes console-audit/ by PI_LOG_RETENTION_DAYS, so upgrade every host before relying on a longer audit retention
186
+ # PI_CONSOLE_AUDIT_RETENTION_DAYS=
187
+ # default 5000; how many runs the shared run index keeps (the run mirror in Valkey that a worker with PI_WORKER_NAME writes, read by the console, the /dispatch panel and capacity); an integer 1000-20000, anything else refuses to boot
188
+ # Set the same value on every host: each worker trims the shared index to its own setting, so in a fleet the lowest one wins, and a worker from before this setting trims at 5000. Each trim is recorded, so a report shows the cut, never idle time
189
+ # PI_RUN_MIRROR_MAX_RUNS=
166
190
  # default 24; how often the three retention sweeps (logs, sandboxes, sessions) re-run while the worker is up
167
191
  # The sandbox one covers two things since issue #337: the retained directories, and the pi-sandbox-<id>-net network of a run whose directory is already gone
168
192
  # 0 = BOOT-ONLY, which is what every version before this one did: a worker that never restarts never re-sweeps, so a configured window described nothing
@@ -170,10 +194,26 @@ PI_JOB_IMAGE=pi-job:latest
170
194
  # The window is a FLOOR, not a ceiling: a file dies on the first sweep AFTER its window closes, so the real ceiling is its window plus this interval (up to 48h for a sandbox at both defaults)
171
195
  # Refused above 168 (one week): setInterval clamps a longer delay to 1ms, and a sweep slower than a week is 0 with extra steps
172
196
  # PI_SWEEP_INTERVAL_HOURS=
197
+ # default 24; how often the worker runs `pi-dispatch doctor --publish` itself (the first run a minute after boot), so the console and the /dispatch panel show each host's checks (issue #712)
198
+ # What is published to Valkey is each check's level, and for a warning or a failure its label and fix with paths and URLs cut to <path> and scheme://host and secret-named values and known token shapes redacted; kept 7 days per host
199
+ # A doctor you run by hand publishes the same way (pass --no-publish to skip it). 0 = the worker runs no doctor of its own; refused above 168 (one week)
200
+ # PI_DOCTOR_INTERVAL_HOURS=
201
+ # 1 = this host looks once a day for a newer pi and newer staged extensions, for the console's Updates page. Off by default.
202
+ # It reads registry.npmjs.org and this project's public check runs on api.github.com, from the worker host and never from a job.
203
+ # It sends the names of your staged packages to that registry, and changes nothing: trying or adopting a version is always a person.
204
+ # PI_UPDATES_LOOK=
205
+ # a proxy for the look's requests, with no direct fallback (http or https URL). Unset, the look connects directly.
206
+ # PI_UPDATES_PROXY=
207
+ # a registry to ask instead of registry.npmjs.org, such as a private mirror, so that private package names stay inside.
208
+ # PI_UPDATES_REGISTRY=
173
209
  # where a finished run's directory is kept so you can re-open it (default: <PI_JOBS_DIR>/sandboxes, mode 0700)
174
210
  # `pi-dispatch sandbox <jobId>` starts a fresh container from the same image with the same workspace and NO credentials (docs/sandbox.md)
175
211
  # A retained directory holds the run's clone plus its prompt.md/event.json -- so issue text. Host-only; never mounted into a job
176
212
  # PI_SANDBOX_DIR=
213
+ # Operator-only Fleet terminal mappings; the console defaults to <deployment>/sandbox-hosts.json
214
+ # PI_SANDBOX_HOSTS_FILE=
215
+ # Optional browser gateway enrollment, hand-owned and off when absent. See docs/sandbox-gateway.md.
216
+ # PI_SANDBOX_GATEWAYS_FILE=sandbox-gateways.json
177
217
  # default 24. NOTE: 0 means OFF here -- nothing is retained and cleanup deletes as it always did
178
218
  # This is the OPPOSITE of PI_LOG_RETENTION_DAYS/PI_SESSIONS_TTL_DAYS, where 0 means keep forever
179
219
  # There is deliberately no keep-forever value: one repo clone per run with no ceiling is a disk bomb. Use --pin for the one run worth keeping
@@ -212,6 +252,10 @@ PI_JOB_IMAGE=pi-job:latest
212
252
  # That source is your whole gh login: full-scope and non-expiring, and a transcript is a FILE -- any command that echoed an auth header persists it
213
253
  # Prefer GITHUB_AUTH_SOURCE=app or a short-expiry fine-grained PAT. Set exactly 1 to accept the trade explicitly (SECURITY.md, docs/sessions.md)
214
254
  # PI_SESSIONS_ALLOW_GH_SOURCE=
255
+ # default <PI_SESSIONS_DIR>/.checkpoints; where workspace checkpoints of durable runs live, unset with no PI_SESSIONS_DIR = nowhere
256
+ # A checkpoint holds a job's workspace, so the same rules as transcripts apply: mode 0700, owned by the worker, never under a temp dir
257
+ # It must not be PI_SESSIONS_DIR itself, contain it, or sit inside one of its session folders: the worker refuses that at start
258
+ # PI_CHECKPOINTS_DIR=
215
259
  # ABSOLUTE path to the unified triggers.json, read by BOTH worker and receiver (a relative path resolves against the service's WorkingDirectory).
216
260
  # Unset = cron disabled for the worker; the receiver falls back to ./triggers.json in the folder it starts from (what `pi-dispatch init` scaffolds)
217
261
  # and refuses to start when neither exists (it holds the label/comment/pull_request trigger config)
@@ -248,8 +292,6 @@ PI_JOB_IMAGE=pi-job:latest
248
292
  # (default: <your pi agent dir>/pi-dispatch-deployment.json). Read by the ADMIN EXTENSION only; the worker and receiver never look at it.
249
293
  # Your own environment still wins key by key, so this points the panel at a deployment, it does not override one
250
294
  # PI_DISPATCH_DEPLOYMENT_FILE=
251
- # where `/dispatch insights` writes its HTML artifact (default: <OS temp dir>/pi-dispatch-<your uid>/graph). Admin extension only. See docs/insights.md
252
- # PI_GRAPH_DIR=
253
295
 
254
296
  # --- Reuse your existing pi setup in every job (see docs/global-pi-overlay.md) ---
255
297
  # dir with your host pi setup (models.json/skills/APPEND_SYSTEM.md), mounted /opt/pi-global:ro into every job, layered UNDER each repo's .pi/. Unset = off. Stage it with: pi-dispatch import-pi
@@ -286,6 +328,13 @@ PI_JOB_IMAGE=pi-job:latest
286
328
  # PI_SECRET_RESOLVER_ROOTS=
287
329
  # default 10000, per reference. Sits before a paid container and is multiplied by the reference count, so tighter than doctor's 30s.
288
330
  # PI_SECRET_RESOLVE_TIMEOUT_MS=
331
+ # Project secret bundles (docs/secrets.md Recipe D, issue #704): a project's whole environment, granted ONCE in
332
+ # triggers.json ("secretBundles": { "shop": { "profile": "shop-env", "ref": "shop", "project": "shop" } }). The worker
333
+ # runs YOUR script once per job start, on the HOST, and it prints every variable of that environment.
334
+ # name:/absolute/path:format, comma separated; format is dotenv (strict NAME=value lines) or json (one object of strings),
335
+ # required, never guessed. A plain file needs no manager: `exec cat "/etc/pi-dispatch/bundles/$1.env"` with :dotenv.
336
+ # Same exit codes as above. Bounded by PI_SECRET_RESOLVER_ROOTS for panel-declared ones. Unset = no bundle resolves here.
337
+ # PI_SECRET_BUNDLES=
289
338
 
290
339
  # --- Holding a job until something else happens (docs/wait-for.md, issue #230) ---
291
340
  # A trigger may carry "waitFor": [{ "after": "2026-09-01T09:00:00Z" }, { "profile": "jira" }]. The job is
@@ -345,6 +394,16 @@ PI_SCHEDULER_STALL_MAX=2
345
394
  # `pi-dispatch doctor` prints enforced quietly, asserted as a warning naming who asserts it, and absent as a failure.
346
395
  # Env-only, deliberately: a bound that can be widened from the surface it bounds is not a bound.
347
396
  # PI_BACKEND_FLOOR=
397
+ # openshell (an NVIDIA OpenShell sandbox per job, docs/backends.md) is blessable since issue #684: name it in PI_BACKENDS.
398
+ # While PI_BACKENDS names it the worker reads these two at boot (a bad value refuses the boot), refuses PI_EGRESS=0, and
399
+ # refuses an openshell CLI or gateway not at the tested release; `pi-dispatch doctor --openshell` checks the host first.
400
+ # openshell-bind is the same venue in bind mode (issue #686): the job's directories bind-mounted, which brings back folder
401
+ # and cron-folder jobs, /inbox steering and run.recover. Opt-in: the gateway's gateway.toml must allow driver config and
402
+ # bind mounts (docs/backends.md, "Bind mode"), docker driver only. It shares the two settings below; name it in PI_BACKENDS.
403
+ # the gateway's compute driver, docker (default) or podman, whose store the job image is read from
404
+ # PI_OPENSHELL_ENGINE=
405
+ # a registered gateway's name (`openshell gateway list`); unset uses the CLI's active gateway
406
+ # PI_OPENSHELL_GATEWAY=
348
407
 
349
408
  # --- Egress policy: what a job container may reach on the network (docs/egress.md) ---
350
409
  # ON by default. Every job runs on its own --internal Docker network whose only other member is an allowlist
@@ -509,6 +568,28 @@ AZURE_WEBHOOK_SECRET=
509
568
  # Required only when AZURE_WEBHOOK_MODE=header.
510
569
  AZURE_WEBHOOK_HEADER=
511
570
 
571
+ # --- The operator console, a web page served by `pi-dispatch-console serve` (docs/console.md, issue #640) ---
572
+ # Nothing listens until you run serve, and serve refuses to start before `pi-dispatch-console token add --name <you> --role operator`.
573
+ # default 127.0.0.1:4790. Also [::1]:<port> or unix:/abs/path (a 0600 socket in a folder only you can open, which no job can reach).
574
+ # IP literals only: a hostname could resolve somewhere you did not mean.
575
+ # The /dispatch panel reads it too: `/dispatch insights` and the `i` key print the console's insights page at this address (docs/insights.md).
576
+ # PI_CONSOLE_LISTEN=
577
+ # default console-tokens.json, relative to this folder. Hashes only, mode 0600, yours; serve refuses anything looser.
578
+ # PI_CONSOLE_TOKENS_FILE=
579
+ # default none. Extra Host names the console answers to, comma separated; any port is accepted, so an ssh -L tunnel needs nothing here.
580
+ # host.docker.internal and the other container gateway names are refused: they are how a job would reach the console.
581
+ # PI_CONSOLE_ALLOWED_HOSTS=
582
+ # default off. 1 answers every write 403 and leaves reads and sign-in on -- for a console you only want to look at.
583
+ # PI_CONSOLE_READ_ONLY=
584
+ # default off. 1 allows a non-loopback IP in PI_CONSOLE_LISTEN, and doctor warns every time: the console has no TLS, use ssh -L instead.
585
+ # PI_CONSOLE_BIND_PUBLIC=
586
+ # default 60, 5 to 1440. Minutes without a request before a session ends.
587
+ # PI_CONSOLE_IDLE_MINUTES=
588
+ # default 12, 1 to 168. Hours after sign-in before a session ends however busy, so a forgotten tab does not stay signed in.
589
+ # PI_CONSOLE_MAX_HOURS=
590
+ # default 4, 1 to 32. Live streams one token may hold across its sign-ins, so no one can hold every connection.
591
+ # PI_CONSOLE_MAX_STREAMS=
592
+
512
593
  # --- What is deliberately NOT a key in this file --- issue #282
513
594
  # The rule, and it is checked by a test (worker/test/env-docs.test.mjs): every environment variable this
514
595
  # project's loaders read is either a key above, or named here with the reason it cannot be one. A variable
package/README.md CHANGED
@@ -2,13 +2,15 @@
2
2
 
3
3
  The worker and the `pi-dispatch` command line tool of
4
4
  **[pi-dispatch](https://github.com/edgehero/pi-dispatch)**, which runs the
5
- [pi](https://github.com/earendil-works/pi) coding agent as a self hosted background service.
5
+ [pi](https://github.com/earendil-works/pi) coding agent for you in the background, in a sealed box for each job, up to the spending limit
6
+ you set, and shows it all on one web page. Most people want the
7
+ [main README](https://github.com/edgehero/pi-dispatch#readme) first: it explains why and how to use it.
6
8
 
7
9
  The worker takes jobs from a durable queue (Valkey with BullMQ). Before anything is spent it checks every
8
10
  cap that applies: job counts and a daily token count for the whole deployment, job counts per repo, folder
9
11
  or project, and dollars per day, week or month for the deployment and per repo, folder, project or model,
10
12
  plus each project's share of the budget split. It then starts one locked down container per job (Docker or
11
- Podman), holds the job to its own dollar cap when one is set, gives the agent the job, records what it did
13
+ Podman), holds the job to its own dollar cap when one is set, gives the agent the job, records what it loaded, what it did
12
14
  and what it cost, and removes the container. Jobs come from the command line, from cron triggers, or from
13
15
  forge events that the
14
16
  [receiver](https://www.npmjs.com/package/@edgehero/pi-dispatch-receiver) queues.
@@ -27,6 +29,10 @@ npx @edgehero/pi-dispatch worker # run jobs
27
29
  npx @edgehero/pi-dispatch run ./my-project --task "add type hints" --flow tidy
28
30
  ```
29
31
 
32
+ On the NVIDIA OpenShell venue (`PI_BACKENDS=openshell` in `.env`), `up` also checks its gateway, offers to start
33
+ and register it (it never installs OpenShell), and puts the job image into the gateway's engine
34
+ ([backends](https://github.com/edgehero/pi-dispatch/blob/main/docs/backends.md#the-openshell-venue)).
35
+
30
36
  A local job edits your folder **in place**, and there is no undo, so commit first. The worker refuses a
31
37
  folder with uncommitted changes unless you pass `--force`. The folder must be the root of a git repository
32
38
  with at least one commit, and `run` refuses any other folder before it queues anything.
@@ -37,15 +43,22 @@ with at least one commit, and `run` refuses any other folder before it queues an
37
43
  |---|---|
38
44
  | `init` | write `.env` and the config files into this folder (never overwrites) |
39
45
  | `up` | one pass that asks before each container action: image, Valkey, egress proxy, `init`, `doctor` |
40
- | `doctor [--fix] [--live]` | check the whole setup; `--live` reads the isolation guarantees back from real containers |
46
+ | `doctor [--fix] [--live] [--openshell] [--secrets] [--no-publish]` | check the whole setup; `--live` reads the isolation guarantees back from real containers; each run publishes its checks (paths, URLs and secret values cut out) for the console and the panel, unless `--no-publish` |
41
47
  | `worker` | run jobs from the queue |
42
48
  | `run <folder> --task "..."` | queue one job against a local folder |
43
49
  | `run --trigger <id>` | fire one cron trigger from the triggers file now, once, as its schedule would |
44
- | `status`, `pause`, `resume`, `cancel <jobId>` | steer the running worker from any terminal |
45
- | `service render\|install\|status\|restart` | run the worker (or `--receiver`) as a service (user level on macOS and Linux, nssm on Windows) |
50
+ | `status`, `pause`, `resume`, `cancel <jobId>` | control the running worker from any terminal |
51
+ | `steer <jobId> "..."` | send a running job a message, which the agent reads between its steps (`--later` once it would otherwise stop) |
52
+ | `capacity` | how busy each host was over the last day, week or thirty days, read from the run records |
53
+ | `service render\|install\|uninstall\|status\|start\|stop\|restart` | run the worker (or `--receiver`, or `--console` for the operator console) as a service (user level on macOS and Linux, nssm on Windows) |
46
54
  | `setup github` | create a GitHub App in one browser click |
55
+ | `auth` | manage host owned subscription authentication: `status`, `login`, `logout`, `usage`, `trial` |
56
+ | `canary` | replay up to three retained jobs on another model in disposable workspaces, under one experiment cap |
57
+ | `updates` | which pi and which extensions jobs run, what is newer, and adopting it |
58
+ | `opener install` | register `pi-dispatch-open`, so the console can open a sandbox in your own terminal |
47
59
  | `import-pi` | stage your own pi setup (models, skills, persona) for every job, without credentials |
48
- | `sandbox <jobId>` | reopen a finished run's workspace in a fresh container with no credentials |
60
+ | `chat <jobId \| url \| key>` | follow and steer a running job, answer a parked one, or fork a finished one into your own pi (`--fork-as-job` to run it as a job), through the operator console |
61
+ | `sandbox <jobId>` | reopen a finished run's workspace in a fresh container with no credentials; `--list`, `--attach` and `--new` to see, return to or prepare one |
49
62
  | `egress render` | write `model-endpoints.conf` from `model-endpoints.json`, then print the command that reloads the egress proxy |
50
63
 
51
64
  Before you rely on it, read [`SECURITY.md`](https://github.com/edgehero/pi-dispatch/blob/main/SECURITY.md).
@@ -54,5 +67,5 @@ guardrails that tell it not to leak them are prompt text. The default GitHub sou
54
67
  whole login, full scope and never expiring. pi-dispatch never merges anything, but the job's token can,
55
68
  so branch protection on your default branch is the real control.
56
69
 
57
- The full guide, with triggers, providers, the admin panel and every forge, is the
70
+ The full guide, with the console, triggers, providers, the panel and every forge, is the
58
71
  [main README](https://github.com/edgehero/pi-dispatch#readme). MIT licensed.
@@ -0,0 +1,52 @@
1
+ # UNTESTED EXAMPLE (DES-OPERATOR-CONSOLE) -- a starting point, not a shipped, verified unit. Adapt it.
2
+ #
3
+ # The operator console (`pi-dispatch-console serve`, issue #640) as a service, so it keeps running after the terminal
4
+ # that started it closes (issue #708). `pi-dispatch service install --console` renders this file for THIS host: the
5
+ # node, the console package's own entry (admin/dist/console.mjs of @edgehero/pi-dispatch-admin, wherever npm put it)
6
+ # and the deployment folder are substituted, so prefer that to adapting it by hand.
7
+ #
8
+ # The console listens on PI_CONSOLE_LISTEN (default 127.0.0.1:4790, loopback only) and reads its tokens file
9
+ # (PI_CONSOLE_TOKENS_FILE, default console-tokens.json in the deployment folder). It never needs Docker or Podman, so
10
+ # this unit is ordered after nothing but the network and has no Quadlet companion. Reach it from another machine
11
+ # with ssh -L, never by binding a public address: it speaks plain HTTP (docs/console.md).
12
+ #
13
+ # NO EnvironmentFile= on purpose: `serve --deployment` reads the deployment's `.env` itself, takes only its own
14
+ # PI_CONSOLE_* keys and the Valkey keys it needs from it, and refuses a `.env` another account can change. Loading the
15
+ # whole file into the process environment would hand it every worker secret (the provider key, the forge token) and
16
+ # would make each of those keys read as this shell's, which wins over the file and skips that trust check.
17
+ # WorkingDirectory / User / node path below are PLACEHOLDERS: set them to wherever your deployment folder is and
18
+ # whoever owns it (the tokens file must be owned by that account, mode 0600, or serve refuses).
19
+
20
+ [Unit]
21
+ Description=pi-dispatch operator console (the web console over the queue, runs and spend; loopback by default)
22
+ # For a system unit. In a --user unit these two are inert (the user manager has no network-online.target), which is
23
+ # harmless: the console binds loopback or a socket, and its Valkey reads retry on their own.
24
+ After=network-online.target
25
+ Wants=network-online.target
26
+ # Crash-loop bound, as the worker and receiver units carry it: at most StartLimitBurst restarts within
27
+ # StartLimitIntervalSec, then systemd stops trying. Pairs with Restart=on-failure below.
28
+ StartLimitIntervalSec=60
29
+ StartLimitBurst=5
30
+
31
+ [Service]
32
+ Type=simple
33
+ User=pi
34
+ WorkingDirectory=/opt/pi-dispatch
35
+ ExecStart=/usr/bin/node /usr/lib/node_modules/@edgehero/pi-dispatch-admin/dist/console.mjs serve --deployment /opt/pi-dispatch
36
+ Restart=on-failure
37
+ RestartSec=5
38
+ # Exit 2 is the console's refusal: no tokens file, no operator token in it, an unusable PI_CONSOLE_LISTEN, or the
39
+ # single-instance lock held by another console of this deployment. The next start reads the same files and fails
40
+ # the same way, so it is never restarted; add a token (`pi-dispatch-console token add`) or fix the setting, then
41
+ # start the unit again.
42
+ # A crash leaves the lock (<PI_LOGS_DIR>/console-audit/.console.lock) behind holding a dead pid, which the next start
43
+ # takes over. But if that pid has since been reused by another process (or by another account's, which reads as
44
+ # alive), every start refuses with exit 2 and the unit stays stopped: when the pid the refusal names is not a console,
45
+ # remove the lock file and start the unit again.
46
+ RestartPreventExitStatus=2
47
+ # serve handles SIGTERM: it ends the open streams, closes the listener and releases the lock, then exits.
48
+ KillSignal=SIGTERM
49
+ TimeoutStopSec=30
50
+
51
+ [Install]
52
+ WantedBy=multi-user.target
@@ -30,7 +30,9 @@
30
30
 
31
31
  services:
32
32
  valkey:
33
- image: valkey/valkey:8
33
+ # Pinned to a minor, never a floating major (issue #761): VALKEY_IMAGE in worker/src/valkey-auth.mjs says why, and a
34
+ # test holds this line to it.
35
+ image: valkey/valkey:8.1
34
36
  # The password (issue #468), from the deployment's .env through `--env-file .env`, into the container's ENVIRONMENT
35
37
  # and never onto a command line: the image's PID 1 is tini, which keeps its whole argv for the life of the container,
36
38
  # readable by every account on the host in /proc (measured). Empty or unset starts Valkey without one, as before.
@@ -10,13 +10,15 @@
10
10
  # (measured on Podman 5.8.1: with linger on it came up 25 s after a reboot with nobody logged in).
11
11
  #
12
12
  # Fully qualified image names throughout: under short-name enforcement a unit cannot answer the prompt a short
13
- # name raises, so `valkey/valkey:8` could fail to start where Docker quietly assumed docker.io.
13
+ # name raises, so `valkey/valkey:8.1` could fail to start where Docker quietly assumed docker.io. Pinned to a minor,
14
+ # never a floating major (issue #761): VALKEY_IMAGE in worker/src/valkey-auth.mjs says why, and a test holds the
15
+ # Image= line to it. A changed line restarts Valkey when `service install --force` writes it; the volume is kept.
14
16
 
15
17
  [Unit]
16
18
  Description=pi-dispatch Valkey (the job queue; rootless Podman, Quadlet)
17
19
 
18
20
  [Container]
19
- Image=docker.io/valkey/valkey:8
21
+ Image=docker.io/valkey/valkey:8.1
20
22
  ContainerName=pi-dispatch-valkey
21
23
  # EXPLICIT, see pi-dispatch-valkey.network for why a container here must never rely on the default network.
22
24
  Network=pi-dispatch-valkey.network
@@ -0,0 +1,23 @@
1
+ # Optional example, installed by hand only. See docs/sandbox-gateway.md (#754).
2
+ # Run as the account owning the sandbox runtime, never add privileges for the gateway.
3
+ # No EnvironmentFile: the program reads only its explicit deployment key allowlist.
4
+ [Unit]
5
+ Description=pi-dispatch sandbox browser gateway
6
+ After=network-online.target
7
+ Wants=network-online.target
8
+ StartLimitIntervalSec=60
9
+ StartLimitBurst=5
10
+
11
+ [Service]
12
+ Type=simple
13
+ User=pi
14
+ WorkingDirectory=/opt/pi-dispatch
15
+ ExecStart=/usr/bin/node /usr/lib/node_modules/@edgehero/pi-dispatch-sandbox-gateway/dist/cli.mjs --config /opt/pi-dispatch/sandbox-gateway.json
16
+ Restart=on-failure
17
+ RestartSec=5
18
+ NoNewPrivileges=yes
19
+ KillSignal=SIGTERM
20
+ TimeoutStopSec=30
21
+
22
+ [Install]
23
+ WantedBy=multi-user.target
@@ -54,6 +54,10 @@ REM number. So after the load the wrapper re-asserts its own: ENV_SETUP and PI_E
54
54
  REM %PI_ENV_SETUP%, which cmd expands when it READS this parenthesized block, before its first line runs
55
55
  REM (`set /?`, the reason delayed expansion exists), so the value is the unit's and no .env line reaches it;
56
56
  REM and ERRORLEVEL cleared just before the command runs. RC is assigned after the command, before it is read.
57
+ REM PI_WRAPPER_NO_DOTENV=1 (issue #708): the command reads the deployment's .env ITSELF and takes only the keys it
58
+ REM owns (the operator console, `serve --deployment`), so .env is neither loaded nor required here; see
59
+ REM worker-env-wrapper.sh for why. Read before the load, from the service's own environment, and cleared after it.
60
+ if "%PI_WRAPPER_NO_DOTENV%"=="1" goto wrapper_env_loaded
57
61
  if exist ".env" (
58
62
  for /f "usebackq eol=# tokens=1,* delims==" %%A in (".env") do set "%%A=%%B"
59
63
  set "ENV_SETUP=%PI_ENV_SETUP%"
@@ -65,6 +69,8 @@ if exist ".env" (
65
69
  )
66
70
  echo worker-env-wrapper: no .env in "%CD%" -- the environment comes from "%ENV_SETUP%" ^(PI_ENV_SETUP^) 1>&2
67
71
  )
72
+ :wrapper_env_loaded
73
+ set "PI_WRAPPER_NO_DOTENV="
68
74
 
69
75
  REM AFTER .env, deliberately: the manager is the newer source of truth, so a stale key left in the file
70
76
  REM loses instead of silently shadowing the managed one (mirrors the .sh twin). A missing or failing
@@ -77,7 +77,15 @@ trap wrapper_stopped_early TERM INT
77
77
  # until the `shift` below.
78
78
  set -- "${PI_ENV_SETUP:-}" "$@"
79
79
 
80
- if [ -f ./.env ]; then
80
+ # PI_WRAPPER_NO_DOTENV=1 (issue #708) is set by the unit for a command that reads the deployment's .env ITSELF and
81
+ # takes only the keys it owns: the operator console (`serve --deployment`), which also refuses a .env another account
82
+ # can change. Sourcing the whole file here would hand that process every worker secret and make each key read as its
83
+ # own shell's, which wins over the file and skips that check. So ./.env is not sourced and not required. Tested
84
+ # before the load, from the unit's environment, so no .env line can set it; PI_ENV_SETUP is honoured exactly as
85
+ # without it (an operator who configured a script asked for it). Unset after, so the command never sees it.
86
+ if [ "${PI_WRAPPER_NO_DOTENV:-}" = "1" ]; then
87
+ :
88
+ elif [ -f ./.env ]; then
81
89
  set -a; . ./.env; set +a
82
90
  elif [ -z "$1" ]; then
83
91
  echo "worker-env-wrapper: .env not found in $PWD -- this wrapper must be started in the deployment folder (the unit's WorkingDirectory / nssm AppDirectory); it no longer guesses a location from its own path" >&2
@@ -90,6 +98,7 @@ fi
90
98
  # AFTER THE LOAD, every variable this wrapper reads is assigned, so no assignment line of ./.env
91
99
  # reaches one. The worker sees the unit's PI_ENV_SETUP too, never one the file set (the setup script,
92
100
  # which is unit configuration, may still set its own).
101
+ unset PI_WRAPPER_NO_DOTENV
93
102
  env_setup=$1
94
103
  shift
95
104
  if [ -n "$env_setup" ]; then
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@edgehero/pi-dispatch",
3
- "version": "4.3.1",
3
+ "version": "5.0.0",
4
4
  "type": "module",
5
- "description": "The pi-dispatch worker and CLI: runs the pi coding agent as a self-hosted service, one locked down Docker or Podman container per job, with spend caps checked before anything is spent, plus init, up, doctor and service install.",
5
+ "description": "The pi-dispatch worker and CLI: go from one AI coding agent to many in the background, running the pi coding agent as a self-hosted service, one locked down Docker or Podman container per job, with spend caps checked before anything is spent, plus init, up, doctor and service install.",
6
6
  "keywords": [
7
7
  "pi",
8
8
  "pi-coding-agent",
@@ -41,11 +41,46 @@
41
41
  "pi-dispatch": "src/cli.mjs"
42
42
  },
43
43
  "exports": {
44
+ "./receiver-status": "./src/receiver-status.mjs",
45
+ "./setup-contract": "./src/setup-contract.mjs",
46
+ "./auth-policy": "./src/auth-policy.mjs",
47
+ "./setup-host-entry": "./src/setup-host-entry.mjs",
48
+ "./setup-lock": "./src/setup-lock.mjs",
49
+ "./provider-key": "./src/provider-key.mjs",
44
50
  ".": "./src/index.mjs",
45
51
  "./config": "./src/config.mjs",
46
52
  "./exit-code": "./src/exit-code.mjs",
47
53
  "./entry": "./src/entry.mjs",
48
54
  "./git-hardening": "./src/git-hardening.mjs",
55
+ "./http-body": "./src/http-body.mjs",
56
+ "./console-tokens": "./src/console-tokens.mjs",
57
+ "./console-probe": "./src/console-probe.mjs",
58
+ "./console-audit": "./src/console-audit.mjs",
59
+ "./console-answer": "./src/console-answer.mjs",
60
+ "./steer-inbox": "./src/steer-inbox.mjs",
61
+ "./steer-framing": "./src/steer-framing.mjs",
62
+ "./inbox-relay": "./src/inbox-relay.mjs",
63
+ "./held-replies": "./src/held-replies.mjs",
64
+ "./live-transcript": "./src/live-transcript.mjs",
65
+ "./run-progress": "./src/run-progress.mjs",
66
+ "./reserve-estimate": "./src/reserve-estimate.mjs",
67
+ "./endpoint-status": "./src/endpoint-status.mjs",
68
+ "./model-endpoints": "./src/model-endpoints.mjs",
69
+ "./egress-allowlist": "./src/egress-allowlist.mjs",
70
+ "./egress": "./src/egress.mjs",
71
+ "./openshell-policy": "./src/openshell-policy.mjs",
72
+ "./model-catalog": "./src/model-catalog.mjs",
73
+ "./output-cap": "./src/output-cap.mjs",
74
+ "./model-roles": "./src/model-roles.mjs",
75
+ "./model-status": "./src/model-status.mjs",
76
+ "./model-observation": "./src/model-observation.mjs",
77
+ "./model-cooldowns": "./src/model-cooldowns.mjs",
78
+ "./model-proposal-protocol": "./src/model-proposal-protocol.mjs",
79
+ "./model-ref": "./src/model-ref.mjs",
80
+ "./model-failover-protocol": "./src/model-failover-protocol.mjs",
81
+ "./model-failover": "./src/model-failover.mjs",
82
+ "./model-attempts": "./src/model-attempts.mjs",
83
+ "./schedules": "./src/schedules.mjs",
49
84
  "./env-file": "./src/env-file.mjs",
50
85
  "./service-env": "./src/service-env.mjs",
51
86
  "./podman-stack": "./src/podman-stack.mjs",
@@ -56,28 +91,41 @@
56
91
  "./queue": "./src/queue.mjs",
57
92
  "./capabilities": "./src/capabilities.mjs",
58
93
  "./parked": "./src/parked.mjs",
94
+ "./approval-policy": "./src/approval-policy.mjs",
95
+ "./approval-state": "./src/approval-state.mjs",
59
96
  "./connection": "./src/connection.mjs",
60
97
  "./valkey-auth": "./src/valkey-auth.mjs",
61
98
  "./job-id": "./src/job-id.mjs",
62
99
  "./forges": "./src/forges.mjs",
100
+ "./fleet-lease": "./src/fleet-lease.mjs",
63
101
  "./backend-local": "./src/backend-local.mjs",
64
- "./backends": "./src/backends.mjs",
65
- "./backend-registry": "./src/backend-registry.mjs",
66
- "./container-spec": "./src/container-spec.mjs",
67
- "./job-size": "./src/job-size.mjs",
68
- "./size-suggest": "./src/size-suggest.mjs",
69
- "./capacity": "./src/capacity.mjs",
70
- "./capacity-records": "./src/capacity-records.mjs",
71
- "./capacity-cli": "./src/capacity-cli.mjs",
72
- "./live-jobs": "./src/live-jobs.mjs",
73
- "./size-records": "./src/size-records.mjs",
74
- "./host-budget": "./src/host-budget.mjs",
75
- "./backend-conformance": "./src/backend-conformance.mjs",
76
- "./live-probes": "./src/live-probes.mjs",
77
- "./triggers": "./src/triggers.mjs",
102
+ "./container-names": "./src/container-names.mjs",
103
+ "./backends": "./src/backends.mjs",
104
+ "./backend-registry": "./src/backend-registry.mjs",
105
+ "./container-spec": "./src/container-spec.mjs",
106
+ "./job-size": "./src/job-size.mjs",
107
+ "./size-suggest": "./src/size-suggest.mjs",
108
+ "./capacity": "./src/capacity.mjs",
109
+ "./capacity-records": "./src/capacity-records.mjs",
110
+ "./touch-state": "./src/touch-state.mjs",
111
+ "./touch-outbox": "./src/touch-outbox.mjs",
112
+ "./zero-touch": "./src/zero-touch.mjs",
113
+ "./capacity-cli": "./src/capacity-cli.mjs",
114
+ "./live-jobs": "./src/live-jobs.mjs",
115
+ "./host-telemetry": "./src/host-telemetry.mjs",
116
+ "./size-records": "./src/size-records.mjs",
117
+ "./host-budget": "./src/host-budget.mjs",
118
+ "./backend-conformance": "./src/backend-conformance.mjs",
119
+ "./live-probes": "./src/live-probes.mjs",
120
+ "./triggers": "./src/triggers.mjs",
78
121
  "./mcp-config": "./src/mcp-config.mjs",
122
+ "./mcp-preflight": "./src/mcp-preflight.mjs",
79
123
  "./triggers-file": "./src/triggers-file.mjs",
124
+ "./file-expect": "./src/file-expect.mjs",
80
125
  "./packages": "./src/packages.mjs",
126
+ "./updates": "./src/updates.mjs",
127
+ "./package-canary-record": "./src/package-canary-record.mjs",
128
+ "./updates-subscription-adapters": "./src/updates-subscription-adapters.mjs",
81
129
  "./pause-windows": "./src/pause-windows.mjs",
82
130
  "./scoped-limits": "./src/scoped-limits.mjs",
83
131
  "./projects": "./src/projects.mjs",
@@ -88,6 +136,10 @@
88
136
  "./wait-state": "./src/wait-state.mjs",
89
137
  "./cancel-state": "./src/cancel-state.mjs",
90
138
  "./host-registry": "./src/host-registry.mjs",
139
+ "./hook-runs": "./src/hook-runs.mjs",
140
+ "./doctor-results": "./src/doctor-results.mjs",
141
+ "./host-loads": "./src/host-loads.mjs",
142
+ "./loaded-record": "./src/loaded-record.mjs",
91
143
  "./identity": "./src/identity.mjs",
92
144
  "./gitlab-identity": "./src/gitlab-identity.mjs",
93
145
  "./forgejo-identity": "./src/forgejo-identity.mjs",
@@ -95,17 +147,57 @@
95
147
  "./get-token": "./src/get-token.mjs",
96
148
  "./octokit-log": "./src/octokit-log.mjs",
97
149
  "./runtime-settings": "./src/runtime-settings.mjs",
150
+ "./secret-profiles": "./src/secret-profiles.mjs",
98
151
  "./run-history": "./src/run-history.mjs",
152
+ "./reason-catalog": "./src/reason-catalog.mjs",
99
153
  "./run-mirror": "./src/run-mirror.mjs",
154
+ "./run-budget-ledger": "./src/run-budget-ledger.mjs",
155
+ "./recovery-queue": "./src/recovery-queue.mjs",
156
+ "./run-checkpoint": "./src/run-checkpoint.mjs",
157
+ "./session-key": "./src/session-key.mjs",
158
+ "./fork-store": "./src/fork-store.mjs",
159
+ "./fork-policy": "./src/fork-policy.mjs",
160
+ "./fork-job": "./src/fork-job.mjs",
161
+ "./chat-mode": "./src/chat-mode.mjs",
162
+ "./chat-fork": "./src/chat-fork.mjs",
163
+ "./console-client": "./src/console-client.mjs",
164
+ "./workspace-checkpoint": "./src/workspace-checkpoint.mjs",
100
165
  "./sandbox": "./src/sandbox.mjs",
166
+ "./sandbox-policy": "./src/sandbox-policy.mjs",
167
+ "./openshell-cli": "./src/openshell-cli.mjs",
101
168
  "./sandbox-store": "./src/sandbox-store.mjs",
169
+ "./sandbox-gateway-protocol": "./src/sandbox-gateway-protocol.mjs",
170
+ "./opener-protocol": "./src/opener-protocol.mjs",
171
+ "./sandbox-hosts": "./src/sandbox-hosts.mjs",
172
+ "./sandbox-prepare": "./src/sandbox-prepare.mjs",
173
+ "./sandbox-prepare-requests": "./src/sandbox-prepare-requests.mjs",
174
+ "./sandbox-inventory": "./src/sandbox-inventory.mjs",
175
+ "./sandbox-sessions": "./src/sandbox-sessions.mjs",
176
+ "./sandbox-session-watch": "./src/sandbox-session-watch.mjs",
102
177
  "./subscriptions": "./src/subscriptions.mjs",
178
+ "./subscription-status": "./src/subscription-status.mjs",
103
179
  "./budget": "./src/budget.mjs",
104
180
  "./pricing": "./src/pricing.mjs",
105
181
  "./money": "./src/money.mjs",
106
182
  "./dollar-budget": "./src/dollar-budget.mjs",
107
183
  "./scheduler-stall-guard": "./src/scheduler-stall-guard.mjs",
108
- "./watch-closer": "./src/watch-closer.mjs"
184
+ "./watch-closer": "./src/watch-closer.mjs",
185
+ "./defer-reasons": "./src/defer-reasons.mjs",
186
+ "./bullmq-internals": "./src/bullmq-internals.mjs",
187
+ "./store-version": "./src/store-version.mjs",
188
+ "./job-data-version": "./src/job-data-version.mjs",
189
+ "./retention": "./src/retention.mjs",
190
+ "./record-reason": "./src/record-reason.mjs",
191
+ "./watch-contract": "./src/watch-contract.mjs",
192
+ "./watch-store": "./src/watch-store.mjs",
193
+ "./watch-decision": "./src/watch-decision.mjs",
194
+ "./watch-board": "./src/watch-board.mjs",
195
+ "./watch-trace": "./src/watch-trace.mjs",
196
+ "./answerer": "./src/answerer.mjs",
197
+ "./answerer-runtime": "./src/answerer-runtime.mjs",
198
+ "./question-groups": "./src/question-groups.mjs",
199
+ "./question-group-runtime": "./src/question-group-runtime.mjs",
200
+ "./zero-touch-fixes": "./src/zero-touch-fixes.mjs"
109
201
  },
110
202
  "engines": {
111
203
  "node": ">=22.19.0"
@@ -115,10 +207,16 @@
115
207
  "start": "node src/cli.mjs worker"
116
208
  },
117
209
  "dependencies": {
118
- "@earendil-works/pi-ai": "1.2.0",
210
+ "@earendil-works/pi-ai": "1.5.0",
211
+ "@earendil-works/pi-tui": "1.5.0",
119
212
  "@octokit/auth-app": "8.2.0",
120
213
  "@octokit/rest": "22.0.1",
121
214
  "bullmq": "5.80.4",
122
- "ioredis": "5.11.1"
215
+ "ioredis": "5.11.1",
216
+ "jiti": "2.7.0",
217
+ "jose": "6.2.14",
218
+ "proper-lockfile": "4.1.2",
219
+ "undici": "8.10.2",
220
+ "yaml": "2.9.0"
123
221
  }
124
222
  }