@loompad/cli 1.2.2 → 1.2.3
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 +28 -0
- package/dist/cli/index.js +33 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/core/crew.d.ts +16 -1
- package/dist/core/crew.js +26 -2
- package/dist/core/crew.js.map +1 -1
- package/dist/core/orchestra.d.ts +36 -0
- package/dist/core/orchestra.js +149 -82
- package/dist/core/orchestra.js.map +1 -1
- package/dist/core/team-hub.d.ts +2 -1
- package/dist/core/team-hub.js +1 -1
- package/dist/core/team-hub.js.map +1 -1
- package/dist/core/team-leases.d.ts +7 -0
- package/dist/core/team-leases.js +19 -0
- package/dist/core/team-leases.js.map +1 -1
- package/dist/daemon/client.d.ts +9 -1
- package/dist/daemon/client.js +2 -2
- package/dist/daemon/client.js.map +1 -1
- package/dist/daemon/routes/brain.js +2 -2
- package/dist/daemon/routes/brain.js.map +1 -1
- package/dist/daemon/routes/orchestra.js +1 -0
- package/dist/daemon/routes/orchestra.js.map +1 -1
- package/dist/daemon/runner.d.ts +11 -0
- package/dist/daemon/runner.js +96 -3
- package/dist/daemon/runner.js.map +1 -1
- package/dist/daemon/runtime/briefings.js +12 -1
- package/dist/daemon/runtime/briefings.js.map +1 -1
- package/dist/daemon/runtime.d.ts +9 -2
- package/dist/daemon/runtime.js +8 -4
- package/dist/daemon/runtime.js.map +1 -1
- package/dist/daemon/team-coordinator.d.ts +27 -3
- package/dist/daemon/team-coordinator.js +54 -2
- package/dist/daemon/team-coordinator.js.map +1 -1
- package/dist/daemon/team.d.ts +9 -1
- package/dist/daemon/team.js +44 -1
- package/dist/daemon/team.js.map +1 -1
- package/dist/hub/supabase-client.d.ts +4 -2
- package/dist/hub/supabase-client.js +21 -12
- package/dist/hub/supabase-client.js.map +1 -1
- package/dist/web/app.css +4 -0
- package/dist/web/app.js +96 -27
- package/dist/web/app.js.map +3 -3
- package/docs/release-notes/v1.2.3.md +29 -0
- package/docs/teams.md +28 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.3] — 2026-10-22
|
|
11
|
+
|
|
12
|
+
Teams that work together on their own: one goal splits across teammates' machines. Plus Simple mode, hub hardening, and the fourth field round.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Team auto-orchestration: two people, one goal, both machines.** With a teammate's Loom running as a shared runner (`loom runner start --shared`, and `runners.shared` in the repo's `loom.team.json`), the goal you start splits across both of you: your orchestrator gets their agents as `team:<agent>` workers, a task sent to one is claimed by their runner on its own and run with *their* agent in a fresh clone, and its commits come back through origin into your goal's integration branch — still one PR, from your machine. `loom orchestrate --solo` keeps a goal local. A task no teammate's machine claims within 10 minutes goes back to the orchestrator to hand to someone else. Hub jobs gain a `task` kind (migration 0012).
|
|
17
|
+
- **Simple mode, and it's the default.** A project opens on five plain tabs — Chat, Team, Agents, Board, Memory — and a composer that's just a box, the agent and Plan. No Orchestra or Crew tabs, no Insights metrics, no terminal, console, browser or right-hand panel, no model, permission or context chips, no cost meter or baton in the status bar, no MCP/skills menu; ⌘K and the menu shortcuts leave those out too, and a stray link to one says where it is instead of half-opening it.
|
|
18
|
+
- **Developer mode** (Settings → Preferences → Mode) turns every one of those back on, instantly and per device. Nothing was removed — it's all where it was.
|
|
19
|
+
- `loom memory:repair --dry-run` shows each memory it would change, before → after, and writes nothing (#326).
|
|
20
|
+
- `loom crew status --json`: crews, goals and every card with its id, stage, touches and hold (#329).
|
|
21
|
+
|
|
22
|
+
### Security
|
|
23
|
+
|
|
24
|
+
- **Hub hardening (migration 0012).** Presence rows get an opaque key, so a disconnect no longer broadcasts another team's repo names to every signed-in subscriber (Realtime skips RLS on deletes); a member can no longer overwrite a teammate's key envelope; the membership helpers aren't callable by anonymous users; stale presence, expired leases and invites, and feed older than 90 days are pruned hourly.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **`loom runner stop` really stops taking work.** A stopped runner still claimed a job the hub pushed to it; it now takes nothing new until it's started again.
|
|
29
|
+
- Simple mode no longer shows Developer vocabulary: the chat's handoff chip drops the word "baton" (and the opening hand-out from nobody), and the Agents tab hides the baton and permission badges.
|
|
30
|
+
- The Verified card quoted an agent's claim with its raw `**`/backticks; it quotes the words.
|
|
31
|
+
- A workspace tab could fail to switch when the window wasn't painting (backgrounded), because its crossfade never ran; the click now switches it regardless.
|
|
32
|
+
- **`memory:repair` now shows in `loom memory` and briefings** (#325). A decision is kept twice: an event (the record) and a memory (the live copy). The repair fixed the memory, but the Memory view and every handoff briefing read the record, so the old text stayed. They now read the live copy.
|
|
33
|
+
- **A crew card no longer loops on its own lease** (#327). Codex reports edits by absolute path inside the card's worktree, and leases are repo-relative globs, so every edit looked out of bounds. The card re-held within a minute of each `crew overlap`, and the builder never ran a turn. Paths are now made repo-relative before matching.
|
|
34
|
+
- **Crew checkouts follow orchestra's** (#328). A project on another disk keeps them in `.loom-worktrees` beside it, not on the internal disk; `orchestra.worktreeDir` decides for both. A checkout that already exists stays where it is.
|
|
35
|
+
- **`crew status` shows card ids** (#329), plus a hint per failed card (`loom crew accept --card <id>`). A lease hold now points at `crew overlap` rather than `crew say`. The goal's `failed` event lists `failedCards`.
|
|
36
|
+
|
|
37
|
+
|
|
10
38
|
## [1.2.2] — 2026-10-18
|
|
11
39
|
|
|
12
40
|
The Team tab and GitHub sign-in, and everything a third stress test (5 projects at once, restarts mid-run) found.
|
package/dist/cli/index.js
CHANGED
|
@@ -1506,6 +1506,7 @@ program
|
|
|
1506
1506
|
.option("--plan", "plan mode: write the plan as markdown specs under plans/<run>/ that any agent can pick up")
|
|
1507
1507
|
.option("--max-usd <n>", "stop this goal when it has spent this much, and say so")
|
|
1508
1508
|
.option("--race", "race: every worker takes the whole goal in its own worktree; pick one with orchestra:apply <run> --task race-<agent>")
|
|
1509
|
+
.option("--solo", "keep every task on this machine, even when teammates' runners could take some")
|
|
1509
1510
|
.option("--no-watch", "start it and return instead of following it to the end")
|
|
1510
1511
|
.action(async (goal, opts) => {
|
|
1511
1512
|
const client = await ensureDaemon();
|
|
@@ -1520,7 +1521,11 @@ program
|
|
|
1520
1521
|
...(opts.plan ? { plan: true } : {}),
|
|
1521
1522
|
...(opts.maxUsd ? { maxUsd: Number(opts.maxUsd) } : {}),
|
|
1522
1523
|
...(opts.race ? { race: true } : {}),
|
|
1524
|
+
...(opts.solo ? { team: false } : {}),
|
|
1523
1525
|
});
|
|
1526
|
+
const mates = run.workers.filter((w) => w.startsWith("team:"));
|
|
1527
|
+
if (mates.length)
|
|
1528
|
+
console.log(pc.dim(` teammates' machines can take tasks too: ${mates.join(", ")}`));
|
|
1524
1529
|
console.log(run.race
|
|
1525
1530
|
? `${pc.magenta("🏁")} ${pc.bold(run.id)} started — ${run.workers.join(", ")} racing on the same goal`
|
|
1526
1531
|
: `${pc.magenta("🎼")} ${pc.bold(run.id)} started — ${pc.bold(run.orchestrator.agent)} orchestrating ` +
|
|
@@ -1855,12 +1860,23 @@ program
|
|
|
1855
1860
|
.option("--card <id>", "overlap: held card id; accept: the failed card to take as it stands")
|
|
1856
1861
|
.option("--decision <decision>", "overlap: narrow, wait:<goal>, or proceed:<reason>")
|
|
1857
1862
|
.option("--touches <globs>", "overlap: comma-separated revised paths")
|
|
1863
|
+
.option("--json", "status: machine-readable")
|
|
1858
1864
|
.action(async (action, args, opts) => {
|
|
1859
1865
|
const client = await ensureDaemon();
|
|
1860
1866
|
const project = await currentProject(client);
|
|
1861
1867
|
const a = (action ?? "status").toLowerCase();
|
|
1862
1868
|
const words = (args ?? []).join(" ").trim();
|
|
1863
1869
|
const { crews, templates } = await client.crews(project.id);
|
|
1870
|
+
if (a === "status" && opts.json) {
|
|
1871
|
+
// every crew, its goal and each card with its id — what accept/overlap take (#329)
|
|
1872
|
+
const pick = opts.crew ? crews.filter((c) => c.id === opts.crew || c.name === opts.crew) : crews;
|
|
1873
|
+
console.log(JSON.stringify({ crews: pick.map((c) => ({ id: c.id, name: c.name, busy: Boolean(c.busy), teammates: c.teammates,
|
|
1874
|
+
goal: c.state?.goal ? { id: c.state.goal.id, status: c.state.goal.status, text: c.state.goal.text, branch: c.state.goal.branch,
|
|
1875
|
+
question: c.state.goal.question ?? null, costUsd: c.state.goal.costUsd ?? null,
|
|
1876
|
+
cards: (c.state.goal.cards ?? []).map((k) => ({ id: k.id, title: k.title, stage: k.stage, builder: k.builder ?? null,
|
|
1877
|
+
touches: k.touches ?? [], blockedBy: k.blockedBy ?? [], error: k.error ?? null, hold: k.teamHold ?? k.hold ?? null })) } : null })) }, null, 2));
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1864
1880
|
if (a === "create") {
|
|
1865
1881
|
const [first, ...rest] = args ?? [];
|
|
1866
1882
|
const template = first && templates.includes(first.toLowerCase()) ? first.toLowerCase() : "ship";
|
|
@@ -1920,10 +1936,16 @@ function printCrew(c) {
|
|
|
1920
1936
|
console.log(` ${color(g.status)} ${g.text.split("\n")[0].slice(0, 80)} ${pc.dim(g.branch)}`);
|
|
1921
1937
|
for (const card of g.cards ?? []) {
|
|
1922
1938
|
const m = card.stage === "done" ? pc.green("✓") : card.stage === "failed" ? pc.red("✗") : card.stage === "planned" ? pc.dim("·") : pc.cyan("→");
|
|
1923
|
-
console.log(` ${m} ${card.title}${card.builder ? pc.dim(` ${card.builder}`) : ""}${card.stage !== "done" && card.stage !== "planned" ? pc.dim(` [${card.stage}]`) : ""}${card.error ? pc.red(` ${card.error.slice(0, 80)}`) : ""}`);
|
|
1939
|
+
console.log(` ${m} ${pc.dim(card.id)} ${card.title}${card.builder ? pc.dim(` ${card.builder}`) : ""}${card.stage !== "done" && card.stage !== "planned" ? pc.dim(` [${card.stage}]`) : ""}${card.error ? pc.red(` ${card.error.slice(0, 80)}`) : ""}`);
|
|
1924
1940
|
}
|
|
1941
|
+
// a lease hold is settled with overlap (widen or narrow the card's paths), not by answering it
|
|
1942
|
+
const leaseHold = (g.cards ?? []).find((k) => k.teamHold?.kind === "decide");
|
|
1925
1943
|
if (g.question)
|
|
1926
|
-
console.log(pc.yellow(` ${g.question.teammate} asks: ${g.question.text}`) + pc.dim(
|
|
1944
|
+
console.log(pc.yellow(` ${g.question.teammate} asks: ${g.question.text}`) + pc.dim(leaseHold
|
|
1945
|
+
? ` (loom crew overlap --card ${leaseHold.id} --decision "proceed: <why>" --touches "<paths>")`
|
|
1946
|
+
: " (loom crew say \"…\")"));
|
|
1947
|
+
for (const k of (g.cards ?? []).filter((x) => x.stage === "failed"))
|
|
1948
|
+
console.log(pc.dim(` take ${k.id} as it stands: loom crew accept --card ${k.id}`));
|
|
1927
1949
|
if (g.status === "awaiting_approval")
|
|
1928
1950
|
console.log(pc.dim(" loom crew approve — or loom crew say \"…\" to change the plan"));
|
|
1929
1951
|
if (g.status === "completed" && !g.applied)
|
|
@@ -3166,10 +3188,17 @@ program
|
|
|
3166
3188
|
program
|
|
3167
3189
|
.command("memory:repair")
|
|
3168
3190
|
.description("mend decisions recorded before 1.2.1 whose underscores and paths were stripped, from the planner's own words")
|
|
3169
|
-
.
|
|
3191
|
+
.option("--dry-run", "show what would change, and change nothing")
|
|
3192
|
+
.action(async (opts) => {
|
|
3170
3193
|
const client = await ensureDaemon();
|
|
3171
3194
|
const project = await currentProject(client);
|
|
3172
|
-
const { repaired, checked } = await client.repairDecisions(project.id);
|
|
3195
|
+
const { repaired, checked, changes } = await client.repairDecisions(project.id, { dryRun: opts.dryRun });
|
|
3196
|
+
if (opts.dryRun) {
|
|
3197
|
+
for (const c of changes ?? [])
|
|
3198
|
+
console.log(`${pc.dim(c.id)}\n ${pc.red("- " + c.before)}\n ${pc.green("+ " + c.after)}`);
|
|
3199
|
+
console.log(repaired ? pc.yellow(`would mend ${repaired} of ${checked} review decisions — nothing written (run without --dry-run to apply)`) : pc.dim(`nothing to mend among ${checked} review decisions`));
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3173
3202
|
console.log(repaired ? pc.green(`✓ mended ${repaired} of ${checked} review decisions`) : pc.dim(`nothing to mend among ${checked} review decisions`));
|
|
3174
3203
|
});
|
|
3175
3204
|
program
|