@loompad/cli 1.2.2 → 1.2.4

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/CHANGELOG.md +42 -0
  2. package/README.md +10 -0
  3. package/dist/cli/index.js +59 -7
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/core/crew.d.ts +16 -1
  6. package/dist/core/crew.js +26 -2
  7. package/dist/core/crew.js.map +1 -1
  8. package/dist/core/orchestra.d.ts +36 -0
  9. package/dist/core/orchestra.js +154 -82
  10. package/dist/core/orchestra.js.map +1 -1
  11. package/dist/core/team-hub.d.ts +2 -1
  12. package/dist/core/team-hub.js +1 -1
  13. package/dist/core/team-hub.js.map +1 -1
  14. package/dist/core/team-leases.d.ts +7 -0
  15. package/dist/core/team-leases.js +19 -0
  16. package/dist/core/team-leases.js.map +1 -1
  17. package/dist/daemon/client.d.ts +12 -1
  18. package/dist/daemon/client.js +5 -2
  19. package/dist/daemon/client.js.map +1 -1
  20. package/dist/daemon/routes/brain.js +2 -2
  21. package/dist/daemon/routes/brain.js.map +1 -1
  22. package/dist/daemon/routes/orchestra.js +1 -0
  23. package/dist/daemon/routes/orchestra.js.map +1 -1
  24. package/dist/daemon/routes/project-team.js +17 -0
  25. package/dist/daemon/routes/project-team.js.map +1 -1
  26. package/dist/daemon/runner.d.ts +11 -0
  27. package/dist/daemon/runner.js +111 -5
  28. package/dist/daemon/runner.js.map +1 -1
  29. package/dist/daemon/runtime/briefings.js +12 -1
  30. package/dist/daemon/runtime/briefings.js.map +1 -1
  31. package/dist/daemon/runtime.d.ts +9 -2
  32. package/dist/daemon/runtime.js +8 -4
  33. package/dist/daemon/runtime.js.map +1 -1
  34. package/dist/daemon/team-coordinator.d.ts +27 -3
  35. package/dist/daemon/team-coordinator.js +54 -2
  36. package/dist/daemon/team-coordinator.js.map +1 -1
  37. package/dist/daemon/team.d.ts +42 -1
  38. package/dist/daemon/team.js +92 -1
  39. package/dist/daemon/team.js.map +1 -1
  40. package/dist/hub/supabase-client.d.ts +4 -2
  41. package/dist/hub/supabase-client.js +21 -12
  42. package/dist/hub/supabase-client.js.map +1 -1
  43. package/dist/web/app.css +12 -0
  44. package/dist/web/app.js +140 -28
  45. package/dist/web/app.js.map +3 -3
  46. package/docs/release-notes/v1.2.3.md +29 -0
  47. package/docs/release-notes/v1.2.4.md +13 -0
  48. package/docs/teams.md +31 -0
  49. package/package.json +1 -1
@@ -19,7 +19,7 @@
19
19
  * crews across people are later phases (§11).
20
20
  */
21
21
  import type { TeamPolicy } from "./team-policy.js";
22
- import type { OrchestraCoordinator, TaskHold, OrchestraRun } from "./orchestra.js";
22
+ import { type OrchestraCoordinator, type TaskHold, type OrchestraRun } from "./orchestra.js";
23
23
  import { type ContinuityEngine } from "./continuity/engine.js";
24
24
  import type { Adapter, AgentConfig, ChatInfo, EventKind, LoomEvent } from "../types.js";
25
25
  import { type BoardTask } from "./registry.js";
@@ -182,6 +182,8 @@ export interface CrewHost {
182
182
  projectId: string;
183
183
  projectName: string;
184
184
  projectDir: string;
185
+ /** `orchestra.worktreeDir` from the project config — crews share orchestra's root (#328). */
186
+ worktreeDir?: () => string | undefined;
185
187
  crews(): CrewConfig[];
186
188
  saveCrews(crews: CrewConfig[]): void;
187
189
  roster(): AgentConfig[];
@@ -249,6 +251,13 @@ export interface CrewSeats {
249
251
  }
250
252
  /** The template's seats, with the ones you named filled by those agents (builders in turn). */
251
253
  export declare function seatCrew(teammates: CrewTeammate[], seats?: CrewSeats): CrewTeammate[];
254
+ /**
255
+ * Where a project's crew worktrees go: orchestra's root (#295) — under ~/.loom
256
+ * on the same disk, beside the project when it lives on another volume, or
257
+ * `orchestra.worktreeDir` — so a crew's checkouts don't fill the internal disk
258
+ * either (#328).
259
+ */
260
+ export declare function crewWorktreeRoot(projectDir: string, projectId: string, override?: string): string;
252
261
  export declare class CrewEngine {
253
262
  private readonly host;
254
263
  private states;
@@ -276,6 +285,12 @@ export declare class CrewEngine {
276
285
  private testControllers;
277
286
  private teamRechecks;
278
287
  private deliveries;
288
+ /**
289
+ * A crew checkout's folder under the worktree root. One a crew already has at
290
+ * the old ~/.loom/crews spot stays where it is — a native seat's workspace
291
+ * lives across goals — so only new checkouts move.
292
+ */
293
+ private worktreePath;
279
294
  constructor(host: CrewHost);
280
295
  /** The agents a teammate can be. */
281
296
  roster(): AgentConfig[];
package/dist/core/crew.js CHANGED
@@ -26,6 +26,7 @@ import { digest } from "./continuity/contracts.js";
26
26
  import { coordinationRun, crewRunId } from "./crew-coordination.js";
27
27
  import { DEFAULT_CREW_POLICY } from "./crew-policy.js";
28
28
  import { vendorOf } from "./team-landing.js";
29
+ import { orchestraWorktreeRoot } from "./orchestra.js";
29
30
  import fs from "node:fs";
30
31
  import path from "node:path";
31
32
  import { randomUUID } from "node:crypto";
@@ -120,6 +121,16 @@ async function git(args, cwd) {
120
121
  const { stdout } = await exec("git", args, { cwd, maxBuffer: 32 * 1024 * 1024 });
121
122
  return stdout;
122
123
  }
124
+ /**
125
+ * Where a project's crew worktrees go: orchestra's root (#295) — under ~/.loom
126
+ * on the same disk, beside the project when it lives on another volume, or
127
+ * `orchestra.worktreeDir` — so a crew's checkouts don't fill the internal disk
128
+ * either (#328).
129
+ */
130
+ export function crewWorktreeRoot(projectDir, projectId, override) {
131
+ const orchestra = orchestraWorktreeRoot(projectDir, projectId, override);
132
+ return orchestra === path.join(loomHome(), "orchestra", projectId) ? path.join(loomHome(), "crews", projectId) : path.join(orchestra, "crews");
133
+ }
123
134
  export class CrewEngine {
124
135
  host;
125
136
  states = new Map();
@@ -149,6 +160,17 @@ export class CrewEngine {
149
160
  testControllers = new Map();
150
161
  teamRechecks = new Map();
151
162
  deliveries = new Map();
163
+ /**
164
+ * A crew checkout's folder under the worktree root. One a crew already has at
165
+ * the old ~/.loom/crews spot stays where it is — a native seat's workspace
166
+ * lives across goals — so only new checkouts move.
167
+ */
168
+ worktreePath(crewId, ...parts) {
169
+ const legacy = path.join(loomHome(), "crews", this.host.projectId, crewId, ...parts);
170
+ if (fs.existsSync(legacy))
171
+ return legacy;
172
+ return path.join(crewWorktreeRoot(this.host.projectDir, this.host.projectId, this.host.worktreeDir?.()), crewId, ...parts);
173
+ }
152
174
  constructor(host) {
153
175
  this.host = host;
154
176
  // A goal that was mid-turn when Loom stopped is interrupted, not lost:
@@ -348,7 +370,7 @@ export class CrewEngine {
348
370
  const branch = `loom/crew/${cfg.id}/${gid}`;
349
371
  // Native sessions retain their workspace identity between goals. Branches
350
372
  // remain per goal; switching a clean, stopped checkout changes its snapshot.
351
- const dir = path.join(loomHome(), "crews", this.host.projectId, cfg.id, this.host.native?.() ? "workspace" : gid);
373
+ const dir = this.worktreePath(cfg.id, this.host.native?.() ? "workspace" : gid);
352
374
  fs.mkdirSync(path.dirname(dir), { recursive: true });
353
375
  await this.serial(async () => {
354
376
  if (fs.existsSync(dir)) {
@@ -1070,7 +1092,7 @@ export class CrewEngine {
1070
1092
  }
1071
1093
  const base = (await git(["rev-parse", "HEAD"], g.dir)).trim();
1072
1094
  const branch = `${g.branch}-card-${card.id}`;
1073
- const dir = path.join(loomHome(), "crews", this.host.projectId, cfg.id, this.host.native?.() ? `seat-${card.builder}` : `cards-${g.id}`, this.host.native?.() ? "workspace" : card.id);
1095
+ const dir = this.worktreePath(cfg.id, this.host.native?.() ? `seat-${card.builder}` : `cards-${g.id}`, this.host.native?.() ? "workspace" : card.id);
1074
1096
  fs.mkdirSync(path.dirname(dir), { recursive: true });
1075
1097
  await this.serial(async () => {
1076
1098
  if (fs.existsSync(dir)) {
@@ -1363,6 +1385,8 @@ export class CrewEngine {
1363
1385
  this.save(st);
1364
1386
  this.phase(cfg, st, status === "completed" ? "completed" : "failed", {
1365
1387
  branch: g.branch, cards: g.cards.length, ...(g.summary ? { summary: g.summary } : {}), ...(error ? { error } : {}), costUsd: g.costUsd,
1388
+ // which cards failed, by id — what `loom crew accept --card` takes (#329)
1389
+ ...(status === "failed" ? { failedCards: g.cards.filter(c => c.stage === "failed").map(c => ({ id: c.id, title: c.title, ...(c.error ? { error: c.error.slice(0, 300) } : {}) })) } : {}),
1366
1390
  });
1367
1391
  const stopping = this.stopAgents(cfg.id).finally(() => this.stopping.delete(cfg.id));
1368
1392
  this.stopping.set(cfg.id, stopping);