@loompad/cli 1.2.5 → 1.2.6
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.
- package/CHANGELOG.md +18 -0
- package/dist/adapters/antigravity-cli.js +1 -1
- package/dist/adapters/antigravity-cli.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +25 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/cli/index.js +241 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/core/orchestra.js +17 -1
- package/dist/core/orchestra.js.map +1 -1
- package/dist/core/review-triage.js +3 -13
- package/dist/core/review-triage.js.map +1 -1
- package/dist/core/secret-scan.d.ts +14 -0
- package/dist/core/secret-scan.js +42 -0
- package/dist/core/secret-scan.js.map +1 -0
- package/dist/core/shares.d.ts +33 -0
- package/dist/core/shares.js +54 -0
- package/dist/core/shares.js.map +1 -0
- package/dist/core/turn-gate.d.ts +53 -0
- package/dist/core/turn-gate.js +126 -0
- package/dist/core/turn-gate.js.map +1 -0
- package/dist/daemon/client.d.ts +19 -0
- package/dist/daemon/client.js +81 -5
- package/dist/daemon/client.js.map +1 -1
- package/dist/daemon/routes/chats.js +18 -0
- package/dist/daemon/routes/chats.js.map +1 -1
- package/dist/daemon/routes/public.d.ts +1 -1
- package/dist/daemon/routes/public.js +21 -0
- package/dist/daemon/routes/public.js.map +1 -1
- package/dist/daemon/runner.js +3 -1
- package/dist/daemon/runner.js.map +1 -1
- package/docs/release-notes/v1.2.6.md +18 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.6] — 2026-10-25
|
|
11
|
+
|
|
12
|
+
A cap that keeps a busy machine from falling over, chained goals across projects, read-only thread links, and orchestras scripts can trust.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **A read-only link to a thread** (#199). `loom share [chat]` gives a link a teammate opens in a browser to read that chat — prompts, replies, what ran, what changed — with no pairing. It expires (`--hours`, default 24, up to 30 days), `loom share --list` shows yours and `--revoke <id>` ends one; only a hash of each link's token is kept. It works for anyone who can reach the daemon (`loom up --tailnet` for another computer).
|
|
17
|
+
- **Goals across several projects, one after another, merged as they finish** (#340). `loom chain plan.txt` takes one `<project> :: <goal>` per line (project by name or folder): each runs as an orchestra, and when it completes its changes are scanned for secrets and merged into that project's branch before the next starts — `--push` pushes each, `--keep-going` carries on past a failure, `--json` summarises. `loom orchestrate "…" --apply` does the same for one goal. The secret scan (keys, tokens, hard-coded credentials, `.env`/private-key files) is the one review triage already used.
|
|
18
|
+
- **A daemon-wide cap on turns at once, and a free-memory floor** (#339). Six agent turns at once — each with a dev server and headless Chrome — exhausted 16 GB and reset a Mac. Every agent turn, from any project (chats, orchestra workers, crews, reviews), now passes one gate: past the cap (one turn per 4 GB by default, so 4 on 16 GB) or with less than 15% of memory free, it waits for a slot and says so in its thread. `loom limits --turns 2 --min-free 25` changes it without a restart; a turn that's waited 10 minutes starts anyway, so nothing deadlocks.
|
|
19
|
+
- **`loom orchestra --json` and `--wait`** (#341). `--json` gives each run's `status` — one of starting, planning, running, reviewing, waiting_human, completed, failed, aborted, moved — whether that's `terminal`, and its tasks by outcome. `loom orchestra <run> --wait` waits for the end (reviewing isn't the end — a script took it for one and skipped the apply) and exits 0 completed, 1 failed/aborted, 2 waiting on you, 124 timed out.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **A `loom` command run during a daemon restart waits for it.** When one command swaps a stale daemon for the newer build (only ever while no turns run), another command that lands in that second — a script's next step — waited for nothing and reached the daemon going away, or started a second one. The restarting command now holds a short lock, and others wait for the new daemon.
|
|
24
|
+
- **A task that recovers frees the ones waiting on it.** When a task failed (an agent timed out) and the orchestrator's follow-up then got it done, the task depending on it stayed failed with "a dependency failed" — the run showed the same task as done and as failed. Tasks failed only on a dependency's account now go back to pending, and run, once it's done.
|
|
25
|
+
- **antigravity gets 30 minutes a turn by default**, up from 15: an e2e-sized turn ran past 15 and timed out (`timeoutMs` on the agent still decides).
|
|
26
|
+
- **A run says what happened to every task** (#342). "completed 9/13" left the other four a mystery; the list and the run now show "9 done · 2 failed · 2 cancelled".
|
|
27
|
+
|
|
10
28
|
## [1.2.5] — 2026-10-25
|
|
11
29
|
|
|
12
30
|
Fixes from a day of real use farming builds out to codex, antigravity and opencode: Loom no longer kills turns when it updates, every agent starts (and says why when it can't), and scripts get honest exits.
|
|
@@ -45,7 +45,7 @@ import { permissionFor } from "../core/permissions.js";
|
|
|
45
45
|
export const HEADLESS_NOTE = "(Loom runs you headless: this turn ends the moment you reply, and nothing will notify or wake you later. " +
|
|
46
46
|
"Run tests, builds and servers in the foreground and wait for them to finish before you answer — don't start them in the background to check on later.)";
|
|
47
47
|
/** agy's own --print-timeout is 5 minutes; a review of a real diff took longer (#246). */
|
|
48
|
-
const DEFAULT_TIMEOUT_MS =
|
|
48
|
+
const DEFAULT_TIMEOUT_MS = 30 * 60_000; // an e2e-sized turn ran past 15 min in the field
|
|
49
49
|
/** The installer drops the binary here; it isn't always on the daemon's PATH. */
|
|
50
50
|
const INSTALLED = [
|
|
51
51
|
...(process.platform === "win32" ? [path.join(os.homedir(), ".local", "bin", "agy.exe")] : []),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"antigravity-cli.js","sourceRoot":"","sources":["../../src/adapters/antigravity-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAGH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GACxB,2GAA2G;IAC3G,wJAAwJ,CAAC;AAE3J,0FAA0F;AAC1F,MAAM,kBAAkB,GAAG,EAAE,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"antigravity-cli.js","sourceRoot":"","sources":["../../src/adapters/antigravity-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAGH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GACxB,2GAA2G;IAC3G,wJAAwJ,CAAC;AAE3J,0FAA0F;AAC1F,MAAM,kBAAkB,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC,iDAAiD;AAezF,iFAAiF;AACjF,MAAM,SAAS,GAAG;IAChB,GAAG,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9F,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC;CAChD,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAC,QAAiB;IACtC,IAAI,QAAQ;QAAE,OAAO,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/D,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QAC3H,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,OAAO,MAAM,CAAC;IAC3C,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,IAAI,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,gDAAgD;AAChE,CAAC;AAED,iFAAiF;AACjF,SAAS,qBAAqB;IAC5B,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,iBAAiB,EAAE,OAAO,EAAE,yBAAyB,CAAC,CAAC;AACvH,CAAC;AAED,2EAA2E;AAC3E,SAAS,eAAe,CAAC,GAAW;IAClC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,qBAAqB,EAAE,EAAE,MAAM,CAAC,CAAC;QAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA2B,CAAC;QACtD,OAAO,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,OAAO,qBAAsB,SAAQ,WAAW;IAC5C,KAAK,GAAwB,IAAI,CAAC;IAClC,OAAO,CAAqB;IAEpC,YAAY,EAAU,EAAE,UAAkB,EAAE,UAAmC,EAAE;QAC/E,KAAK,CAAC,EAAE,EAAE,iBAAiB,EAAE,UAAU,CAAC,CAAC;QACzC,IAAI,CAAC,OAAO,GAAG,OAA6B,CAAC;IAC/C,CAAC;IAED,mFAAmF;IACnF,IAAY,cAAc;QACxB,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,SAA+B,CAAC;IACjE,CAAC;IAED,IAAY,cAAc,CAAC,KAAyB;QAClD,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,SAAS;QACb,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,CAAC,GAAG;YAAE,OAAO,KAAK,CAAC;QACvB,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,cAAc,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;IACnG,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;IACzB,CAAC;IAED,mGAAmG;IAC3F,SAAS;QACf,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC;IACtD,CAAC;IAED,kDAAkD;IAC1C,UAAU;QAChB,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAAgB;QACzB,IAAI,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;QAC1E,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,6FAA6F,CAAC,CAAC;QACzH,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE3B,wEAAwE;QACxE,8EAA8E;QAC9E,iDAAiD;QACjD,MAAM,IAAI,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,IAAI,OAAO,aAAa,EAAE,CAAC;QAEhH,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC;QACnC,yEAAyE;QACzE,2EAA2E;QAC3E,0EAA0E;QAC1E,MAAM,IAAI,GAAG,aAAa,CAAC,iBAAiB,EAAE,IAAI,CAAC,OAAkC,CAAC,CAAC;QACvF,MAAM,QAAQ,GACZ,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;QAChH,MAAM,IAAI,GAAG,CAAC,SAAS,EAAE,IAAI,EAAE,GAAG,QAAQ,EAAE,iBAAiB,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QAClF,IAAI,MAAM,EAAE,CAAC;YACX,yEAAyE;YACzE,yDAAyD;YACzD,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;QACtC,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAC1C,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK;YAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACjE,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM;YAAE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACpE,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS;YAAE,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAEjE,2EAA2E;QAC3E,6EAA6E;QAC7E,6CAA6C;QAC7C,MAAM,GAAG,GAAG,QAAQ,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACjE,GAAG,CAAC,IAAI,GAAG,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;QAEzC,IAAI,CAAC;YACH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;oBAC7B,GAAG,EAAE,IAAI,CAAC,UAAU;oBACpB,wEAAwE;oBACxE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;oBACjC,GAAG;iBACJ,CAAC,CAAC;gBACH,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;gBACnB,IAAI,GAAG,GAAG,EAAE,CAAC;gBACb,IAAI,UAAU,GAAG,EAAE,CAAC;gBAEpB,KAAK,CAAC,MAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE;oBACrC,GAAG,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;gBACtB,CAAC,CAAC,CAAC;gBACH,KAAK,CAAC,MAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE;oBACrC,UAAU,GAAG,CAAC,UAAU,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;gBACxD,CAAC,CAAC,CAAC;gBAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;gBACxC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;oBACjC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;oBAClB,IAAI,MAAM,EAAE,CAAC;wBACX,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;wBACzE,OAAO,EAAE,CAAC;wBACV,OAAO;oBACT,CAAC;oBACD,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;oBAC3B,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;wBAC3B,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,cAAc,IAAI,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;wBAC7F,MAAM,CAAC,IAAI,KAAK,CAAC,cAAc,IAAI,KAAK,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;wBACrE,OAAO;oBACT,CAAC;oBACD,iGAAiG;oBACjG,gGAAgG;oBAChG,IAAI,CAAC,OAAO,EAAE,CAAC;wBACb,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;wBAClC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;wBAC/B,MAAM,QAAQ,GAAG,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,KAAK,EAAE,KAAK,GAAG,GAAG,CAAC,CAAC;wBAC9D,MAAM,GAAG,GAAG,QAAQ;4BAClB,CAAC,CAAC,uBAAuB,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,oEAAoE;4BACpH,CAAC,CAAC,+BAA+B,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;wBACxH,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;wBAC7K,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;wBACvB,OAAO;oBACT,CAAC;oBAED,IAAI,OAAO,EAAE,CAAC;wBACZ,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;wBAC3D,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;oBACvC,CAAC;oBAED,uEAAuE;oBACvE,qEAAqE;oBACrE,iDAAiD;oBACjD,IAAI,CAAC,MAAM,EAAE,CAAC;wBACZ,MAAM,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;wBAC5C,IAAI,EAAE;4BAAE,IAAI,CAAC,cAAc,GAAG,EAAE,CAAC;oBACnC,CAAC;oBAED,sEAAsE;oBACtE,+DAA+D;oBAC/D,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;wBAC3B,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;oBACjF,CAAC;oBAED,IAAI,CAAC,IAAI,CAAC;wBACR,IAAI,EAAE,cAAc;wBACpB,OAAO,EAAE;4BACP,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO;4BAChC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;yBAC7D;qBACF,CAAC,CAAC;oBACH,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;YACnB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACK,aAAa,CAAC,OAAe,EAAE,KAAa;QAClD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,MAAM,EAAE,GAAG,mCAAmC,CAAC;QAC/C,IAAI,CAAyB,CAAC;QAC9B,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACvC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,2DAA2D;YAC9F,IAAI,CAAC;gBACH,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC;YAC5B,CAAC;YAAC,MAAM,CAAC;gBACP,oDAAoD;YACtD,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAE,SAAS;YAC1B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACZ,wGAAwG;YACxG,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC;YACvD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,CAAC,OAAO,GAAG,KAAK,GAAG,IAAI;gBAAE,SAAS;YAC7D,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,KAAK,CAAC,SAAS;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAClC,MAAM,CAAC,GAAG,UAAU,CAAC,GAAG,EAAE;gBACxB,IAAI,CAAC;oBACH,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACxB,CAAC;gBAAC,MAAM,CAAC;oBACP,eAAe;gBACjB,CAAC;gBACD,OAAO,EAAE,CAAC;YACZ,CAAC,EAAE,IAAI,CAAC,CAAC;YACT,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;gBACrB,YAAY,CAAC,CAAC,CAAC,CAAC;gBAChB,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Adapter factory — the one place agent kinds are wired up.
|
|
3
3
|
* Community adapters register here (or via the SDK entry point).
|
|
4
4
|
*/
|
|
5
|
-
import type
|
|
5
|
+
import { type AgentConfig, type AnyAgent } from "../types.js";
|
|
6
6
|
export type AgentFactory = (cfg: AgentConfig, projectDir: string) => AnyAgent;
|
|
7
7
|
export declare function registerAgentKind(kind: string, factory: AgentFactory, tier?: "adapter" | "bridge"): void;
|
|
8
8
|
/** The tier a kind builds as, or null if nothing registers that kind. */
|
package/dist/adapters/index.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Adapter factory — the one place agent kinds are wired up.
|
|
3
3
|
* Community adapters register here (or via the SDK entry point).
|
|
4
4
|
*/
|
|
5
|
+
import { turnGate } from "../core/turn-gate.js";
|
|
6
|
+
import { isAdapter } from "../types.js";
|
|
5
7
|
import { AntigravityCliAdapter } from "./antigravity-cli.js";
|
|
6
8
|
import { AntigravityBridge } from "./bridges/antigravity.js";
|
|
7
9
|
import { KiroBridge } from "./bridges/kiro.js";
|
|
@@ -71,7 +73,29 @@ export function createAgent(cfg, projectDir) {
|
|
|
71
73
|
if (!factory) {
|
|
72
74
|
throw new Error(`unknown agent kind "${cfg.kind}" (known: ${[...factories.keys()].join(", ")})`);
|
|
73
75
|
}
|
|
74
|
-
return factory(cfg, projectDir);
|
|
76
|
+
return gated(factory(cfg, projectDir), cfg, projectDir);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Every turn an adapter takes passes the daemon-wide gate (#339): past the cap on turns at
|
|
80
|
+
* once, or short of free memory, it waits for a slot — and says so in the thread — instead
|
|
81
|
+
* of piling onto a machine that's already full. LOOM_TURN_GATE=off skips it (tests).
|
|
82
|
+
*/
|
|
83
|
+
function gated(agent, cfg, projectDir) {
|
|
84
|
+
if (!isAdapter(agent) || process.env.LOOM_TURN_GATE === "off")
|
|
85
|
+
return agent;
|
|
86
|
+
const send = agent.send.bind(agent);
|
|
87
|
+
const say = (why) => agent
|
|
88
|
+
.emit({ kind: "status", payload: { state: "queued_for_slot", text: `waiting for a free slot — ${why}` } });
|
|
89
|
+
agent.send = async (input) => {
|
|
90
|
+
const release = await turnGate.acquire(`${projectDir.split(/[\\/]/).pop()}: ${cfg.id}`, say);
|
|
91
|
+
try {
|
|
92
|
+
return await send(input);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
release();
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
return agent;
|
|
75
99
|
}
|
|
76
100
|
export function knownAgentKinds() {
|
|
77
101
|
return [...factories.keys()];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/index.js
CHANGED
|
@@ -1465,11 +1465,34 @@ const TASK_COLOR = {
|
|
|
1465
1465
|
needs_input: pc.yellow,
|
|
1466
1466
|
cancelled: pc.dim,
|
|
1467
1467
|
};
|
|
1468
|
+
/**
|
|
1469
|
+
* A run's tasks by outcome (#342: "completed 9/13" said nothing about the other four — dropped,
|
|
1470
|
+
* merged elsewhere, failed?). Only the non-zero ones, done first.
|
|
1471
|
+
*/
|
|
1472
|
+
function taskCounts(run) {
|
|
1473
|
+
const order = ["done", "running", "pending", "needs_input", "conflict", "failed", "cancelled"];
|
|
1474
|
+
const n = {};
|
|
1475
|
+
for (const t of run.tasks)
|
|
1476
|
+
n[t.status] = (n[t.status] ?? 0) + 1;
|
|
1477
|
+
return Object.fromEntries(order.filter((k) => n[k]).map((k) => [k, n[k]]).concat(Object.entries(n).filter(([k]) => !order.includes(k))));
|
|
1478
|
+
}
|
|
1479
|
+
const countsLine = (run) => {
|
|
1480
|
+
const c = taskCounts(run);
|
|
1481
|
+
return Object.keys(c).length ? Object.entries(c).map(([k, v]) => `${v} ${k.replace("_", " ")}`).join(" · ") : "no tasks";
|
|
1482
|
+
};
|
|
1483
|
+
/** What `loom orchestra --json` says about a run — status is one of OrchestraStatus (#341). */
|
|
1484
|
+
function runJson(run) {
|
|
1485
|
+
return {
|
|
1486
|
+
id: run.id, goal: run.goal, status: run.status, ...(run.status === "aborted" && run.interrupted ? { resumable: true } : {}),
|
|
1487
|
+
terminal: isTerminalStatus(run.status), tasks: { total: run.tasks.length, ...taskCounts(run) }, branch: run.branch, costUsd: run.costUsd,
|
|
1488
|
+
...(run.landing?.pr ? { pr: run.landing.pr } : {}), ...(run.question ? { question: run.question } : {}), createdAt: run.createdAt, updatedAt: run.updatedAt,
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
const isTerminalStatus = (s) => s === "completed" || s === "failed" || s === "aborted" || s === "moved";
|
|
1468
1492
|
function printRun(run) {
|
|
1469
|
-
const done = run.tasks.filter((t) => t.status === "done").length;
|
|
1470
1493
|
console.log(run.race
|
|
1471
|
-
? `${pc.magenta("🏁")} ${pc.bold(run.id)} ${pc.dim(runStatus(run))} race · ${
|
|
1472
|
-
: `${pc.magenta("🎼")} ${pc.bold(run.id)} ${pc.dim(runStatus(run))} ${
|
|
1494
|
+
? `${pc.magenta("🏁")} ${pc.bold(run.id)} ${pc.dim(runStatus(run))} race · ${countsLine(run)} · ${fmtUsd(run.costUsd)}`
|
|
1495
|
+
: `${pc.magenta("🎼")} ${pc.bold(run.id)} ${pc.dim(runStatus(run))} ${countsLine(run)} · ` +
|
|
1473
1496
|
`round ${run.round}/${run.maxRounds} · ${fmtUsd(run.costUsd)} · ${pc.dim(run.branch)}`);
|
|
1474
1497
|
console.log(` ${pc.dim("goal")} ${run.goal}`);
|
|
1475
1498
|
if (run.race)
|
|
@@ -1504,6 +1527,58 @@ function printRun(run) {
|
|
|
1504
1527
|
if (run.race && run.applied?.task)
|
|
1505
1528
|
console.log(` ${pc.green("kept:")} ${run.applied.task} → ${run.applied.into}`);
|
|
1506
1529
|
}
|
|
1530
|
+
/**
|
|
1531
|
+
* #340: wait for a run to end; if it completed, scan everything it changed for secrets, merge it
|
|
1532
|
+
* into the project's current branch, and (asked to) push. Stops at the first thing that's wrong,
|
|
1533
|
+
* and says what — a run that failed, a key in the diff, a merge or push that didn't go.
|
|
1534
|
+
*/
|
|
1535
|
+
async function waitScanApply(client, projectId, dir, runId, opts) {
|
|
1536
|
+
let run = (await client.orchestraRun(projectId, runId)).run;
|
|
1537
|
+
while (!isTerminalStatus(run.status) && run.status !== "waiting_human") {
|
|
1538
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
1539
|
+
run = (await client.orchestraRun(projectId, runId)).run;
|
|
1540
|
+
}
|
|
1541
|
+
if (run.status !== "completed")
|
|
1542
|
+
return { ok: false, runId, status: run.status, error: run.status === "waiting_human" ? `it's waiting on you: ${run.question ?? "a question"}` : (run.error ?? run.status) };
|
|
1543
|
+
const { execFileSync } = await import("node:child_process");
|
|
1544
|
+
const git = (...args) => execFileSync("git", args, { cwd: dir, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
|
|
1545
|
+
let diff = "";
|
|
1546
|
+
try {
|
|
1547
|
+
diff = git("diff", `${run.baseCommit}..${run.branch}`);
|
|
1548
|
+
}
|
|
1549
|
+
catch (e) {
|
|
1550
|
+
return { ok: false, runId, status: run.status, error: `couldn't read the run's changes: ${e.message.split("\n")[0]}` };
|
|
1551
|
+
}
|
|
1552
|
+
const { findSecrets } = await import("../core/secret-scan.js");
|
|
1553
|
+
const secrets = findSecrets(diff);
|
|
1554
|
+
if (secrets.length)
|
|
1555
|
+
return { ok: false, runId, status: run.status, secrets, error: "secrets in the run's changes — not applied" };
|
|
1556
|
+
let out;
|
|
1557
|
+
try {
|
|
1558
|
+
out = await client.orchestraAction(projectId, runId, "apply");
|
|
1559
|
+
}
|
|
1560
|
+
catch (e) {
|
|
1561
|
+
return { ok: false, runId, status: run.status, error: `merge failed: ${e.message}` };
|
|
1562
|
+
}
|
|
1563
|
+
const res = { ok: true, runId, status: run.status, merged: String(out.merged ?? run.branch), into: String(out.into ?? "") };
|
|
1564
|
+
if (opts.push) {
|
|
1565
|
+
try {
|
|
1566
|
+
git("push", "origin", "HEAD");
|
|
1567
|
+
res.pushed = true;
|
|
1568
|
+
}
|
|
1569
|
+
catch (e) {
|
|
1570
|
+
return { ...res, ok: false, pushed: false, error: `merged, but the push failed: ${e.message.split("\n")[0]}` };
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
return res;
|
|
1574
|
+
}
|
|
1575
|
+
function printApplyResult(r) {
|
|
1576
|
+
if (r.ok)
|
|
1577
|
+
return void console.log(`${pc.green("✓")} ${r.runId} merged into ${pc.bold(r.into || "your branch")}${r.pushed ? " and pushed" : ""} — no secrets in its changes`);
|
|
1578
|
+
console.log(`${pc.red("✗")} ${r.runId} (${r.status}): ${r.error}`);
|
|
1579
|
+
for (const s of r.secrets ?? [])
|
|
1580
|
+
console.log(pc.red(` ${s.file}${s.line ? `:${s.line}` : ""} — ${s.what}`));
|
|
1581
|
+
}
|
|
1507
1582
|
async function watchRun(client, projectId, runId) {
|
|
1508
1583
|
let last = "";
|
|
1509
1584
|
for (;;) {
|
|
@@ -1536,6 +1611,8 @@ program
|
|
|
1536
1611
|
.option("--max-usd <n>", "stop this goal when it has spent this much, and say so")
|
|
1537
1612
|
.option("--race", "race: every worker takes the whole goal in its own worktree; pick one with orchestra:apply <run> --task race-<agent>")
|
|
1538
1613
|
.option("--solo", "keep every task on this machine, even when teammates' runners could take some")
|
|
1614
|
+
.option("--apply", "when the run completes, scan its changes for secrets and merge it into your branch (#340)")
|
|
1615
|
+
.option("--push", "with --apply: push the branch after merging")
|
|
1539
1616
|
.option("--no-watch", "start it and return instead of following it to the end")
|
|
1540
1617
|
.action(async (goal, opts) => {
|
|
1541
1618
|
const client = await ensureDaemon();
|
|
@@ -1561,8 +1638,14 @@ program
|
|
|
1561
1638
|
`${run.workers.join(", ")} (${run.maxParallel} in parallel) on ${pc.dim(run.branch)}`);
|
|
1562
1639
|
if (opts.watch)
|
|
1563
1640
|
await watchRun(client, project.id, run.id);
|
|
1564
|
-
else
|
|
1641
|
+
else if (!opts.apply)
|
|
1565
1642
|
console.log(pc.dim(` follow it: loom orchestra ${run.id} --watch`));
|
|
1643
|
+
if (opts.apply) {
|
|
1644
|
+
const res = await waitScanApply(client, project.id, project.dir, run.id, { push: Boolean(opts.push) });
|
|
1645
|
+
printApplyResult(res);
|
|
1646
|
+
if (!res.ok)
|
|
1647
|
+
process.exitCode = 1;
|
|
1648
|
+
}
|
|
1566
1649
|
}
|
|
1567
1650
|
catch (err) {
|
|
1568
1651
|
console.error(pc.red(err instanceof Error ? err.message : String(err)));
|
|
@@ -1571,24 +1654,41 @@ program
|
|
|
1571
1654
|
});
|
|
1572
1655
|
program
|
|
1573
1656
|
.command("orchestra [runId]")
|
|
1574
|
-
.description("orchestra runs in this project — or one run in detail")
|
|
1657
|
+
.description("orchestra runs in this project — or one run in detail. Status: starting | planning | running | reviewing | waiting_human | completed | failed | aborted | moved")
|
|
1575
1658
|
.option("--watch", "follow the run until it finishes")
|
|
1659
|
+
.option("--json", "print runs (or one run) as JSON")
|
|
1660
|
+
.option("--wait", "with a run id: wait until it's completed, failed or aborted (or asks you something); exit 0 completed, 1 failed/aborted, 2 waiting on you")
|
|
1661
|
+
.option("--timeout <seconds>", "with --wait: give up after this long (exit 124)", "7200")
|
|
1576
1662
|
.action(async (runId, opts) => {
|
|
1577
1663
|
const client = await ensureDaemon();
|
|
1578
1664
|
const project = await currentProject(client);
|
|
1579
1665
|
try {
|
|
1580
1666
|
if (runId) {
|
|
1667
|
+
if (opts.wait) {
|
|
1668
|
+
// "reviewing" is the orchestrator reading results, not the end (#341) — only these are
|
|
1669
|
+
const deadline = Date.now() + Math.max(1, Number(opts.timeout) || 7200) * 1000;
|
|
1670
|
+
let run = (await client.orchestraRun(project.id, runId)).run;
|
|
1671
|
+
while (!isTerminalStatus(run.status) && run.status !== "waiting_human" && Date.now() < deadline) {
|
|
1672
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
1673
|
+
run = (await client.orchestraRun(project.id, runId)).run;
|
|
1674
|
+
}
|
|
1675
|
+
const done = isTerminalStatus(run.status) || run.status === "waiting_human";
|
|
1676
|
+
process.exitCode = !done ? 124 : run.status === "completed" ? 0 : run.status === "waiting_human" ? 2 : 1;
|
|
1677
|
+
return void (opts.json ? console.log(JSON.stringify(runJson(run))) : printRun(run));
|
|
1678
|
+
}
|
|
1581
1679
|
if (opts.watch)
|
|
1582
1680
|
return void (await watchRun(client, project.id, runId));
|
|
1583
|
-
|
|
1681
|
+
const { run } = await client.orchestraRun(project.id, runId);
|
|
1682
|
+
return void (opts.json ? console.log(JSON.stringify(runJson(run))) : printRun(run));
|
|
1584
1683
|
}
|
|
1585
1684
|
const { runs } = await client.orchestraRuns(project.id);
|
|
1685
|
+
if (opts.json)
|
|
1686
|
+
return void console.log(JSON.stringify(runs.map(runJson)));
|
|
1586
1687
|
if (!runs.length)
|
|
1587
1688
|
return void console.log(pc.dim('no orchestra runs yet — loom orchestrate "<goal>"'));
|
|
1588
1689
|
for (const r of runs.slice(0, 15)) {
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
`${pc.dim(new Date(r.createdAt).toLocaleString())} ${r.goal.slice(0, 70)}`);
|
|
1690
|
+
console.log(`${pc.bold(r.id)} ${runStatus(r).padEnd(13)} ${countsLine(r).padEnd(30)} ` +
|
|
1691
|
+
`${pc.dim(new Date(r.createdAt).toLocaleString())} ${r.goal.slice(0, 60)}`);
|
|
1592
1692
|
}
|
|
1593
1693
|
}
|
|
1594
1694
|
catch (err) {
|
|
@@ -2557,6 +2657,138 @@ program
|
|
|
2557
2657
|
process.exitCode = 1;
|
|
2558
2658
|
}
|
|
2559
2659
|
});
|
|
2660
|
+
program
|
|
2661
|
+
.command("share [chat]")
|
|
2662
|
+
.description("a read-only link to a chat (default: main) for a teammate: prompts, replies, what ran and changed. It expires; --list shows yours, --revoke ends one (#199)")
|
|
2663
|
+
.option("--hours <n>", "how long the link works (1 to 720, default 24)")
|
|
2664
|
+
.option("--list", "the links that still work")
|
|
2665
|
+
.option("--revoke <id>", "end a link now")
|
|
2666
|
+
.option("--json", "print as JSON")
|
|
2667
|
+
.action(async (chat, opts) => {
|
|
2668
|
+
const client = await ensureDaemon();
|
|
2669
|
+
if (opts.revoke) {
|
|
2670
|
+
const { revoked } = await client.revokeShare(opts.revoke);
|
|
2671
|
+
if (!revoked)
|
|
2672
|
+
fail(`no link ${opts.revoke} — loom share --list`);
|
|
2673
|
+
return void console.log(`${pc.green("✓")} link ${opts.revoke} no longer opens`);
|
|
2674
|
+
}
|
|
2675
|
+
if (opts.list) {
|
|
2676
|
+
const { shares } = await client.shares();
|
|
2677
|
+
if (opts.json)
|
|
2678
|
+
return void console.log(JSON.stringify(shares));
|
|
2679
|
+
if (!shares.length)
|
|
2680
|
+
return void console.log(pc.dim("no links open — loom share [chat]"));
|
|
2681
|
+
for (const s of shares)
|
|
2682
|
+
console.log(`${pc.bold(s.id)} ${s.title} ${pc.dim(`until ${new Date(s.expiresAt).toLocaleString()}`)}`);
|
|
2683
|
+
return;
|
|
2684
|
+
}
|
|
2685
|
+
const project = await currentProject(client);
|
|
2686
|
+
let chatId = "main";
|
|
2687
|
+
if (chat && chat !== "main") {
|
|
2688
|
+
const { chats } = await client.chats(project.id);
|
|
2689
|
+
const hit = chats.find((c) => c.id === chat || c.title.toLowerCase() === chat.toLowerCase());
|
|
2690
|
+
if (!hit)
|
|
2691
|
+
fail(`no chat "${chat}" here — ${chats.slice(0, 6).map((c) => c.title).join(", ") || "only main"}`);
|
|
2692
|
+
chatId = hit.id;
|
|
2693
|
+
}
|
|
2694
|
+
const out = await client.shareChat(project.id, chatId, opts.hours ? Number(opts.hours) : undefined);
|
|
2695
|
+
if (opts.json)
|
|
2696
|
+
return void console.log(JSON.stringify(out));
|
|
2697
|
+
console.log(`${pc.green("✓")} ${out.title} — read-only, until ${new Date(out.expiresAt).toLocaleString()}\n\n ${out.url}\n`);
|
|
2698
|
+
console.log(pc.dim(`anyone who can reach this machine with the link can read it (on another computer: start the daemon with loom up --tailnet). loom share --revoke ${out.id} ends it`));
|
|
2699
|
+
});
|
|
2700
|
+
program
|
|
2701
|
+
.command("chain [file]")
|
|
2702
|
+
.description("run goals in several projects, one after another: each line is `<project> :: <goal>` (project = name or folder). Each completed run is scanned for secrets and merged before the next starts (#340)")
|
|
2703
|
+
.option("--push", "push each project's branch after merging its run")
|
|
2704
|
+
.option("--keep-going", "carry on to the next project when one fails")
|
|
2705
|
+
.option("--json", "print a JSON summary at the end")
|
|
2706
|
+
.action(async (file, opts) => {
|
|
2707
|
+
const text = file && file !== "-" ? fs.readFileSync(file, "utf8") : await new Promise((resolve) => {
|
|
2708
|
+
let buf = "";
|
|
2709
|
+
process.stdin.setEncoding("utf8");
|
|
2710
|
+
process.stdin.on("data", (d) => (buf += d));
|
|
2711
|
+
process.stdin.on("end", () => resolve(buf));
|
|
2712
|
+
});
|
|
2713
|
+
const steps = text.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#")).map((l) => {
|
|
2714
|
+
const at = l.indexOf("::");
|
|
2715
|
+
if (at < 0)
|
|
2716
|
+
fail(`each line is "<project> :: <goal>" — got: ${l}`);
|
|
2717
|
+
return { project: l.slice(0, at).trim(), goal: l.slice(at + 2).trim() };
|
|
2718
|
+
});
|
|
2719
|
+
if (!steps.length)
|
|
2720
|
+
fail("nothing to run — one `<project> :: <goal>` per line");
|
|
2721
|
+
const client = await ensureDaemon();
|
|
2722
|
+
const { projects } = await client.listProjects();
|
|
2723
|
+
const results = [];
|
|
2724
|
+
for (const step of steps) {
|
|
2725
|
+
const p = projects.find((x) => x.name === step.project || x.id === step.project || path.resolve(x.dir) === path.resolve(step.project));
|
|
2726
|
+
if (!p) {
|
|
2727
|
+
results.push({ project: step.project, ok: false, runId: "-", status: "not started", error: `no project "${step.project}" — loom projects` });
|
|
2728
|
+
if (!opts.keepGoing)
|
|
2729
|
+
break;
|
|
2730
|
+
continue;
|
|
2731
|
+
}
|
|
2732
|
+
if (!opts.json)
|
|
2733
|
+
console.log(`${pc.magenta("▶")} ${pc.bold(p.name)} — ${step.goal.slice(0, 80)}`);
|
|
2734
|
+
try {
|
|
2735
|
+
const { run } = await client.startOrchestra(p.id, { goal: step.goal });
|
|
2736
|
+
const r = { project: p.name, ...(await waitScanApply(client, p.id, p.dir, run.id, { push: Boolean(opts.push) })) };
|
|
2737
|
+
results.push(r);
|
|
2738
|
+
if (!opts.json)
|
|
2739
|
+
printApplyResult(r);
|
|
2740
|
+
if (!r.ok && !opts.keepGoing)
|
|
2741
|
+
break;
|
|
2742
|
+
}
|
|
2743
|
+
catch (e) {
|
|
2744
|
+
results.push({ project: p.name, ok: false, runId: "-", status: "not started", error: e.message });
|
|
2745
|
+
if (!opts.json)
|
|
2746
|
+
console.log(`${pc.red("✗")} ${p.name}: ${e.message}`);
|
|
2747
|
+
if (!opts.keepGoing)
|
|
2748
|
+
break;
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
const failed = results.filter((r) => !r.ok).length;
|
|
2752
|
+
if (opts.json)
|
|
2753
|
+
console.log(JSON.stringify({ ok: failed === 0, steps: results }));
|
|
2754
|
+
else
|
|
2755
|
+
console.log(pc.dim(`\n${results.length - failed}/${steps.length} merged${failed ? ` · ${failed} failed` : ""}${results.length < steps.length ? ` · ${steps.length - results.length} not run` : ""}`));
|
|
2756
|
+
if (failed || results.length < steps.length)
|
|
2757
|
+
process.exitCode = 1;
|
|
2758
|
+
});
|
|
2759
|
+
program
|
|
2760
|
+
.command("limits")
|
|
2761
|
+
.description("how many agent turns run at once across every project, and how much memory stays free — past either, turns queue (#339)")
|
|
2762
|
+
.option("--turns <n>", "turns at once across all projects (0 = no cap)")
|
|
2763
|
+
.option("--min-free <pct>", "don't start a turn while less than this % of memory is free (0 = off)")
|
|
2764
|
+
.option("--json", "print as JSON")
|
|
2765
|
+
.action(async (opts) => {
|
|
2766
|
+
const { readLimits, writeLimits, availableMemoryPct, defaultLimits } = await import("../core/turn-gate.js");
|
|
2767
|
+
const patch = {};
|
|
2768
|
+
if (opts.turns !== undefined) {
|
|
2769
|
+
const n = Number(opts.turns);
|
|
2770
|
+
if (!Number.isInteger(n) || n < 0)
|
|
2771
|
+
fail("--turns takes a whole number (0 = no cap)");
|
|
2772
|
+
patch.maxTurns = n;
|
|
2773
|
+
}
|
|
2774
|
+
if (opts.minFree !== undefined) {
|
|
2775
|
+
const n = Number(opts.minFree);
|
|
2776
|
+
if (!(n >= 0 && n < 100))
|
|
2777
|
+
fail("--min-free takes a % from 0 to 99");
|
|
2778
|
+
patch.minFreePct = n;
|
|
2779
|
+
}
|
|
2780
|
+
const limits = Object.keys(patch).length ? writeLimits(patch) : readLimits();
|
|
2781
|
+
const free = Math.round(availableMemoryPct());
|
|
2782
|
+
const cfg = await daemonRunning();
|
|
2783
|
+
const running = cfg ? await turnsInFlight(new DaemonClient(cfg)) : [];
|
|
2784
|
+
if (opts.json)
|
|
2785
|
+
return void console.log(JSON.stringify({ ...limits, freePct: free, running, defaults: defaultLimits() }));
|
|
2786
|
+
if (Object.keys(patch).length)
|
|
2787
|
+
console.log(pc.green("✓ saved — the daemon picks it up within a few seconds"));
|
|
2788
|
+
console.log(`turns at once ${limits.maxTurns ? pc.bold(String(limits.maxTurns)) : pc.dim("no cap")} ${pc.dim(`(running now: ${running.length}${running.length ? ` — ${running.slice(0, 4).join(", ")}` : ""})`)}`);
|
|
2789
|
+
console.log(`memory kept free ${limits.minFreePct ? pc.bold(`${limits.minFreePct}%`) : pc.dim("off")} ${pc.dim(`(free now: ${free}%)`)}`);
|
|
2790
|
+
console.log(pc.dim("past either, a new turn waits for a slot and says so in its thread; one that waits 10 min starts anyway"));
|
|
2791
|
+
});
|
|
2560
2792
|
program
|
|
2561
2793
|
.command("agents:mode <agentId> [mode]")
|
|
2562
2794
|
.description("an agent's access: bypass (no sandbox — can commit, push, use the network), auto (the default), or ask. No mode: show what each means for it")
|