@aron98/load-lock 0.1.1 → 1.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 (49) hide show
  1. package/README.md +37 -12
  2. package/demo/demo.sh +1 -1
  3. package/dist/adapters/opencode/plugin.js +39 -18
  4. package/dist/adapters/opencode/tui.d.ts +31 -0
  5. package/dist/adapters/opencode/tui.js +268 -0
  6. package/dist/cli/hook.js +35 -1
  7. package/dist/cli/install.js +740 -61
  8. package/dist/cli/main.js +36 -61
  9. package/dist/cli/top.d.ts +7 -0
  10. package/dist/cli/top.js +98 -0
  11. package/dist/core/daemon.d.ts +25 -4
  12. package/dist/core/daemon.js +67 -30
  13. package/dist/core/persist.js +5 -12
  14. package/dist/core/state.js +7 -0
  15. package/dist/dashboard/feed.d.ts +74 -0
  16. package/dist/dashboard/feed.js +256 -0
  17. package/dist/dashboard/frame.d.ts +12 -0
  18. package/dist/dashboard/frame.js +94 -0
  19. package/dist/dashboard/view.d.ts +148 -0
  20. package/dist/dashboard/view.js +214 -0
  21. package/dist/mcp/server.d.ts +2 -1
  22. package/dist/mcp/server.js +54 -19
  23. package/dist/policy/text.d.ts +1 -1
  24. package/dist/policy/text.js +3 -0
  25. package/dist/shared/client.d.ts +10 -0
  26. package/dist/shared/client.js +41 -14
  27. package/dist/shared/protocol.d.ts +29 -0
  28. package/dist/shared/transport.d.ts +40 -0
  29. package/dist/shared/transport.js +136 -0
  30. package/dist/shared/wait-turn.d.ts +29 -0
  31. package/dist/shared/wait-turn.js +315 -0
  32. package/dist/test/dashboard.test.d.ts +1 -0
  33. package/dist/test/dashboard.test.js +405 -0
  34. package/dist/test/hook.test.js +53 -3
  35. package/dist/test/install.test.d.ts +1 -0
  36. package/dist/test/install.test.js +658 -0
  37. package/dist/test/mcp.test.js +36 -1
  38. package/dist/test/platform.test.d.ts +1 -0
  39. package/dist/test/platform.test.js +193 -0
  40. package/dist/test/testutil.js +4 -1
  41. package/dist/test/wait-turn.test.d.ts +1 -0
  42. package/dist/test/wait-turn.test.js +376 -0
  43. package/docs/ADAPTERS.md +5 -2
  44. package/docs/AGENT_GUIDE.md +33 -1
  45. package/docs/ARCHITECTURE.md +25 -5
  46. package/docs/COMPATIBILITY.md +215 -26
  47. package/docs/OPERATIONS.md +313 -8
  48. package/package.json +25 -1
  49. package/skills/load-lock/SKILL.md +4 -0
@@ -3,19 +3,35 @@
3
3
  ## Install
4
4
 
5
5
  Requirements: Node 20+, Linux (macOS works with the limitations noted below;
6
- Windows would need a named-pipe adapter — not implemented).
6
+ Windows has a named-pipe transport adapter — implemented and unit-tested on
7
+ Linux via platform injection, not yet verified on a Windows machine; see the
8
+ Windows section at the bottom).
7
9
 
8
10
  ```sh
9
11
  npm install -g @aron98/load-lock # or: npm install -g ./aron98-load-lock-<ver>.tgz
10
12
  load-lock install --harness opencode
11
13
  load-lock install --harness claude-code
12
14
  load-lock install --harness codex
15
+ load-lock install --harness gemini-cli
16
+ load-lock install --harness copilot-cli
17
+ load-lock install --harness cline
18
+ load-lock install --harness roo-code
19
+ load-lock install --harness goose
20
+ load-lock install --harness cursor --project /path/to/repo
13
21
  load-lock install --harness generic # prints MCP snippets for other harnesses
14
22
  ```
15
23
 
16
24
  What each installer does (all merges are non-destructive; uninstall removes
17
25
  exactly the entries it added):
18
26
 
27
+ Hook-bearing installers (claude-code, codex, cursor) recognize their own
28
+ hook entries by the JSON-quoted CLI path embedded in the hook command, so
29
+ re-running an installer is idempotent and uninstall removes exactly our
30
+ entries, leaving user hooks alone. This is contract-tested via temp-HOME
31
+ round-trips in `src/test/install.test.ts` for all three (an earlier matcher
32
+ never matched its own entries — duplicate installs and hooks that survived
33
+ uninstall — see issue #19); not live-verified inside the real CLIs.
34
+
19
35
  - **opencode** — writes a plugin stub to
20
36
  `~/.config/opencode/plugins/load-lock/` (npm-installed against the global
21
37
  package), registers it in `~/.config/opencode/opencode.json`.
@@ -27,15 +43,52 @@ exactly the entries it added):
27
43
  `Stop`), appends `[mcp_servers.load-lock]` to `~/.codex/config.toml` if
28
44
  absent, adds a marked section to `~/.codex/AGENTS.md`. **Then run `/hooks`
29
45
  inside Codex once to trust the hooks.**
30
- - **generic** — prints MCP stdio config snippets (Gemini CLI, Cursor,
31
- Cline/Roo, Goose) plus a suggested instructions-file section.
46
+ - **gemini-cli** — writes `mcpServers.load-lock` (stdio: `node` + absolute
47
+ path to `dist/mcp/main.js`) into `~/.gemini/settings.json`, adds a marked
48
+ section to `~/.gemini/GEMINI.md`. MCP-fallback tier: Gemini CLI has no
49
+ hooks and no async push, so there is no idle wakeup — offers surface at
50
+ tool boundaries.
51
+ - **copilot-cli** — merges `mcpServers.load-lock` (`type: local`) into
52
+ `~/.copilot/mcp-config.json` (verify with `copilot mcp list`) and adds a
53
+ marked section to `~/.copilot/copilot-instructions.md`. MCP-fallback
54
+ tier: hooks are reported in ecosystem content but not verified in
55
+ official docs, so none are installed — no idle wakeup is claimed; offers
56
+ surface at tool boundaries.
57
+ - **cline** — merges `mcpServers.load-lock` into
58
+ `~/.cline/data/settings/cline_mcp_settings.json` (or the path in
59
+ `CLINE_MCP_SETTINGS_PATH`) and writes a dedicated
60
+ `~/.cline/rules/load-lock.md`. MCP-fallback tier: no hooks, no async
61
+ push — no idle wakeup.
62
+ - **roo-code** — merges `mcpServers.load-lock` into `<project>/.roo/mcp.json`
63
+ (the CLI's cwd unless `--project` is passed) and into every **existing**
64
+ global `mcp_settings.json` (VS Code globalStorage; existing files only —
65
+ never created), plus a dedicated `~/.roo/rules/load-lock.md`. MCP-fallback
66
+ tier: no hooks, no async push — no idle wakeup.
67
+ - **goose** — merges an `extensions.load-lock` stdio block (marker-tagged
68
+ block-mapping YAML; refuses flow-style `extensions:`) into
69
+ `~/.config/goose/config.yaml` (XDG_CONFIG_HOME respected) and adds a
70
+ marked section to `~/.config/goose/.goosehints`. MCP-fallback tier: no
71
+ hooks, no async push — no idle wakeup.
72
+ - **cursor** — project-level (pass `--project`, defaults to cwd): merges
73
+ `mcpServers.load-lock` into `<project>/.cursor/mcp.json`, merges a flat
74
+ hooks-v1 stop entry into `<project>/.cursor/hooks.json` (`version` added
75
+ only if absent; user entries preserved), writes a dedicated Always-apply
76
+ rule `<project>/.cursor/rules/load-lock.mdc`. Hooks tier: the stop hook
77
+ returns `{"followup_message":...}` so Cursor continues the session when an
78
+ offer arrives. Contract-tested against the documented hooks JSON
79
+ (https://cursor.com/docs/hooks); not live-verified.
80
+ - **generic** — prints MCP stdio config snippets (harnesses without a
81
+ dedicated installer) plus a suggested
82
+ instructions-file section.
32
83
 
33
84
  `load-lock uninstall --harness <harness>` reverses any of these.
34
85
 
35
86
  ## The daemon
36
87
 
37
- - State dir: `~/.local/state/load-lock` (override `LOAD_LOCK_STATE_DIR`).
38
- - Socket: `<state dir>/daemon.sock` (override `LOAD_LOCK_SOCKET`).
88
+ - State dir: `~/.local/state/load-lock` (Windows: `%LOCALAPPDATA%\load-lock`;
89
+ override `LOAD_LOCK_STATE_DIR`).
90
+ - Socket: `<state dir>/daemon.sock` (Windows: named pipe `\\.\pipe\load-lock`;
91
+ override `LOAD_LOCK_SOCKET`).
39
92
  - **Auto-spawns** on first client connection; no service unit needed.
40
93
  - Files: `config.json` (settings + history), `journal.json` (held permits),
41
94
  `daemon.json` (pid), `spawn.lock` (bootstrap race guard).
@@ -61,8 +114,9 @@ load-lock get-config
61
114
  ```
62
115
 
63
116
  Defaults: one pool `heavy` capacity 1 · offer window 30s · blocking wait 5s ·
117
+ turn-wait cap (`maxTurnWaitMs`, bounds `wait_for_turn`/`load-lock wait`) 10min ·
64
118
  start deadline 120s · untracked lease 15min. Valid ranges:
65
- offer window 1s–10min, wait 0–10min, start deadline 1s–1h, lease 10s–24h.
119
+ offer window 1s–10min, wait 0–10min, turn-wait cap 1s–1h, start deadline 1s–1h, lease 10s–24h.
66
120
 
67
121
  Semantics of changes:
68
122
 
@@ -76,6 +130,64 @@ limits (containers respected — we never assume 2 cores means 2 jobs),
76
130
  available memory, and current load/memory pressure with an advisory
77
131
  suggestion. Agents are expected to consult it before proposing capacity.
78
132
 
133
+ ## Dashboard: watching the budget live
134
+
135
+ Two read-only viewers surface the machine-wide state in real time. Both use
136
+ the library's own client + protocol (`./shared/client`, `./shared/protocol`)
137
+ over the daemon's watch event stream — neither scrapes CLI output — and
138
+ both resume cleanly across daemon restarts (event-cursor `events since` +
139
+ journal recovery).
140
+
141
+ **The viewers never acquire a permit.** Watching the lock is light work by
142
+ construction: they only ever call `status` / `events` / `inspect` / `watch`.
143
+ The one admin action they expose — force-release — sits behind an explicit
144
+ select + confirm step and never runs automatically.
145
+
146
+ ### `load-lock top` (portable, any terminal)
147
+
148
+ ```sh
149
+ load-lock top # full-screen live dashboard (q quit, r resync)
150
+ load-lock top --once # render one frame and exit (pipes, scripts, CI logs)
151
+ ```
152
+
153
+ Shows pools with capacity vs in-use, holder cards (label, reason, phase,
154
+ pid/pgid, age, start-deadline or lease countdown), offer cards with the
155
+ claim-window countdown, FIFO queue positions, paused requests, the recent
156
+ event feed, and the `inspect_machine` panel. It reconnects with backoff if
157
+ the daemon dies, and a daemon restart is announced and re-synced (durable
158
+ event ids are per daemon lifetime; the cursor resets on restart).
159
+
160
+ Zero extra dependencies: it renders plain text lines over the standard
161
+ alternate screen, so it works anywhere the CLI works.
162
+
163
+ ### OpenCode TUI panel
164
+
165
+ The OpenCode installer also ships the package's `./tui` plugin export (a
166
+ companion to the main plugin, which is unchanged). Inside the opencode TUI:
167
+
168
+ - `/load-lock` (or the command palette) opens the **load-lock.dashboard**
169
+ session panel full-screen: same data as `load-lock top`, themed,
170
+ live-updating.
171
+ - The home footer gains a small `lock 1/1·Q2` indicator.
172
+ - `r` resyncs; `x` opens force-release, which requires an explicit
173
+ select + confirm — the panel itself is read-only.
174
+
175
+ Verified live against opencode 2.0.29: the panel renders pools, holder
176
+ cards, queue positions and the event feed against the real daemon, and the
177
+ `x` flow refuses to release anything until the confirm dialog accepts.
178
+ Plugins configured in `opencode.json` that expose `./tui` are loaded
179
+ automatically by the CLI, so the published package needs no extra config.
180
+ For local development against a checkout, opencode also auto-discovers
181
+ plugins laid out like a package under `<project>/.opencode/plugins/<name>/`
182
+ (`index.ts` re-exporting the main plugin, `tui.ts` re-exporting
183
+ `./dist/adapters/opencode/tui.js`).
184
+
185
+ ### Notes for other harnesses
186
+
187
+ `load-lock top` is the dashboard for every harness (it only needs the
188
+ daemon, which every integration spawns). Harnesses without an in-app UI
189
+ surface get exactly that — no parity claims; see `docs/COMPATIBILITY.md`.
190
+
79
191
  ## Diagnostics & recovery
80
192
 
81
193
  ```sh
@@ -117,10 +229,203 @@ Recovery policy summary:
117
229
  | Config change lost | Configuration is CAS-versioned: fetch `get_config`, retry with the fresh `version` on `version_conflict`. |
118
230
  | After OS reboot | Nothing to do: state dir persists config; daemon respawns on first use; journal reconciliation releases permits whose processes died. |
119
231
 
232
+ ## Containers / WSL2
233
+
234
+ ### Docker — verified
235
+
236
+ Verified 2026-10-16 with Docker Engine 29.1.3 on Linux (amd64) and
237
+ `node:24-bookworm-slim`, running load-lock 0.1.2 installed from the packed
238
+ tarball (`npm pack`, then `npm install -g --omit=dev <tgz>` inside the
239
+ container). Two runs, both in containers started with `--cpus=1.5
240
+ --memory=2g`:
241
+
242
+ - a sanity-check container (`load-lock doctor`, `load-lock inspect`,
243
+ auto-spawn proof, `attach-pid` round trip), and
244
+ - the full `demo/demo.sh` (which needs bash + git + node; the slim image
245
+ needs `apt-get install -y git` first).
246
+
247
+ **Cgroup v2 quota detection.** `load-lock inspect` inside the constrained
248
+ container reported the *container* limits, not the host's:
249
+
250
+ ```json
251
+ "cpu": { "logicalCores": 2, "cgroupVersion": "v2", "cgroupCpuQuota": 150000,
252
+ "cgroupCpuPeriod": 100000, "effectiveCores": 1 },
253
+ "memory": { "totalBytes": 8195100672, "cgroupMemoryLimitBytes": 2147483648,
254
+ "effectiveLimitBytes": 2147483648 }
255
+ ```
256
+
257
+ `/sys/fs/cgroup/cpu.max` reads `150000 100000` inside the container; the 2
258
+ logical cores are still visible (`os.cpus()`/`nproc` are not namespaced),
259
+ but `effectiveCores` is 1 (floor of the 1.5-CPU quota) and
260
+ `effectiveLimitBytes` is exactly the 2 GiB `--memory=2g` limit while
261
+ `totalBytes` shows the ~8 GiB host view. Pressure and the inspect
262
+ suggestion therefore scale to the container, not the host — exactly the
263
+ "don't assume the host's cores" behavior the inspect output promises.
264
+
265
+ **Auto-spawn + default state dir.** With no `LOAD_LOCK_*` environment
266
+ variables set, the first CLI call auto-spawned the daemon on the
267
+ container-default `~/.local/state/load-lock` (state dir contained
268
+ `daemon.json`, `daemon.sock`, `journal.json`; `service status` reported
269
+ `pid 35, protocol v1, config version 1`). No special env or volumes
270
+ needed.
271
+
272
+ **Process tracking.** `/proc`-based tracking works unchanged in the
273
+ container: `attach-pid` recorded the target with its
274
+ `/proc/<pid>/stat` start time (`pidStartTime`), `recover list` reported
275
+ `tracked process (pid 3367) is running`, and after the process was killed
276
+ the daemon's 5s sweep auto-released the permit (`pool "heavy": 0/1 in
277
+ use`). `run_with_permit` and journal reconciliation use the same mechanism.
278
+
279
+ **demo.sh.** The scripted three-agent demo ran to completion (exit 0)
280
+ inside the same plain node:24 container. Its inventory line reflected
281
+ container limits — `cpu: 2 logical / 1 effective (v2)`, `memory: 7.6 GiB
282
+ total / 2.0 GiB effective limit` — and the full flow played out: A
283
+ granted, B and C queued, strict FIFO hand-off, C's offer expired →
284
+ `paused` (not recycled), explicit `rejoin` → claim → run → release; 21
285
+ durable events.
286
+
287
+ Limitations observed (all inherent to containers, not load-lock bugs):
288
+
289
+ - `/proc/loadavg` and CPU inventory are not namespaced: `loadPerEffectiveCore`
290
+ reflects host load, so a quiet container on a busy host can still show
291
+ "high" load pressure, and `nproc` shows the host CPU set. The cgroup quota
292
+ (`effectiveCores`) is the honest number to reason with.
293
+ - State dir is per-container: with `docker run --rm` the daemon, config
294
+ history and journal vanish with the container; the next use just
295
+ respawns with defaults (fine for ephemeral CI containers; volume-mount
296
+ `~/.local/state/load-lock` if you want persistence).
297
+
298
+ Reproduce from a checkout (needs `dist/` built; tarball from `npm pack`):
299
+
300
+ ```sh
301
+ npm ci && npm run build && npm pack --pack-destination /tmp
302
+ docker run --rm --cpus=1.5 --memory=2g \
303
+ -v "$PWD":/src:ro -v /tmp:/pkg:ro \
304
+ node:24 bash -c '
305
+ cp -a /src /work && cd /work
306
+ apt-get update -qq && apt-get install -y -qq git
307
+ npm install -g --omit=dev /pkg/aron98-load-lock-<version>.tgz
308
+ load-lock doctor && load-lock inspect
309
+ bash demo/demo.sh'
310
+ ```
311
+
312
+ ### WSL2 — procedure (NOT verified here)
313
+
314
+ **No WSL2 environment was available on the verification machine** (the
315
+ Docker results above were produced on a plain Linux VM). Nothing in this
316
+ subsection has been executed by us; it is a ready-to-run procedure with
317
+ *expected* outcomes, to be verified and recorded by someone with a WSL2
318
+ setup.
319
+
320
+ 1. Use a WSL2 distro with Node 20+ (e.g. Ubuntu 24.04). Check the cgroup
321
+ layout inside the distro:
322
+
323
+ ```sh
324
+ stat -fc %T /sys/fs/cgroup # expect cgroup2fs (cgroup v2)
325
+ cat /sys/fs/cgroup/cpu.max /sys/fs/cgroup/memory.max
326
+ ```
327
+
328
+ 2. Run load-lock on the distro itself (not in Docker):
329
+
330
+ ```sh
331
+ load-lock doctor && load-lock inspect
332
+ ```
333
+
334
+ Expected: WSL2 presents a full VM, so `inspect` reports the *WSL VM's*
335
+ resources, not the Windows host's. `.wslconfig` caps (`memory=`,
336
+ `processors=`) are enforced by the VM itself (vCPU count / VM RAM), not
337
+ via cgroup quotas — so `cgroupVersion` should read `v2` on a
338
+ systemd-enabled distro, `cgroupCpuQuota` typically `null` (`max`), and
339
+ `effectiveCores` = the `.wslconfig` `processors=` value with
340
+ `memory.totalBytes`/`effectiveLimitBytes` ≈ the `memory=` cap. If the
341
+ distro runs without systemd the cgroup controllers may not be mounted;
342
+ expect `cgroupVersion` to degrade to `none` and the raw VM numbers to be
343
+ reported — either way, no host overstating.
344
+
345
+ 3. With Docker inside WSL2 (docker-ce in the distro, or Docker Desktop's
346
+ WSL2 backend), re-run the constrained-container check from the verified
347
+ Docker procedure above. Expected: containers get their own nested
348
+ cgroup, so `inspect` should report the container quota exactly as in the
349
+ verified results above, regardless of the extra nesting level.
350
+
351
+ 4. Run `demo/demo.sh` in the distro (needs bash, git, node) and in a plain
352
+ `node:24` container under WSL2's Docker.
353
+
354
+ Caveats to watch for (unverified expectations):
355
+
356
+ - Nested cgroups (Docker Desktop's WSL2 backend adds a level) must not mask
357
+ the container's own `cpu.max`/`memory.max` — that's what `inspect` reads.
358
+ - `/proc/loadavg` reflects the WSL VM, not Windows.
359
+ - Keep the state dir on the Linux filesystem (`~/.local/state/load-lock`),
360
+ not on `/mnt/c` (drvfs/9p is slow and lock semantics differ).
361
+
120
362
  ## macOS / Windows notes
121
363
 
122
364
  - macOS: no cgroup limits; process-liveness checks fall back from
123
365
  `/proc/<pid>/stat` start times to `kill(pid, 0)` (weaker PID-reuse
124
366
  defense — document and prefer `run_with_permit`). Everything else works.
125
- - Windows: Unix-domain sockets are not used; a named-pipe transport adapter
126
- would be required (planned, not implemented).
367
+
368
+ ### Windows — procedure (NOT verified here)
369
+
370
+ **No Windows environment was available on the verification machine** (all
371
+ development and testing happened on Linux). The transport adapter is
372
+ implemented in `src/shared/transport.ts` and its logic is covered by
373
+ `src/test/platform.test.ts` (endpoint resolution, pipe naming, probe,
374
+ stale-cleanup, liveness, and job-tree kill paths, exercised via platform
375
+ injection on Linux) — but nothing in this subsection has been executed on
376
+ Windows. It is a ready-to-run procedure with *expected* outcomes, to be
377
+ verified and recorded by someone with a Windows setup.
378
+
379
+ What the adapter does:
380
+
381
+ - Transport: clients and the daemon talk over the named pipe
382
+ `\\.\pipe\load-lock` (Node's `net` module accepts pipe paths in
383
+ `connect`/`createServer` natively). No Unix-domain socket is involved.
384
+ - State dir: `%LOCALAPPDATA%\load-lock` (falls back to
385
+ `%USERPROFILE%\AppData\Local\load-lock`); `LOAD_LOCK_STATE_DIR` and
386
+ `LOAD_LOCK_SOCKET` overrides still win, as on POSIX.
387
+ - Daemon claim: a second daemon probes the pipe before listening and
388
+ exits with "denied" if a live daemon answers (same semantics as the
389
+ POSIX socket claim). Because a named pipe is a kernel object, there is
390
+ no stale-socket file to clean up.
391
+
392
+ 1. Install Node 20+ on Windows and install the package:
393
+
394
+ ```powershell
395
+ npm install -g @aron98/load-lock
396
+ load-lock install --harness <harness>
397
+ load-lock doctor
398
+ ```
399
+
400
+ Expected: `doctor` reports the `%LOCALAPPDATA%\load-lock` state dir and
401
+ reaches the daemon over the pipe (it probes by connecting, since a pipe
402
+ is not a filesystem entry).
403
+
404
+ 2. Run two `load-lock acquire` in separate shells, then `load-lock top --once`.
405
+
406
+ Expected: the same queue/permit behavior as on Linux — the pipe carries
407
+ the identical NDJSON protocol.
408
+
409
+ 3. Start a permit-tracked job (`run_with_permit` / `load-lock run`), kill
410
+ the job, and confirm the permit auto-releases via the sweep.
411
+
412
+ 4. Run `load-lock service stop`, then any tool call.
413
+
414
+ Expected: the daemon exits and the next client auto-spawns a fresh one.
415
+
416
+ Caveats to watch for (unverified expectations):
417
+
418
+ - **No PID-reuse defense.** Windows has no `/proc`-style start time, so
419
+ `isProcessAlive` can only use `kill(pid, 0)` semantics. A recycled PID
420
+ could be mistaken for the original job — the same limitation as macOS,
421
+ documented here rather than solved.
422
+ - **No process-group scan.** The `/proc`-based process-group scan does not
423
+ exist on Windows. Tracked permits release when the main PID dies even if
424
+ child processes survive; job stop/kill uses `taskkill /PID <pid> /T`
425
+ (`/F` for SIGKILL) to take down the tree.
426
+ - **Pipe name is machine-wide.** Unlike POSIX, where each user gets their
427
+ own state dir and socket, a named pipe named `\\.\pipe\load-lock` is
428
+ machine-global — two Windows users on one host share one daemon. Point
429
+ `LOAD_LOCK_SOCKET` at a different pipe name if that is a problem.
430
+ - `inspect_machine` cgroup detection reads `/sys/fs/cgroup` and will
431
+ degrade gracefully (`cgroupVersion: "none"`), reporting raw host numbers.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aron98/load-lock",
3
- "version": "0.1.1",
3
+ "version": "1.0.0",
4
4
  "description": "Machine-wide counting semaphore coordinating resource-intensive work across parallel coding agents (OpenCode, Codex CLI, Claude Code, ...)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -72,6 +72,10 @@
72
72
  "types": "./dist/core/daemon.d.ts",
73
73
  "default": "./dist/core/daemon.js"
74
74
  },
75
+ "./tui": {
76
+ "types": "./dist/adapters/opencode/tui.d.ts",
77
+ "default": "./dist/adapters/opencode/tui.js"
78
+ },
75
79
  "./dist/*": "./dist/*"
76
80
  },
77
81
  "files": [
@@ -97,8 +101,28 @@
97
101
  "@opencode/plugin": "^2.0.29",
98
102
  "zod": "^3.24.0"
99
103
  },
104
+ "peerDependencies": {
105
+ "@opentui/core": ">=0.5.8",
106
+ "@opentui/solid": ">=0.5.8",
107
+ "solid-js": ">=1.9.0"
108
+ },
109
+ "peerDependenciesMeta": {
110
+ "@opentui/core": {
111
+ "optional": true
112
+ },
113
+ "@opentui/solid": {
114
+ "optional": true
115
+ },
116
+ "solid-js": {
117
+ "optional": true
118
+ }
119
+ },
100
120
  "devDependencies": {
121
+ "@opencode/theme": "2.0.29",
122
+ "@opentui/core": "0.5.17",
123
+ "@opentui/solid": "0.5.17",
101
124
  "@types/node": "^24.0.0",
125
+ "solid-js": "1.9.12",
102
126
  "typescript": "^5.9.0"
103
127
  }
104
128
  }
@@ -23,6 +23,10 @@ Judge by *expected* CPU/memory/IO impact, not by command name. When unsure,
23
23
  `granted` (you hold a permit; start heavy work now) or `queued`
24
24
  (keep doing light work; an offer will arrive).
25
25
  2. While queued: **continue useful light work**. Do not idle-poll.
26
+ Out of light work? `wait_for_turn(request_id?, timeout_ms?)` — a
27
+ bounded blocking sleep until your turn that auto-claims the offer and
28
+ wakes you with the permit_token (holds no capacity; cancellation
29
+ leaves nothing behind).
26
30
  3. When an offer arrives (harness wakeup or `pending`): `claim(offer_token)`
27
31
  inside the claim window (default 30s). Expired offers are rejected; your
28
32
  request pauses until you `rejoin`.