@usine/server 0.2.1 → 0.3.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 (146) hide show
  1. package/README.md +37 -21
  2. package/dist/backend.d.ts +24 -5
  3. package/dist/backend.d.ts.map +1 -1
  4. package/dist/backend.js +37 -16
  5. package/dist/backend.js.map +1 -1
  6. package/dist/backup.d.ts +6 -3
  7. package/dist/backup.d.ts.map +1 -1
  8. package/dist/backup.js +25 -5
  9. package/dist/backup.js.map +1 -1
  10. package/dist/cli.d.ts +20 -7
  11. package/dist/cli.d.ts.map +1 -1
  12. package/dist/cli.js +65 -15
  13. package/dist/cli.js.map +1 -1
  14. package/dist/export-run.d.ts +1 -1
  15. package/dist/export-run.d.ts.map +1 -1
  16. package/dist/export-run.js +3 -3
  17. package/dist/export-run.js.map +1 -1
  18. package/dist/failure-message.d.ts +13 -0
  19. package/dist/failure-message.d.ts.map +1 -0
  20. package/dist/failure-message.js +58 -0
  21. package/dist/failure-message.js.map +1 -0
  22. package/dist/http.d.ts.map +1 -1
  23. package/dist/http.js +64 -23
  24. package/dist/http.js.map +1 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/instance-lock.d.ts +23 -0
  30. package/dist/instance-lock.d.ts.map +1 -0
  31. package/dist/instance-lock.js +58 -0
  32. package/dist/instance-lock.js.map +1 -0
  33. package/dist/main.js +4 -10
  34. package/dist/main.js.map +1 -1
  35. package/dist/reload-signal.d.ts +8 -0
  36. package/dist/reload-signal.d.ts.map +1 -0
  37. package/dist/reload-signal.js +21 -0
  38. package/dist/reload-signal.js.map +1 -0
  39. package/dist/shutdown.d.ts +5 -2
  40. package/dist/shutdown.d.ts.map +1 -1
  41. package/dist/shutdown.js +11 -5
  42. package/dist/shutdown.js.map +1 -1
  43. package/dist/up.d.ts +1 -1
  44. package/dist/up.d.ts.map +1 -1
  45. package/dist/up.js +29 -16
  46. package/dist/up.js.map +1 -1
  47. package/dist/web/assets/DialogTitle-DrHlwOSg.js +1 -0
  48. package/dist/web/assets/_agent-cBqm12lE.js +1 -0
  49. package/dist/web/assets/_loop-kf1h3qkS.js +1 -0
  50. package/dist/web/assets/_name-CKWcxUh0.js +2 -0
  51. package/dist/web/assets/_name-DCEDRHnF.js +1 -0
  52. package/dist/web/assets/_name-sJkGyn7O.js +1 -0
  53. package/dist/web/assets/_number-DbgcVg1N.js +1 -0
  54. package/dist/web/assets/_run-DqslP0y6.js +8 -0
  55. package/dist/web/assets/_worker-CH4UoJT_.js +1 -0
  56. package/dist/web/assets/action-DYT048Jm.js +1 -0
  57. package/dist/web/assets/agents-DIZolb0U.js +1 -0
  58. package/dist/web/assets/agents-DMhSMb9O.js +1 -0
  59. package/dist/web/assets/button-C4yvcSko.js +2 -0
  60. package/dist/web/assets/copy-BM1l6IGB.js +1 -0
  61. package/dist/web/assets/core-BUmsKffF.js +12 -0
  62. package/dist/web/assets/engine-javascript-D-6se_EF.js +141 -0
  63. package/dist/web/assets/error-alert-BrI00JbQ.js +1 -0
  64. package/dist/web/assets/file-error-C--_cWuj.js +1 -0
  65. package/dist/web/assets/index-BljdfRBm.js +10 -0
  66. package/dist/web/assets/index-YKcAk3No.css +2 -0
  67. package/dist/web/assets/inter-cyrillic-ext-opsz-normal-CfYZpDVp.woff2 +0 -0
  68. package/dist/web/assets/inter-cyrillic-opsz-normal-BFQXbQ-f.woff2 +0 -0
  69. package/dist/web/assets/inter-greek-ext-opsz-normal-BmPQvyvo.woff2 +0 -0
  70. package/dist/web/assets/inter-greek-opsz-normal-BM0NlUE_.woff2 +0 -0
  71. package/dist/web/assets/inter-latin-ext-opsz-normal-DpK-iCPk.woff2 +0 -0
  72. package/dist/web/assets/inter-latin-opsz-normal-BwkfbSeq.woff2 +0 -0
  73. package/dist/web/assets/inter-vietnamese-opsz-normal-Ci9T9ieq.woff2 +0 -0
  74. package/dist/web/assets/item-rBBCTV8x.js +1 -0
  75. package/dist/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  76. package/dist/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  77. package/dist/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  78. package/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  79. package/dist/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  80. package/dist/web/assets/label-badge-ohqvZnY7.js +1 -0
  81. package/dist/web/assets/loops-CXM3f90o.js +1 -0
  82. package/dist/web/assets/loops-DWLXyUPN.js +1 -0
  83. package/dist/web/assets/markdown-D07-JkYB.js +29 -0
  84. package/dist/web/assets/moves-RiWgdCxQ.js +5 -0
  85. package/dist/web/assets/operator-copy-DsM7LJx6.js +301 -0
  86. package/dist/web/assets/outcome-YiIf6xbJ.js +5 -0
  87. package/dist/web/assets/project-not-found-CQVclgb0.js +1 -0
  88. package/dist/web/assets/query-DWN8IHdt.js +1 -0
  89. package/dist/web/assets/repositories-CoTEmYmS.js +1 -0
  90. package/dist/web/assets/repositories-Cq7zGzNb.js +1 -0
  91. package/dist/web/assets/rolldown-runtime-XKyaHtl9.js +1 -0
  92. package/dist/web/assets/route-CE9U74a9.js +1 -0
  93. package/dist/web/assets/route-DNH2EwHP.js +1 -0
  94. package/dist/web/assets/routes-XAUJJqfj.js +1 -0
  95. package/dist/web/assets/run-again-DxjFCuNu.js +1 -0
  96. package/dist/web/assets/run-drawer-CyjxH5mf.js +1 -0
  97. package/dist/web/assets/section-BBnBOk-U.js +1 -0
  98. package/dist/web/assets/section-Cm-j3zHs.js +1 -0
  99. package/dist/web/assets/section-DkBCDv4D.js +1 -0
  100. package/dist/web/assets/settings-iZf872O5.js +1 -0
  101. package/dist/web/assets/status-badge-CNlyvwWL.js +1 -0
  102. package/dist/web/assets/table-B6NH8hTW.js +1 -0
  103. package/dist/web/assets/tasks-DhLW-1gg.js +1 -0
  104. package/dist/web/assets/usage-DPUTTLGs.js +1 -0
  105. package/dist/web/assets/use-now-CcFZyFvf.js +1 -0
  106. package/dist/web/assets/useNavigate-lXd3v3E0.js +1 -0
  107. package/dist/web/assets/usePositioner-WErrA3Ah.js +1 -0
  108. package/dist/web/assets/useScrollLock-BOb01sL8.js +1 -0
  109. package/dist/web/assets/useTriggerFocusGuards-B0FItfLc.js +1 -0
  110. package/dist/web/assets/utils-BEQKLGgi.js +1 -0
  111. package/dist/web/assets/with-selector-NsM_5qXG.js +1 -0
  112. package/dist/web/assets/workers-BgocBXT9.js +1 -0
  113. package/dist/web/assets/workers-Dw2uHY0Y.js +1 -0
  114. package/dist/web/index.html +13 -10
  115. package/package.json +13 -9
  116. package/dist/web/assets/_name-DdyMEUN6.js +0 -6
  117. package/dist/web/assets/_name-eoobiMh0.js +0 -1
  118. package/dist/web/assets/_name-pUxlKZt5.js +0 -1
  119. package/dist/web/assets/badge-t7m0Vk1K.js +0 -146
  120. package/dist/web/assets/button-BoOukj77.js +0 -1
  121. package/dist/web/assets/core-CLXRiB63.js +0 -12
  122. package/dist/web/assets/empty-DbVYU7bi.js +0 -1
  123. package/dist/web/assets/engine-javascript-h-x4hGa4.js +0 -141
  124. package/dist/web/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  125. package/dist/web/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  126. package/dist/web/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  127. package/dist/web/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  128. package/dist/web/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  129. package/dist/web/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  130. package/dist/web/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  131. package/dist/web/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  132. package/dist/web/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  133. package/dist/web/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  134. package/dist/web/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  135. package/dist/web/assets/index-CVBBoW6r.css +0 -2
  136. package/dist/web/assets/index-CsW8KcJW.js +0 -10
  137. package/dist/web/assets/loops-DghpuoiG.js +0 -1
  138. package/dist/web/assets/operator-copy-DIsVXn17.js +0 -1
  139. package/dist/web/assets/overview-DmCIVPwN.js +0 -1
  140. package/dist/web/assets/rolldown-runtime-hePW80VL.js +0 -1
  141. package/dist/web/assets/routes-BDyP7vCr.js +0 -1
  142. package/dist/web/assets/run-drawer-B-xhTXe4.js +0 -157
  143. package/dist/web/assets/status-badge-yblcElE0.js +0 -1
  144. package/dist/web/assets/table-B8cLYNvq.js +0 -1
  145. package/dist/web/assets/useCompositeItem-HmBmWxUW.js +0 -1
  146. package/dist/web/assets/with-selector-N6m4X0X_.js +0 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @usine/server
2
2
 
3
- The foreground CLI for a local [usine](https://github.com/basaltbytes/usine) factory. It is a thin Node adapter over `@usine/orchestrator`: the library owns polling, schedule triggers, durable execution, worker composition, and operator acts; this package serves their JSON and web surfaces.
3
+ The foreground CLI for a local [usine](https://github.com/basaltbytes/usine) factory: a thin Node adapter over `@usine/orchestrator`. The library does the polling, schedule triggers, durable execution, worker composition, and operator actions; this package serves their JSON and web surfaces. The [CLI reference](https://docs.usine.dev/reference/configuration/cli/) lists every command, flag, and variable, and [Operate runs](https://docs.usine.dev/guides/operate-runs/) covers day-to-day use.
4
4
 
5
5
  Run it from an instance folder:
6
6
 
@@ -8,49 +8,65 @@ Run it from an instance folder:
8
8
  usine up
9
9
  ```
10
10
 
11
- The command prints `http://127.0.0.1:7331` and stays attached to the terminal. Pass `--port <port>` (or set `USINE_PORT`) to listen elsewhere. `usine --help` prints the complete command surface and `usine --version` prints the exact `@usine/server` version pinned by the instance. If the chosen port is occupied, the CLI names the conflict and suggests the override instead of exposing Node's bind failure.
11
+ It prints `http://127.0.0.1:7331` and stays attached to the terminal. `--port <port>` or `USINE_PORT` changes the port; if the port is taken, the CLI names the conflict and suggests the override. `usine --version` prints the `@usine/server` version that the instance pins.
12
12
 
13
- Without GitHub App credentials, it opens the setup screen in the default browser, logs the typed credential error, and leaves both trigger streams idle instead of crashing. The browser command has a two-second timeout; a failed opener produces a warning and does not stop the server. Existing Runs, transcript detail, and database-backed Loop pause state remain observable, while interventions stay unavailable. Credentials may come from `USINE_GITHUB_APP_ID` plus `USINE_GITHUB_PRIVATE_KEY`, or from a mode-0600 `.usine/github-app.json` file with `app_id` and `private_key` fields. The running process reads credentials only at startup, so stop and rerun `usine up` after adding them; the setup screen states that restart requirement explicitly.
13
+ One `usine up` runs per instance folder. It holds `.usine/instance.lock` from startup until every store closes, and writes its pid to `.usine/instance.pid`. A second `usine up` there names the running process and exits nonzero before opening a store. A draining process keeps the folder until it exits.
14
14
 
15
- The local-only UI opens each project as a Board built from its authored or proposed lanes. Proposed lanes are read-only. Configure `repos[].lanes` in `usine.yaml` and restart `usine up` to enable moves; see the [lane configuration example](../../packages/config/README.md). Moving an Item into an authored label-backed lane exchanges only the labels in that map, preserving descriptive labels outside it; normal polling then evaluates label-protocol loops. Board cards show the latest Run and open its existing detail drawer. The UI can also approve, retry, or cancel a Run; pause or unpause a Loop's database runtime state; page backward through older transcript rows; and tail parent and one-level child session activity.
15
+ Without GitHub App credentials, it logs the typed error, leaves both trigger streams idle, and opens the setup screen in the default browser. The browser command times out after two seconds; a failed opener only logs a warning. Runs, transcripts, and loop pause state stay readable, and the instance folder can be reloaded, but interventions are unavailable. Supply `USINE_GITHUB_APP_ID` and `USINE_GITHUB_PRIVATE_KEY`, or `app_id` and `private_key` in a mode-0600 `.usine/github-app.json`. Credentials are read only at startup, so restart `usine up` after adding them, as the setup screen says.
16
16
 
17
- Run conversations remember the reader’s filter, inspected disclosures and reading place when the drawer reopens or the page refreshes. Bookmarks are specific to each Run and browser origin; unread activity remains pending while a saved place is restored. See the [conversation reader behavior](../web/README.md#returning-to-a-conversation) for retention and history-recovery limits.
17
+ ## Web UI
18
18
 
19
- Direct browser requests for UI routes serve the application shell, so refreshing or sharing a Project or Configuration URL works. Unknown `/api/*` routes, missing `/assets/*` files, and other file-like paths remain 404 responses.
19
+ Each project opens as a board of authored or proposed lanes; proposed lanes are read-only. To enable moves, define `repos[].lanes` in `usine.yaml` and reload (see the [lane example](../../packages/config/README.md)). Moving an item to an authored label-backed lane swaps only the labels in that map and keeps other labels; polling then evaluates label-driven loops. Cards show the latest run and open its drawer. The UI can approve, retry, or cancel a run, pause or unpause a loop, page back through older transcript rows, and tail parent and first-level child activity.
20
20
 
21
- Run conversations summarize adjacent completed tools by their recorded actions, including the files read or changed and recognized test or build invocations. Expand a group to inspect each tool and its result.
21
+ Run conversations remember the reader's filter, opened disclosures, and reading position across drawer reopens and page refreshes. Bookmarks are per run and browser origin, and unread activity stays pending while a saved position is restored. See [Returning to a conversation](../web/README.md#returning-to-a-conversation) for retention and history-recovery limits. Adjacent completed tools are summarized by their recorded actions, such as files read or changed and recognized test or build commands; expand a group to see each tool and result.
22
22
 
23
- The operator API includes `POST /api/cycle-budgets/reset` with an Ask identity. It uses the same request-origin guard and typed error contract as other interventions; stale Asks return 409 and needs-setup mode rejects resets until the runtime is available.
23
+ Direct browser requests for UI routes serve the app shell, so refreshing or sharing a project or configuration URL works. Unknown `/api/*` routes, missing `/assets/*` files, and other file-like paths return 404.
24
24
 
25
- ## Runtime backup and Run evidence
25
+ `POST /api/cycle-budgets/reset` takes an ask identity, with the same origin guard and typed errors as other interventions. A stale ask returns 409, and setup mode rejects resets.
26
26
 
27
- From the instance folder, copy the runtime store while the orchestrator runs:
27
+ After `pnpm build`, reload the page to use the new web bundle; no restart is needed.
28
+
29
+ ## Reload the configuration
30
+
31
+ **Reload configuration** on any Configuration page, `POST /api/instance/reload`, and `SIGHUP` reload `usine.yaml`, `agents/`, and `loops/` without stopping the process. `SIGHUP` no longer ends the process; stop it with Ctrl+C or `SIGTERM`. The [CLI reference](https://docs.usine.dev/reference/configuration/cli/) describes what a reload applies. A refused reload keeps the running configuration and returns:
32
+
33
+ - 422 `FactoryConfigError` for an invalid `usine.yaml`;
34
+ - 409 `RestartRequiredError`, naming the field, for a changed `webhooks` block or a `workers` change that needs a restart (a worker's kind or settings, or a rename);
35
+ - 409 `ReloadPreflightError` if a worker, slot count, or agent brain that the reload adds fails preflight, or a new SSH worker's gateway admin token variable is unset.
36
+
37
+ An accepted reload's response names the loops, agents, labels, repos, and workers it added, removed, or changed. Until a removed worker's last attempt ends, Needs attention counts its running attempts, and `POST /api/workers/:worker/discard-leftovers` returns 409 `LeftoversDiscardRejectedError` with that count. GitHub credentials, the `workers` changes above, and the `webhooks` block apply only at startup.
38
+
39
+ ## Back up and export
40
+
41
+ While the orchestrator runs, `usine backup` copies `.usine/runtime.sqlite` to the path you give, and `.usine/durable.sqlite` next to it with `.durable` before the extension (`/path/to/runtime-backup.durable.sqlite`):
28
42
 
29
43
  ```sh
30
44
  usine backup /path/to/runtime-backup.sqlite
31
45
  ```
32
46
 
33
- `usine backup` copies all runtime tables, including committed WAL data, while the orchestrator runs. It opens the source read-only and doesn't need GitHub credentials.
47
+ Both copies include committed WAL data and are owner-only. The command opens each source read-only, copies the durable storage first, needs no GitHub credentials, and never overwrites a file. Use a new name in an existing writable directory on a file system with hard links. If the runtime copy fails, the durable copy is removed. Without durable storage (no attempt has run yet), it writes only the runtime copy and says so. Errors give the path and cause.
34
48
 
35
- Use a new filename in an existing writable directory on a filesystem that supports hard links. The command writes a complete backup with owner-only permissions and won't overwrite existing files. Errors identify the affected path and cause.
49
+ Keep backups outside the instance folder, and back up the instance files and credentials separately. To restore, stop the orchestrator and restore both files of one backup:
36
50
 
37
- Keep the backup outside the instance folder to protect it against loss of that folder. Back up the authored instance files and credentials separately. To restore, stop the orchestrator, move the existing `.usine/runtime.sqlite` and any `runtime.sqlite-wal` / `runtime.sqlite-shm` files aside, and copy the backup to `.usine/runtime.sqlite`. Restart with the matching instance configuration and engine version. Restoring an older backup also restores its older deduplication ledger, so work performed after that backup can run again.
51
+ 1. Move `.usine/runtime.sqlite` and `.usine/durable.sqlite` aside, with their `-wal` and `-shm` files.
52
+ 2. Copy the runtime backup to `.usine/runtime.sqlite` and the `.durable` backup to `.usine/durable.sqlite`.
53
+ 3. Restart with the matching instance configuration and engine version.
38
54
 
39
- From the instance folder, export one Run into a local JSON file:
55
+ An attempt that was running at backup time reattaches if its checkout under `.usine/workers` still exists. Without its checkout or the durable copy, it fails as interrupted and the run retries. An older backup has an older dedup ledger, so work done after it can run again.
56
+
57
+ `usine export-run` writes one run to a local JSON file:
40
58
 
41
59
  ```sh
42
60
  usine export-run <runId>
43
61
  usine export-run <runId> --output /path/to/evidence.json
44
62
  ```
45
63
 
46
- The default destination is `run-<encoded-runId>.json` in the current directory; characters such as `/` in the Run id are percent-encoded. The output directory must already exist. The command writes a complete file with owner-only permissions, preserves existing destinations, and prints its path after success. A missing Run, unreadable runtime store, encoding failure, or write failure returns a nonzero exit status.
47
-
48
- The versioned JSON contains `exportedAt`, the full durable Run `snapshot` (Event, effective task and Agent, repository revision, Step result, and all Attempts), and every saved transcript envelope for those Attempts in append order. The snapshot and transcripts come from one database transaction, including history beyond the drawer's current page. Active Runs continue accumulating history, so export a settled Run for its final evidence.
64
+ The default is `run-<encoded-runId>.json` in the current directory, with characters such as `/` percent-encoded; the output directory must exist. The command writes a complete owner-only file, never overwrites one, and prints its path. A missing run, unreadable store, encoding failure, or write failure exits nonzero.
49
65
 
50
- Exporting opens the instance's existing `.usine/runtime.sqlite` in read-only mode, without creating tables, running migrations, starting Attempts, or polling triggers. The command keeps the output local. Transcripts can contain repository content and tool output; inspect the file before sharing it. A Run export preserves selected evidence; use `usine backup <path>` for a restorable runtime database, as described in [ADR 0010](../../docs/adr/0010-single-sqlite-runtime-store.md).
66
+ The versioned JSON has `exportedAt`, the full run `snapshot` (event, effective task and agent, revision, step result, and all attempts), and every saved transcript envelope in append order, from one transaction, including history past the drawer's page. Active runs keep adding history, so export a settled run. The export opens `.usine/runtime.sqlite` read-only, without creating tables, migrating, starting attempts, or polling. Transcripts can contain repo content and tool output; check the file before you share it. For a restorable database, use `usine backup` ([ADR 0010](../../docs/adr/0010-single-sqlite-runtime-store.md)).
51
67
 
52
- ## Stopping and updating
68
+ ## Stop the server
53
69
 
54
- Send SIGTERM or press Ctrl+C once to stop trigger polling and drain admitted Attempts. The server waits up to `max_attempt_minutes` from the shutdown request before closing the runtime. A second SIGTERM or Ctrl+C stops the wait immediately. Queued Runs and automatic retries remain saved for the next start. An Attempt interrupted by the deadline or force stop uses normal restart recovery.
70
+ Press Ctrl+C or send `SIGTERM` once to stop polling and drain admitted attempts. The server waits up to `max_attempt_minutes` from the request, or indefinitely if unset; a second signal stops the wait. Queued runs and automatic retries stay saved, and an attempt cut off by the deadline uses normal restart recovery. Start the next `usine up` after the process exits.
55
71
 
56
- After `pnpm build`, reload the page to use the rebuilt web bundle; no process restart is required. Instance configuration and GitHub credentials are read at startup.
72
+ To clear a handled failure, select **Mark resolved** in the run drawer, opened from Needs attention or run history. Resolution survives restarts and keeps the failed outcome, transcript, and retry. A later failed attempt asks for attention again. After a question the button reads **Mark answered**; after a question or blocker, the loop then acts on the item's newest state.
package/dist/backend.d.ts CHANGED
@@ -1,33 +1,52 @@
1
- import type { LoadedInstance } from "@usine/config/instance";
2
1
  import { LoopName } from "@usine/core/loop";
3
2
  import { CycleBudgetAskId } from "@usine/core/cycle-budget";
3
+ import { AttemptId } from "@usine/core/attempt";
4
4
  import { RunId } from "@usine/core/run";
5
5
  import { AgentName } from "@usine/core/agent";
6
- import { type BoardMove, type FactoryOverview, type RunDetailView, type TranscriptRequest, type Operator, type OperatorError, type OperatorObservation, type EditAndRetry, type SessionIntervention } from "@usine/orchestrator/operator";
6
+ import type { RunAgainResult } from "@usine/engine/engine";
7
+ import type { CurrentInstance, InstanceReload, InstanceReloadError } from "@usine/orchestrator/current-instance";
8
+ import type { WorkerName } from "@usine/core/worker";
9
+ import { type BoardMove, type ConfigurationView, type DeskView, type FactoryOverview, type RunDetailView, type RunGitHubChangesView, type TranscriptRequest, type Operator, type OperatorConfiguration, type OperatorError, type OperatorObservation, type EditAndRetry, type SessionIntervention, type TaskView } from "@usine/orchestrator/operator";
10
+ import type { ItemNumber } from "@usine/core/item";
11
+ import type { RepositoryName } from "@usine/core/repository";
7
12
  import { Context, Effect, Layer, Schema } from "effect";
8
13
  declare const BackendUnavailableError_base: Schema.Class<BackendUnavailableError, Schema.TaggedStruct<"BackendUnavailableError", {
9
14
  readonly message: Schema.String;
10
15
  }>, import("effect/Cause").YieldableError>;
11
16
  export declare class BackendUnavailableError extends BackendUnavailableError_base {
12
17
  }
13
- export type BackendError = OperatorError | BackendUnavailableError;
18
+ export type BackendError = OperatorError | BackendUnavailableError | InstanceReloadError;
14
19
  declare const FactoryBackend_base: Context.ServiceClass<FactoryBackend, "@usine/server/FactoryBackend", {
15
20
  readonly overview: Effect.Effect<FactoryOverview, BackendError>;
16
21
  readonly detail: (runId: RunId, request: TranscriptRequest) => Effect.Effect<RunDetailView, BackendError>;
22
+ readonly githubChanges: (runId: RunId) => Effect.Effect<RunGitHubChangesView, BackendError>;
17
23
  readonly approve: (runId: RunId) => Effect.Effect<void, BackendError>;
18
24
  readonly resetCycleBudget: (askId: CycleBudgetAskId) => Effect.Effect<void, BackendError>;
25
+ readonly resolveFailure: (runId: RunId, attemptId: AttemptId) => Effect.Effect<void, BackendError>;
19
26
  readonly retry: (runId: RunId) => Effect.Effect<void, BackendError>;
20
27
  readonly agents: Effect.Effect<ReadonlyArray<AgentName>, BackendError>;
21
28
  readonly editAndRetry: (runId: RunId, edit: EditAndRetry) => Effect.Effect<void, BackendError>;
29
+ readonly runAgain: (runId: RunId, edit: EditAndRetry) => Effect.Effect<RunAgainResult, BackendError>;
22
30
  readonly intervene: (runId: RunId, input: SessionIntervention) => Effect.Effect<void, BackendError>;
23
31
  readonly cancel: (runId: RunId) => Effect.Effect<void, BackendError>;
32
+ readonly discardLeftovers: (worker: WorkerName) => Effect.Effect<void, BackendError>;
24
33
  readonly moveBoardItem: (move: BoardMove) => Effect.Effect<void, BackendError>;
25
34
  readonly pause: (loop: LoopName) => Effect.Effect<void, BackendError>;
26
35
  readonly unpause: (loop: LoopName) => Effect.Effect<void, BackendError>;
36
+ readonly reload: Effect.Effect<InstanceReload, BackendError>;
37
+ readonly task: (repository: RepositoryName, item: ItemNumber) => Effect.Effect<TaskView, BackendError>;
38
+ readonly configuration: Effect.Effect<ConfigurationView, BackendError>;
39
+ readonly desk: Effect.Effect<DeskView, BackendError>;
27
40
  }>;
28
41
  export declare class FactoryBackend extends FactoryBackend_base {
29
42
  }
30
- export declare const liveBackendLayer: (instance: LoadedInstance, operator: Operator["Service"]) => Layer.Layer<FactoryBackend>;
31
- export declare const needsSetupBackendLayer: (instance: LoadedInstance, errorTag: string, observation: OperatorObservation["Service"]) => Layer.Layer<FactoryBackend>;
43
+ type BackendInstance = Pick<CurrentInstance["Service"], "get" | "reload">;
44
+ /** Provides the backend of a running factory, which reads the current instance on each request. */
45
+ export declare const liveBackendLayer: (instances: BackendInstance, operator: Operator["Service"], configuration: OperatorConfiguration["Service"]) => Layer.Layer<FactoryBackend>;
46
+ /**
47
+ * Provides the setup-mode backend, where only the overview, configuration, and reload work. Other
48
+ * operations fail with `BackendUnavailableError`.
49
+ */
50
+ export declare const needsSetupBackendLayer: (instances: BackendInstance, errorTag: string, observation: OperatorObservation["Service"], configuration: OperatorConfiguration["Service"]) => Layer.Layer<FactoryBackend>;
32
51
  export {};
33
52
  //# sourceMappingURL=backend.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EACL,KAAK,SAAS,EACd,KAAK,eAAe,EAEpB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACzB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;;;;AAExD,qBAAa,uBAAwB,SAAQ,4BAI3C;CAAG;AAEL,MAAM,MAAM,YAAY,GAAG,aAAa,GAAG,uBAAuB,CAAC;;uBAK5C,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,YAAY,CAAC;qBAC9C,CACf,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,iBAAiB,KACvB,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,YAAY,CAAC;sBAC7B,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC;+BAC1C,CAAC,KAAK,EAAE,gBAAgB,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC;oBACzE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC;qBAClD,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,EAAE,YAAY,CAAC;2BAC/C,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC;wBAC1E,CAClB,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,mBAAmB,KACvB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC;qBACrB,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC;4BAC5C,CAAC,IAAI,EAAE,SAAS,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC;oBAC9D,CAAC,IAAI,EAAE,QAAQ,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC;sBACnD,CAAC,IAAI,EAAE,QAAQ,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC;;AApB3E,qBAAa,cAAe,SAAQ,mBAsBD;CAAG;AAEtC,eAAO,MAAM,gBAAgB,aACjB,cAAc,YACd,QAAQ,CAAC,SAAS,CAAC,KAC5B,KAAK,CAAC,KAAK,CAAC,cAAc,CAczB,CAAC;AAEL,eAAO,MAAM,sBAAsB,aACvB,cAAc,YACd,MAAM,eACH,mBAAmB,CAAC,SAAS,CAAC,KAC1C,KAAK,CAAC,KAAK,CAAC,cAAc,CA2B5B,CAAC"}
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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"}
package/dist/backend.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { LoopName } from "@usine/core/loop";
2
2
  import { CycleBudgetAskId } from "@usine/core/cycle-budget";
3
+ import { AttemptId } from "@usine/core/attempt";
3
4
  import { RunId } from "@usine/core/run";
4
5
  import { AgentName } from "@usine/core/agent";
5
6
  import { NeedsSetupOverview, } from "@usine/orchestrator/operator";
@@ -10,41 +11,61 @@ export class BackendUnavailableError extends Schema.TaggedError("@usine/server/B
10
11
  }
11
12
  export class FactoryBackend extends Context.Service()("@usine/server/FactoryBackend") {
12
13
  }
13
- export const liveBackendLayer = (instance, operator) => Layer.succeed(FactoryBackend)({
14
- overview: operator.overview(instance),
15
- detail: operator.detail,
16
- approve: operator.approve,
14
+ const rosterOf = (instances) => Effect.map(instances.get, ({ agents }) => agents.map(({ name }) => name));
15
+ /** Provides the backend of a running factory, which reads the current instance on each request. */
16
+ export const liveBackendLayer = (instances, operator, configuration) => Layer.succeed(FactoryBackend)({
17
+ overview: Effect.flatMap(instances.get, operator.overview),
18
+ detail: (runId, request) => Effect.flatMap(instances.get, (instance) => operator.detail(instance, runId, request)),
19
+ githubChanges: operator.githubChanges,
20
+ approve: (runId) => Effect.flatMap(instances.get, (instance) => operator.approve(instance, runId)),
17
21
  resetCycleBudget: operator.resetCycleBudget,
18
- retry: operator.retry,
19
- agents: Effect.succeed(instance.agents.map(({ name }) => name)),
20
- editAndRetry: (runId, edit) => operator.editAndRetry(instance, runId, edit),
21
- intervene: (runId, input) => operator.intervene(instance, runId, input),
22
+ retry: (runId) => Effect.flatMap(instances.get, (instance) => operator.retry(instance, runId)),
23
+ resolveFailure: operator.resolveFailure,
24
+ agents: rosterOf(instances),
25
+ editAndRetry: (runId, edit) => Effect.flatMap(instances.get, (instance) => operator.editAndRetry(instance, runId, edit)),
26
+ runAgain: (runId, edit) => Effect.flatMap(instances.get, (instance) => operator.runAgain(instance, runId, edit)),
27
+ intervene: (runId, input) => Effect.flatMap(instances.get, (instance) => operator.intervene(instance, runId, input)),
22
28
  cancel: operator.cancel,
23
- moveBoardItem: (move) => operator.moveBoardItem(instance, move),
29
+ discardLeftovers: operator.discardLeftovers,
30
+ moveBoardItem: (move) => Effect.flatMap(instances.get, (instance) => operator.moveBoardItem(instance, move)),
24
31
  pause: (loop) => operator.pause(loop).pipe(Effect.asVoid),
25
32
  unpause: (loop) => operator.unpause(loop).pipe(Effect.asVoid),
33
+ reload: instances.reload,
34
+ task: (repository, item) => Effect.flatMap(instances.get, (instance) => operator.task(instance, repository, item)),
35
+ configuration: Effect.flatMap(instances.get, configuration.view),
36
+ desk: Effect.flatMap(instances.get, operator.desk),
26
37
  });
27
- export const needsSetupBackendLayer = (instance, errorTag, observation) => {
38
+ /**
39
+ * Provides the setup-mode backend, where only the overview, configuration, and reload work. Other
40
+ * operations fail with `BackendUnavailableError`.
41
+ */
42
+ export const needsSetupBackendLayer = (instances, errorTag, observation, configuration) => {
28
43
  const unavailable = new BackendUnavailableError({
29
- message: "GitHub App setup is required before operator actions are available.",
44
+ message: "The GitHub App is not set up. Set it up to use operator actions.",
30
45
  });
31
- const overview = observation
32
- .overview(instance)
33
- .pipe(Effect.map(({ projects, loops, needsYou }) => new NeedsSetupOverview({ errorTag, projects, loops, needsYou })));
46
+ const overview = Effect.flatMap(instances.get, observation.overview).pipe(Effect.map(({ projects, loops, needsYou }) => new NeedsSetupOverview({ errorTag, projects, loops, needsYou })));
34
47
  const fail = () => Effect.fail(unavailable);
35
48
  return Layer.succeed(FactoryBackend)({
36
49
  overview,
37
- detail: observation.detail,
50
+ detail: (runId, request) => Effect.flatMap(instances.get, (instance) => observation.detail(instance, runId, request)),
51
+ githubChanges: fail,
38
52
  approve: fail,
39
53
  resetCycleBudget: fail,
40
54
  retry: fail,
41
- agents: Effect.succeed(instance.agents.map(({ name }) => name)),
55
+ resolveFailure: fail,
56
+ agents: rosterOf(instances),
42
57
  editAndRetry: fail,
58
+ runAgain: fail,
43
59
  intervene: fail,
44
60
  cancel: fail,
61
+ discardLeftovers: fail,
45
62
  moveBoardItem: fail,
46
63
  pause: fail,
47
64
  unpause: fail,
65
+ reload: instances.reload,
66
+ task: (repository, item) => Effect.flatMap(instances.get, (instance) => observation.task(instance, repository, item)),
67
+ configuration: Effect.flatMap(instances.get, configuration.view),
68
+ desk: Effect.flatMap(instances.get, observation.desk),
48
69
  });
49
70
  };
50
71
  //# sourceMappingURL=backend.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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"}
package/dist/backup.d.ts CHANGED
@@ -1,4 +1,7 @@
1
- import { Effect, Path } from "effect";
2
- /** Copies the current instance's runtime database to `output`, resolved from the working directory. */
3
- export declare const backup: (output: string) => Effect.Effect<void, import("@usine/engine/local-sqlite").RuntimeStoreBackupError, Path.Path>;
1
+ import { Effect, FileSystem, Path } from "effect";
2
+ /**
3
+ * Copies the runtime database to `output` and the durable storage next to it, durable storage
4
+ * first. If the runtime copy fails, removes the durable copy.
5
+ */
6
+ export declare const backup: (output: string) => Effect.Effect<void, import("@usine/durable/backup").DurableStorageBackupError | import("@usine/engine/local-sqlite").RuntimeStoreBackupError, FileSystem.FileSystem | Path.Path>;
4
7
  //# sourceMappingURL=backup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"backup.d.ts","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"AAEA,OAAO,EAAW,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAE/C,uGAAuG;AACvG,eAAO,MAAM,MAAM,kHAOjB,CAAC"}
1
+ {"version":3,"file":"backup.d.ts","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"AAGA,OAAO,EAAW,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAQ3D;;;GAGG;AACH,eAAO,MAAM,MAAM,sMA6BjB,CAAC"}
package/dist/backup.js CHANGED
@@ -1,13 +1,33 @@
1
1
  import { InstanceDirectory } from "@usine/core/instance";
2
+ import { backup as backupDurableStorage } from "@usine/durable/backup";
2
3
  import { backup as backupDatabase } from "@usine/engine/local-sqlite";
3
- import { Console, Effect, Path } from "effect";
4
- /** Copies the current instance's runtime database to `output`, resolved from the working directory. */
4
+ import { Console, Effect, FileSystem, Path } from "effect";
5
+ /** Returns the durable storage copy path: `backup.sqlite` becomes `backup.durable.sqlite`. */
6
+ const durableDestination = (path, destination) => {
7
+ const { dir, name, ext } = path.parse(destination);
8
+ return path.join(dir, `${name}.durable${ext === "" ? ".sqlite" : ext}`);
9
+ };
10
+ /**
11
+ * Copies the runtime database to `output` and the durable storage next to it, durable storage
12
+ * first. If the runtime copy fails, removes the durable copy.
13
+ */
5
14
  export const backup = Effect.fn("Cli.backup")(function* (output) {
6
15
  const directory = InstanceDirectory.make(process.cwd());
16
+ const fileSystem = yield* FileSystem.FileSystem;
7
17
  const path = yield* Path.Path;
8
18
  const destination = path.resolve(output);
9
- yield* Effect.annotateCurrentSpan({ instanceDirectory: directory, destination });
10
- yield* backupDatabase(directory, destination);
11
- yield* Console.log(`Backed up runtime store to ${destination}`);
19
+ const durable = durableDestination(path, destination);
20
+ yield* Effect.annotateCurrentSpan({ instanceDirectory: directory, destination, durable });
21
+ const copied = yield* backupDurableStorage(directory, durable);
22
+ yield* backupDatabase(directory, destination).pipe(Effect.tapError(() => copied
23
+ ? fileSystem.remove(durable).pipe(Effect.tapError((error) => Effect.logWarning("Could not remove the durable copy of a failed backup", {
24
+ durable,
25
+ message: error.message,
26
+ })), Effect.ignore)
27
+ : Effect.void));
28
+ yield* Console.log(`Backed up the runtime store to ${destination}`);
29
+ yield* Console.log(copied
30
+ ? `Backed up the durable storage to ${durable}`
31
+ : "No durable storage to back up: .usine/durable.sqlite does not exist");
12
32
  });
13
33
  //# sourceMappingURL=backup.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,MAAM,IAAI,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAE/C,uGAAuG;AACvG,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,EAAE,MAAc;IACrE,MAAM,SAAS,GAAG,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACxD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC9B,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,iBAAiB,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC;IACjF,KAAK,CAAC,CAAC,cAAc,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAC9C,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,8BAA8B,WAAW,EAAE,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,MAAM,IAAI,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,MAAM,IAAI,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAE3D,8FAA8F;AAC9F,MAAM,kBAAkB,GAAG,CAAC,IAAe,EAAE,WAAmB,EAAU,EAAE;IAC1E,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IACnD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,WAAW,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;AAC1E,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,EAAE,MAAc;IACrE,MAAM,SAAS,GAAG,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACxD,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;IAChD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC9B,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACtD,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE,iBAAiB,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1F,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,oBAAoB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC/D,KAAK,CAAC,CAAC,cAAc,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,IAAI,CAChD,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CACnB,MAAM;QACJ,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAC7B,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CACxB,MAAM,CAAC,UAAU,CAAC,sDAAsD,EAAE;YACxE,OAAO;YACP,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,CAAC,CACH,EACD,MAAM,CAAC,MAAM,CACd;QACH,CAAC,CAAC,MAAM,CAAC,IAAI,CAChB,CACF,CAAC;IACF,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,kCAAkC,WAAW,EAAE,CAAC,CAAC;IACpE,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,CAChB,MAAM;QACJ,CAAC,CAAC,oCAAoC,OAAO,EAAE;QAC/C,CAAC,CAAC,qEAAqE,CAC1E,CAAC;AACJ,CAAC,CAAC,CAAC"}
package/dist/cli.d.ts CHANGED
@@ -2,10 +2,12 @@ import { Duration, Effect, FileSystem, Option, Schema } from "effect";
2
2
  import { ChildProcessSpawner } from "effect/unstable/process";
3
3
  import type { ServeError } from "effect/unstable/http/HttpServerError";
4
4
  export declare const DEFAULT_PORT = 7331;
5
+ /** The address that the HTTP server listens on and the port check probes. */
6
+ export declare const SERVER_HOST = "127.0.0.1";
5
7
  export declare const BROWSER_OPEN_TIMEOUT = "2 seconds";
6
8
  export declare const Port: Schema.brand<Schema.Int, "ServerPort">;
7
9
  export type Port = typeof Port.Type;
8
- export declare const CLI_USAGE = "Usage:\n usine up [--port <port>] [--allow-volatile-instance]\n usine backup <path>\n usine export-run <runId> [--output <path>]\n usine --help\n usine --version\n\nOptions:\n -p, --port <port> Listen on this port (default: 7331)\n --allow-volatile-instance Start even when the instance folder sits under a temp path\n -o, --output <path> Write Run evidence here (default: run-<encoded-runId>.json)\n -h, --help Print this usage\n -v, --version Print the pinned @usine/server version\n\nEnvironment:\n USINE_PORT Listen on this port when --port is absent\n USINE_ALLOW_VOLATILE_INSTANCE Same as --allow-volatile-instance when set to 1";
10
+ export declare const CLI_USAGE = "Usage:\n usine up [--port <port>] [--allow-volatile-instance]\n usine backup <path>\n usine export-run <runId> [--output <path>]\n usine --help\n usine --version\n\nOptions:\n -p, --port <port> Listen on this port (default: 7331)\n --allow-volatile-instance Start even when the instance folder is in a temporary directory\n -o, --output <path> Write the run evidence to this file (default: run-<encoded-runId>.json)\n -h, --help Print this help\n -v, --version Print the installed @usine/server version\n\nEnvironment:\n USINE_PORT Listen on this port if --port is not set\n USINE_ALLOW_VOLATILE_INSTANCE Set to 1 for the same effect as --allow-volatile-instance";
9
11
  declare const CliUsageError_base: Schema.Class<CliUsageError, Schema.TaggedStruct<"CliUsageError", {
10
12
  readonly message: Schema.String;
11
13
  }>, import("effect/Cause").YieldableError>;
@@ -18,7 +20,10 @@ export declare class CliVersionError extends CliVersionError_base {
18
20
  }
19
21
  declare const PortInUseError_base: Schema.Class<PortInUseError, Schema.TaggedStruct<"PortInUseError", {
20
22
  readonly port: Schema.brand<Schema.Int, "ServerPort">;
23
+ /** The PID of the listening process, or `None` if lsof can't find it. */
24
+ readonly holder: Schema.Option<Schema.Int>;
21
25
  }>, import("effect/Cause").YieldableError>;
26
+ /** Another process is listening on the HTTP port. */
22
27
  export declare class PortInUseError extends PortInUseError_base {
23
28
  }
24
29
  declare const ServerStartError_base: Schema.Class<ServerStartError, Schema.TaggedStruct<"ServerStartError", {
@@ -31,7 +36,7 @@ declare const VolatileInstanceDirectoryError_base: Schema.Class<VolatileInstance
31
36
  readonly directory: Schema.String;
32
37
  readonly root: Schema.String;
33
38
  }>, import("effect/Cause").YieldableError>;
34
- /** Raised when the instance folder sits under a path the OS may clear, since its runtime store is the only copy of run history. */
39
+ /** The instance folder is in a directory the OS may clear, which would delete the run history. */
35
40
  export declare class VolatileInstanceDirectoryError extends VolatileInstanceDirectoryError_base {
36
41
  }
37
42
  declare const BrowserOpenError_base: Schema.Class<BrowserOpenError, Schema.TaggedStruct<"BrowserOpenError", {
@@ -73,15 +78,23 @@ export declare const CliCommand: Schema.Union<readonly [typeof HelpCommand, type
73
78
  export type CliCommand = typeof CliCommand.Type;
74
79
  /** Parses process arguments and the environment into one validated CLI command. */
75
80
  export declare const parseCli: (args: readonly string[], environment: CliEnvironment) => Effect.Effect<BackupCommand | ExportRunCommand | HelpCommand | UpCommand | VersionCommand, CliUsageError, never>;
76
- /** Directories the OS may clear: `os.tmpdir()`, `$TMPDIR`, and the usual temp locations. */
81
+ /**
82
+ * Returns the directories the OS may clear: `os.tmpdir()`, `$TMPDIR`, and common temp locations.
83
+ */
77
84
  export declare const volatileRoots: (environmentTmpdir: Option.Option<string>) => ReadonlyArray<string>;
78
- /** Returns the volatile root the directory sits under, if any. Paths are compared as given, with symlinks unresolved. */
85
+ /** Returns the volatile root that contains `directory`, if any, without resolving symlinks. */
79
86
  export declare const volatileRoot: (directory: string, roots: ReadonlyArray<string>) => Option.Option<string>;
80
- /** Reads the version of this installed server from its package.json. */
81
87
  export declare const packageVersion: () => Effect.Effect<string, CliVersionError, FileSystem.FileSystem>;
82
- /** Maps a bind failure to the CLI's startup error, so an address already in use is reported as such. */
88
+ /** Returns `PortInUseError` for `EADDRINUSE`, otherwise `ServerStartError`. */
83
89
  export declare const serverStartError: (port: Port, error: ServeError) => PortInUseError | ServerStartError;
84
- /** Opens the setup page in a browser; call it only after the HTTP server has bound. */
90
+ /** Fails with `PortInUseError`, with the listening PID if found. */
91
+ export declare const portInUse: (port: number & import("effect/Brand").Brand<"ServerPort">) => Effect.Effect<never, PortInUseError, ChildProcessSpawner.ChildProcessSpawner>;
92
+ /**
93
+ * Fails with `PortInUseError` if another process is listening on `port`. Call it before `usine up`
94
+ * builds its runtime.
95
+ */
96
+ export declare const refuseTakenPort: (port: number & import("effect/Brand").Brand<"ServerPort">) => Effect.Effect<undefined, PortInUseError | ServerStartError, ChildProcessSpawner.ChildProcessSpawner>;
97
+ /** Opens the setup page in a browser. Call it only after the HTTP server has bound. */
85
98
  export declare const openBrowser: (url: string, timeout?: Duration.Input | undefined) => Effect.Effect<undefined, BrowserOpenError, ChildProcessSpawner.ChildProcessSpawner>;
86
99
  export {};
87
100
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAIA,OAAO,EAAW,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC/E,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAEvE,eAAO,MAAM,YAAY,OAAQ,CAAC;AAClC,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAEhD,eAAO,MAAM,IAAI,wCAEiB,CAAC;AACnC,MAAM,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,CAAC;AAIpC,eAAO,MAAM,SAAS,wtBAgB2D,CAAC;;;;AAElF,qBAAa,aAAc,SAAQ,kBAGlC;CAAG;;;;AAEJ,qBAAa,eAAgB,SAAQ,oBAEW;CAAG;;;;AAEnD,qBAAa,cAAe,SAAQ,mBAED;CAAG;;;;;AAEtC,qBAAa,gBAAiB,SAAQ,qBAEuB;CAAG;;;;;AAEhE,mIAAmI;AACnI,qBAAa,8BAA+B,SAAQ,mCAKlD;CAAG;;;;;AAEL,qBAAa,gBAAiB,SAAQ,qBAKpC;CAAG;;AAEL,qBAAa,WAAY,SAAQ,gBAGhC;CAAG;;AAEJ,qBAAa,cAAe,SAAQ,mBAEb;CAAG;;;;;AAE1B,qBAAa,SAAU,SAAQ,cAG9B;CAAG;;;;;AAEJ,4EAA4E;AAC5E,qBAAa,gBAAiB,SAAQ,qBAKpC;CAAG;;;;AAEL,0EAA0E;AAC1E,qBAAa,aAAc,SAAQ,kBAGlC;CAAG;AAEJ,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;CACvD;AAED,eAAO,MAAM,UAAU,qIAMrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAC;AAyEhD,mFAAmF;AACnF,eAAO,MAAM,QAAQ,4KAmBnB,CAAC;AAEH,4FAA4F;AAC5F,eAAO,MAAM,aAAa,sBAAuB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAG,aAAa,CAAC,MAAM,CAW5D,CAAC;AAKlC,yHAAyH;AACzH,eAAO,MAAM,YAAY,cACZ,MAAM,SACV,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,MAAM,CAGtB,CAAC;AAMF,wEAAwE;AACxE,eAAO,MAAM,cAAc,qEAUzB,CAAC;AAIH,wGAAwG;AACxG,eAAO,MAAM,gBAAgB,SAAU,IAAI,SAAS,UAAU,sCAGN,CAAC;AAUzD,uFAAuF;AACvF,eAAO,MAAM,WAAW,4IAyBtB,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAKA,OAAO,EAAyB,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC7F,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAEvE,eAAO,MAAM,YAAY,OAAQ,CAAC;AAClC,6EAA6E;AAC7E,eAAO,MAAM,WAAW,cAAc,CAAC;AACvC,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAGhD,eAAO,MAAM,IAAI,wCAEiB,CAAC;AACnC,MAAM,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,CAAC;AAIpC,eAAO,MAAM,SAAS,ovBAgBqE,CAAC;;;;AAE5F,qBAAa,aAAc,SAAQ,kBAGlC;CAAG;;;;AAEJ,qBAAa,eAAgB,SAAQ,oBAEW;CAAG;;;IAOjD,yEAAyE;;;AAL3E,qDAAqD;AACrD,qBAAa,cAAe,SAAQ,mBAMlC;CAAG;;;;;AAEL,qBAAa,gBAAiB,SAAQ,qBAEuB;CAAG;;;;;AAEhE,kGAAkG;AAClG,qBAAa,8BAA+B,SAAQ,mCAKlD;CAAG;;;;;AAEL,qBAAa,gBAAiB,SAAQ,qBAKpC;CAAG;;AAEL,qBAAa,WAAY,SAAQ,gBAGhC;CAAG;;AAEJ,qBAAa,cAAe,SAAQ,mBAEb;CAAG;;;;;AAE1B,qBAAa,SAAU,SAAQ,cAG9B;CAAG;;;;;AAEJ,4EAA4E;AAC5E,qBAAa,gBAAiB,SAAQ,qBAKpC;CAAG;;;;AAEL,0EAA0E;AAC1E,qBAAa,aAAc,SAAQ,kBAGlC;CAAG;AAEJ,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;CACvD;AAED,eAAO,MAAM,UAAU,qIAMrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAC;AAyEhD,mFAAmF;AACnF,eAAO,MAAM,QAAQ,4KAmBnB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,sBAAuB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAG,aAAa,CAAC,MAAM,CAW5D,CAAC;AAKlC,+FAA+F;AAC/F,eAAO,MAAM,YAAY,cACZ,MAAM,SACV,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,MAAM,CAGtB,CAAC;AAMF,eAAO,MAAM,cAAc,qEAUzB,CAAC;AAIH,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,SAAU,IAAI,SAAS,UAAU,sCAGN,CAAC;AA6BzD,oEAAoE;AACpE,eAAO,MAAM,SAAS,8IAOpB,CAAC;AAkBH;;;GAGG;AACH,eAAO,MAAM,eAAe,qKAE1B,CAAC;AAUH,uFAAuF;AACvF,eAAO,MAAM,WAAW,4IAyBtB,CAAC"}
package/dist/cli.js CHANGED
@@ -1,11 +1,15 @@
1
+ import { createServer } from "node:net";
1
2
  import { tmpdir } from "node:os";
2
3
  import { resolve, sep } from "node:path";
3
4
  import { fileURLToPath } from "node:url";
4
5
  import { RunId } from "@usine/core/run";
5
- import { Console, Duration, Effect, FileSystem, Option, Schema } from "effect";
6
+ import { Array as Arr, Console, Duration, Effect, FileSystem, Option, Schema } from "effect";
6
7
  import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
7
8
  export const DEFAULT_PORT = 7_331;
9
+ /** The address that the HTTP server listens on and the port check probes. */
10
+ export const SERVER_HOST = "127.0.0.1";
8
11
  export const BROWSER_OPEN_TIMEOUT = "2 seconds";
12
+ const PORT_HOLDER_TIMEOUT = "2 seconds";
9
13
  export const Port = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65_535 }, { message: "Expected a TCP port" })).pipe(Schema.brand("ServerPort"));
10
14
  const PortFromString = Schema.NumberFromString.pipe(Schema.decodeTo(Port));
11
15
  export const CLI_USAGE = `Usage:
@@ -17,23 +21,28 @@ export const CLI_USAGE = `Usage:
17
21
 
18
22
  Options:
19
23
  -p, --port <port> Listen on this port (default: 7331)
20
- --allow-volatile-instance Start even when the instance folder sits under a temp path
21
- -o, --output <path> Write Run evidence here (default: run-<encoded-runId>.json)
22
- -h, --help Print this usage
23
- -v, --version Print the pinned @usine/server version
24
+ --allow-volatile-instance Start even when the instance folder is in a temporary directory
25
+ -o, --output <path> Write the run evidence to this file (default: run-<encoded-runId>.json)
26
+ -h, --help Print this help
27
+ -v, --version Print the installed @usine/server version
24
28
 
25
29
  Environment:
26
- USINE_PORT Listen on this port when --port is absent
27
- USINE_ALLOW_VOLATILE_INSTANCE Same as --allow-volatile-instance when set to 1`;
30
+ USINE_PORT Listen on this port if --port is not set
31
+ USINE_ALLOW_VOLATILE_INSTANCE Set to 1 for the same effect as --allow-volatile-instance`;
28
32
  export class CliUsageError extends Schema.TaggedError("@usine/server/CliUsageError")("CliUsageError", { message: Schema.String }) {
29
33
  }
30
34
  export class CliVersionError extends Schema.TaggedError("@usine/server/CliVersionError")("CliVersionError", { cause: Schema.Defect() }) {
31
35
  }
32
- export class PortInUseError extends Schema.TaggedError("@usine/server/PortInUseError")("PortInUseError", { port: Port }) {
36
+ /** Another process is listening on the HTTP port. */
37
+ export class PortInUseError extends Schema.TaggedError("@usine/server/PortInUseError")("PortInUseError", {
38
+ port: Port,
39
+ /** The PID of the listening process, or `None` if lsof can't find it. */
40
+ holder: Schema.Option(Schema.Int),
41
+ }) {
33
42
  }
34
43
  export class ServerStartError extends Schema.TaggedError("@usine/server/ServerStartError")("ServerStartError", { port: Port, cause: Schema.Defect() }) {
35
44
  }
36
- /** Raised when the instance folder sits under a path the OS may clear, since its runtime store is the only copy of run history. */
45
+ /** The instance folder is in a directory the OS may clear, which would delete the run history. */
37
46
  export class VolatileInstanceDirectoryError extends Schema.TaggedError("@usine/server/VolatileInstanceDirectoryError")("VolatileInstanceDirectoryError", {
38
47
  directory: Schema.String,
39
48
  root: Schema.String,
@@ -137,7 +146,9 @@ export const parseCli = Effect.fn("Cli.parse")((args, environment) => {
137
146
  return Effect.fail(usageError());
138
147
  return upCommand(args.slice(1), environment);
139
148
  });
140
- /** Directories the OS may clear: `os.tmpdir()`, `$TMPDIR`, and the usual temp locations. */
149
+ /**
150
+ * Returns the directories the OS may clear: `os.tmpdir()`, `$TMPDIR`, and common temp locations.
151
+ */
141
152
  export const volatileRoots = (environmentTmpdir) => [
142
153
  ...new Set([
143
154
  tmpdir(),
@@ -150,13 +161,12 @@ export const volatileRoots = (environmentTmpdir) => [
150
161
  .filter((root) => root.length > 0)
151
162
  .map((root) => resolve(root));
152
163
  const isUnder = (directory, root) => directory === root || directory.startsWith(root.endsWith(sep) ? root : `${root}${sep}`);
153
- /** Returns the volatile root the directory sits under, if any. Paths are compared as given, with symlinks unresolved. */
164
+ /** Returns the volatile root that contains `directory`, if any, without resolving symlinks. */
154
165
  export const volatileRoot = (directory, roots) => {
155
166
  const resolved = resolve(directory);
156
167
  return Option.fromNullishOr(roots.find((root) => isUnder(resolved, root)));
157
168
  };
158
169
  const PackageMetadataFromJson = Schema.fromJsonString(Schema.Struct({ version: Schema.NonEmptyString }));
159
- /** Reads the version of this installed server from its package.json. */
160
170
  export const packageVersion = Effect.fn("Cli.packageVersion")(function* () {
161
171
  const fileSystem = yield* FileSystem.FileSystem;
162
172
  const packagePath = fileURLToPath(new URL("../package.json", import.meta.url));
@@ -167,10 +177,50 @@ export const packageVersion = Effect.fn("Cli.packageVersion")(function* () {
167
177
  return metadata.version;
168
178
  });
169
179
  const AddressInUse = Schema.Struct({ code: Schema.Literal("EADDRINUSE") });
170
- /** Maps a bind failure to the CLI's startup error, so an address already in use is reported as such. */
180
+ /** Returns `PortInUseError` for `EADDRINUSE`, otherwise `ServerStartError`. */
171
181
  export const serverStartError = (port, error) => Schema.is(AddressInUse)(error.cause)
172
- ? new PortInUseError({ port })
182
+ ? new PortInUseError({ port, holder: Option.none() })
173
183
  : new ServerStartError({ port, cause: error.cause });
184
+ const ProcessIdFromString = Schema.NumberFromString.pipe(Schema.decodeTo(Schema.Int));
185
+ /**
186
+ * Returns the PID listening on `port` from lsof, or `None` if lsof is missing or can't see the
187
+ * process.
188
+ */
189
+ const portHolder = Effect.fn("Cli.portHolder")(function* (port) {
190
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
191
+ const lines = yield* spawner
192
+ .lines(ChildProcess.make("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], {
193
+ stdin: "ignore",
194
+ stderr: "ignore",
195
+ }))
196
+ .pipe(Effect.timeout(PORT_HOLDER_TIMEOUT), Effect.tapError((cause) => Effect.logDebug("Could not look up the process listening on the port", { port, cause })), Effect.orElseSucceed(() => []));
197
+ return Arr.findFirst(lines, (line) => Schema.decodeUnknownOption(ProcessIdFromString)(line.trim()));
198
+ });
199
+ /** Fails with `PortInUseError`, with the listening PID if found. */
200
+ export const portInUse = Effect.fn("Cli.portInUse")(function* (port) {
201
+ const holder = yield* portHolder(port);
202
+ yield* Effect.logWarning("Another process listens on the HTTP port", {
203
+ port,
204
+ holder: Option.getOrUndefined(holder),
205
+ });
206
+ return yield* new PortInUseError({ port, holder });
207
+ });
208
+ const portTaken = (port) => Effect.callback((resume) => {
209
+ const server = createServer();
210
+ server.once("error", (cause) => resume(Schema.is(AddressInUse)(cause)
211
+ ? Effect.succeed(true)
212
+ : Effect.fail(new ServerStartError({ port, cause }))));
213
+ server.listen({ host: SERVER_HOST, port }, () => server.close(() => resume(Effect.succeed(false))));
214
+ return Effect.sync(() => server.close());
215
+ });
216
+ /**
217
+ * Fails with `PortInUseError` if another process is listening on `port`. Call it before `usine up`
218
+ * builds its runtime.
219
+ */
220
+ export const refuseTakenPort = Effect.fn("Cli.refuseTakenPort")(function* (port) {
221
+ if (yield* portTaken(port))
222
+ return yield* portInUse(port);
223
+ });
174
224
  const browserCommand = (url) => {
175
225
  if (process.platform === "darwin")
176
226
  return { command: "open", args: [url] };
@@ -179,7 +229,7 @@ const browserCommand = (url) => {
179
229
  }
180
230
  return { command: "xdg-open", args: [url] };
181
231
  };
182
- /** Opens the setup page in a browser; call it only after the HTTP server has bound. */
232
+ /** Opens the setup page in a browser. Call it only after the HTTP server has bound. */
183
233
  export const openBrowser = Effect.fn("Cli.openBrowser")(function* (url, timeout = BROWSER_OPEN_TIMEOUT) {
184
234
  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
185
235
  const { command, args } = browserCommand(url);