@clipboard-health/groundcrew 3.1.2 → 3.1.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 (59) hide show
  1. package/README.md +47 -17
  2. package/crew.config.example.ts +25 -10
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +12 -0
  5. package/dist/commands/cleaner.d.ts.map +1 -1
  6. package/dist/commands/cleaner.js +6 -4
  7. package/dist/commands/cleanupWorkspace.d.ts.map +1 -1
  8. package/dist/commands/cleanupWorkspace.js +2 -0
  9. package/dist/commands/dispatcher.d.ts.map +1 -1
  10. package/dist/commands/dispatcher.js +6 -6
  11. package/dist/commands/eligibility.d.ts.map +1 -1
  12. package/dist/commands/eligibility.js +2 -2
  13. package/dist/commands/interruptWorkspace.d.ts +8 -0
  14. package/dist/commands/interruptWorkspace.d.ts.map +1 -0
  15. package/dist/commands/interruptWorkspace.js +108 -0
  16. package/dist/commands/orchestrator.d.ts +4 -2
  17. package/dist/commands/orchestrator.d.ts.map +1 -1
  18. package/dist/commands/orchestrator.js +6 -105
  19. package/dist/commands/resumeWorkspace.d.ts +7 -0
  20. package/dist/commands/resumeWorkspace.d.ts.map +1 -0
  21. package/dist/commands/resumeWorkspace.js +163 -0
  22. package/dist/commands/setupWorkspace.d.ts.map +1 -1
  23. package/dist/commands/setupWorkspace.js +78 -79
  24. package/dist/commands/ticketDoctor.d.ts +18 -3
  25. package/dist/commands/ticketDoctor.d.ts.map +1 -1
  26. package/dist/commands/ticketDoctor.js +105 -11
  27. package/dist/index.d.ts +6 -3
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +5 -2
  30. package/dist/lib/agentLaunch.d.ts +29 -0
  31. package/dist/lib/agentLaunch.d.ts.map +1 -0
  32. package/dist/lib/agentLaunch.js +53 -0
  33. package/dist/lib/boardSource.d.ts +41 -5
  34. package/dist/lib/boardSource.d.ts.map +1 -1
  35. package/dist/lib/boardSource.js +211 -70
  36. package/dist/lib/config.d.ts +59 -25
  37. package/dist/lib/config.d.ts.map +1 -1
  38. package/dist/lib/config.js +130 -22
  39. package/dist/lib/linearIssueStatus.d.ts +3 -1
  40. package/dist/lib/linearIssueStatus.d.ts.map +1 -1
  41. package/dist/lib/linearIssueStatus.js +0 -0
  42. package/dist/lib/runState.d.ts +46 -0
  43. package/dist/lib/runState.d.ts.map +1 -0
  44. package/dist/lib/runState.js +137 -0
  45. package/dist/lib/runStateCleanup.d.ts +4 -0
  46. package/dist/lib/runStateCleanup.d.ts.map +1 -0
  47. package/dist/lib/runStateCleanup.js +12 -0
  48. package/dist/lib/stagedLaunch.d.ts +32 -0
  49. package/dist/lib/stagedLaunch.d.ts.map +1 -0
  50. package/dist/lib/stagedLaunch.js +58 -0
  51. package/dist/lib/util.d.ts +0 -1
  52. package/dist/lib/util.d.ts.map +1 -1
  53. package/dist/lib/util.js +0 -4
  54. package/dist/lib/workspaces.d.ts +19 -1
  55. package/dist/lib/workspaces.d.ts.map +1 -1
  56. package/dist/lib/workspaces.js +29 -9
  57. package/dist/lib/worktrees.d.ts.map +1 -1
  58. package/dist/lib/worktrees.js +12 -4
  59. package/package.json +1 -1
@@ -173,11 +173,11 @@ function uniqueStrings(values) {
173
173
  function normalizeStatusName(value, fallback, path) {
174
174
  return normalizeOptionalString(value, path) ?? fallback;
175
175
  }
176
- function normalizeStatuses(user) {
177
- const todo = normalizeStatusName(user?.todo, DEFAULT_STATUSES.todo, "linear.statuses.todo");
178
- const inProgress = normalizeStatusName(user?.inProgress, DEFAULT_STATUSES.inProgress, "linear.statuses.inProgress");
179
- const done = normalizeStatusName(user?.done, DEFAULT_STATUSES.done, "linear.statuses.done");
180
- const terminal = normalizeOptionalStringArray(user?.terminal, "linear.statuses.terminal") ?? [];
176
+ function normalizeStatuses(user, path) {
177
+ const todo = normalizeStatusName(user?.todo, DEFAULT_STATUSES.todo, `${path}.todo`);
178
+ const inProgress = normalizeStatusName(user?.inProgress, DEFAULT_STATUSES.inProgress, `${path}.inProgress`);
179
+ const done = normalizeStatusName(user?.done, DEFAULT_STATUSES.done, `${path}.done`);
180
+ const terminal = normalizeOptionalStringArray(user?.terminal, `${path}.terminal`) ?? [];
181
181
  return {
182
182
  todo,
183
183
  inProgress,
@@ -337,12 +337,83 @@ function requireObject(value, path) {
337
337
  fail(`${path} must be an object (got ${JSON.stringify(value)})`);
338
338
  }
339
339
  }
340
+ function normalizeProject(value, index) {
341
+ const path = `linear.projects[${index}]`;
342
+ if (!isPlainObject(value)) {
343
+ fail(`${path} must be an object (got ${JSON.stringify(value)})`);
344
+ }
345
+ const { projectSlug, statuses } = value;
346
+ requireString(projectSlug, `${path}.projectSlug`);
347
+ const slugId = extractSlugId(projectSlug);
348
+ if (slugId === undefined) {
349
+ fail(`${path}.projectSlug must end with a 12-character hex slugId (got ${JSON.stringify(projectSlug)}). Copy the trailing segment from your Linear project URL, e.g. "ai-strategy-5152195762f3" from "https://linear.app/<workspace>/project/ai-strategy-5152195762f3".`);
350
+ }
351
+ if (statuses !== undefined && !isPlainObject(statuses)) {
352
+ fail(`${path}.statuses must be an object`);
353
+ }
354
+ return {
355
+ projectSlug,
356
+ slugId,
357
+ statuses: normalizeStatuses(statuses, `${path}.statuses`),
358
+ };
359
+ }
360
+ function failOnLegacyLinearShape(user) {
361
+ const { linear } = user;
362
+ if (!isPlainObject(linear)) {
363
+ return;
364
+ }
365
+ if (!Object.hasOwn(linear, "projectSlug") && !Object.hasOwn(linear, "statuses")) {
366
+ return;
367
+ }
368
+ const legacySlug = linear["projectSlug"];
369
+ const { projects } = linear;
370
+ const firstProject = Array.isArray(projects) ? projects[0] : undefined;
371
+ const migratedSlug = isPlainObject(firstProject) && typeof firstProject["projectSlug"] === "string"
372
+ ? firstProject["projectSlug"]
373
+ : undefined;
374
+ let slugLiteral = `"your-project-name-0123456789ab"`;
375
+ if (typeof legacySlug === "string") {
376
+ slugLiteral = JSON.stringify(legacySlug);
377
+ }
378
+ else if (migratedSlug !== undefined) {
379
+ slugLiteral = JSON.stringify(migratedSlug);
380
+ }
381
+ const statusesBlock = isPlainObject(linear["statuses"])
382
+ ? `, statuses: ${JSON.stringify(linear["statuses"])}`
383
+ : "";
384
+ fail([
385
+ "linear.projectSlug / linear.statuses are no longer supported — wrap them in linear.projects[].",
386
+ "Migrate by replacing your `linear` block with:",
387
+ ` linear: { projects: [{ projectSlug: ${slugLiteral}${statusesBlock} }] }`,
388
+ "See crew.config.example.ts for the multi-project shape.",
389
+ ].join("\n"));
390
+ }
391
+ function normalizeProjects(linear) {
392
+ const { projects } = linear;
393
+ if (!Array.isArray(projects)) {
394
+ fail("linear.projects must be a non-empty array");
395
+ }
396
+ if (projects.length === 0) {
397
+ fail("linear.projects must be a non-empty array");
398
+ }
399
+ const resolved = projects.map((entry, index) => normalizeProject(entry, index));
400
+ const seen = new Map();
401
+ resolved.forEach((project, index) => {
402
+ const previous = seen.get(project.slugId);
403
+ if (previous !== undefined) {
404
+ fail(`linear.projects[${index}].projectSlug duplicates the slugId "${project.slugId}" already used by linear.projects[${previous}]`);
405
+ }
406
+ seen.set(project.slugId, index);
407
+ });
408
+ return resolved;
409
+ }
340
410
  function applyDefaults(user) {
341
411
  // Guard the top-level shape before reading nested fields, so a
342
412
  // malformed runtime config produces a `groundcrew config: ...` error
343
413
  // instead of a raw `TypeError: Cannot read properties of undefined`.
414
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- `user` is loosely typed input from the loader; we narrow with requireObject below
415
+ failOnLegacyLinearShape(user);
344
416
  requireObject(user.linear, "linear");
345
- requireString(user.linear.projectSlug, "linear.projectSlug");
346
417
  requireObject(user.workspace, "workspace");
347
418
  if (isPlainObject(user.models) && Object.hasOwn(user.models, "isolation")) {
348
419
  fail("models.isolation is no longer supported: set `local.runner` ('safehouse' | 'sdx' | 'none' | 'auto') instead");
@@ -354,16 +425,10 @@ function applyDefaults(user) {
354
425
  if (userLocal !== undefined && !isPlainObject(userLocal)) {
355
426
  fail("local must be an object");
356
427
  }
357
- const slugId = extractSlugId(user.linear.projectSlug);
358
- if (slugId === undefined) {
359
- fail(`linear.projectSlug must end with a 12-character hex slugId (got ${JSON.stringify(user.linear.projectSlug)}). Copy the trailing segment from your Linear project URL, e.g. "ai-strategy-5152195762f3" from "https://linear.app/<workspace>/project/ai-strategy-5152195762f3".`);
360
- }
428
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- runtime fields validated by normalizeProjects below
429
+ const projects = normalizeProjects(user.linear);
361
430
  return {
362
- linear: {
363
- projectSlug: user.linear.projectSlug,
364
- slugId,
365
- statuses: normalizeStatuses(user.linear.statuses),
366
- },
431
+ linear: { projects },
367
432
  git: { ...DEFAULT_GIT, ...user.git },
368
433
  workspace: {
369
434
  projectDir: expandHome(user.workspace.projectDir),
@@ -394,13 +459,20 @@ function validatePromptPlaceholders(template) {
394
459
  }
395
460
  }
396
461
  function validate(config) {
397
- requireString(config.linear.projectSlug, "linear.projectSlug");
398
- requireString(config.linear.slugId, "linear.slugId");
399
- requireString(config.linear.statuses.todo, "linear.statuses.todo");
400
- requireString(config.linear.statuses.inProgress, "linear.statuses.inProgress");
401
- requireString(config.linear.statuses.done, "linear.statuses.done");
402
- config.linear.statuses.terminal.forEach((status, index) => {
403
- requireString(status, `linear.statuses.terminal[${index}]`);
462
+ /* v8 ignore next 3 @preserve -- normalizeProjects already enforces non-empty; belt-and-suspenders */
463
+ if (config.linear.projects.length === 0) {
464
+ fail("linear.projects must be a non-empty array");
465
+ }
466
+ config.linear.projects.forEach((project, index) => {
467
+ const path = `linear.projects[${index}]`;
468
+ requireString(project.projectSlug, `${path}.projectSlug`);
469
+ requireString(project.slugId, `${path}.slugId`);
470
+ requireString(project.statuses.todo, `${path}.statuses.todo`);
471
+ requireString(project.statuses.inProgress, `${path}.statuses.inProgress`);
472
+ requireString(project.statuses.done, `${path}.statuses.done`);
473
+ project.statuses.terminal.forEach((status, terminalIndex) => {
474
+ requireString(status, `${path}.statuses.terminal[${terminalIndex}]`);
475
+ });
404
476
  });
405
477
  requireString(config.git.remote, "git.remote");
406
478
  requireString(config.git.defaultBranch, "git.defaultBranch");
@@ -539,6 +611,42 @@ async function discoverUserConfig() {
539
611
  // terminating statement; it doesn't track `fail()`'s `never` return.
540
612
  throw new Error(`groundcrew config: no crew config found. Create crew.config.ts in your project root, or ${xdgConfigPath("groundcrew", "crew.config.ts")}, or set GROUNDCREW_CONFIG.`);
541
613
  }
614
+ /**
615
+ * Returns the resolved project the issue belongs to, or `undefined` when
616
+ * its slugId isn't in `linear.projects[]`. Callers in the dispatcher
617
+ * path expect a project to always exist (the board fetch only surfaces
618
+ * issues from configured projects); callers in the manual-ticket path
619
+ * (`setupWorkspace`, `ticketDoctor`) use this to detect off-config
620
+ * tickets and surface a clear error.
621
+ */
622
+ export function findProjectBySlugId(config, slugId) {
623
+ return config.linear.projects.find((project) => project.slugId === slugId);
624
+ }
625
+ // Memoize per-config so blocker checks (called per blocker per tick) don't
626
+ // rebuild the same Set on every call. The resolved config is frozen and
627
+ // long-lived, so the WeakMap key is stable.
628
+ const terminalUnionCache = new WeakMap();
629
+ /**
630
+ * Union of every terminal status name configured across all watched
631
+ * projects. Used for blocker terminal checks when the blocker belongs
632
+ * to a project we don't watch — matches today's single-project "is the
633
+ * status terminal under any configured project?" behavior so off-config
634
+ * blockers don't regress.
635
+ */
636
+ export function unionTerminalStatuses(config) {
637
+ const cached_ = terminalUnionCache.get(config);
638
+ if (cached_ !== undefined) {
639
+ return cached_;
640
+ }
641
+ const set = new Set();
642
+ for (const project of config.linear.projects) {
643
+ for (const status of project.statuses.terminal) {
644
+ set.add(status);
645
+ }
646
+ }
647
+ terminalUnionCache.set(config, set);
648
+ return set;
649
+ }
542
650
  let cached;
543
651
  export async function loadConfig() {
544
652
  if (cached) {
@@ -1,9 +1,11 @@
1
1
  import type { LinearClient } from "@linear/sdk";
2
- import type { ResolvedConfig } from "./config.ts";
2
+ import { type ResolvedConfig } from "./config.ts";
3
3
  interface LinearIssueReference {
4
4
  id: string;
5
5
  uuid: string;
6
6
  teamId: string;
7
+ /** SlugId of the issue's Linear project — selects which `inProgress` name to look up on the team's workflow. */
8
+ projectSlugId: string;
7
9
  }
8
10
  interface LinearIssueStatusUpdater {
9
11
  markInProgress(issue: LinearIssueReference): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"linearIssueStatus.d.ts","sourceRoot":"","sources":["../../src/lib/linearIssueStatus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,UAAU,oBAAoB;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,wBAAwB;IAChC,cAAc,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,2BAA2B,IAAI,IAAI,CAAC;CACrC;AAED,wBAAgB,8BAA8B,CAAC,UAAU,EAAE;IACzD,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;CACtB,GAAG,wBAAwB,CAgD3B"}
1
+ {"version":3,"file":"linearIssueStatus.d.ts","sourceRoot":"","sources":["../../src/lib/linearIssueStatus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAGvE,UAAU,oBAAoB;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,gHAAgH;IAChH,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,wBAAwB;IAChC,cAAc,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,2BAA2B,IAAI,IAAI,CAAC;CACrC;AAQD,wBAAgB,8BAA8B,CAAC,UAAU,EAAE;IACzD,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;CACtB,GAAG,wBAAwB,CA+D3B"}
Binary file
@@ -0,0 +1,46 @@
1
+ import type { ResolvedConfig } from "./config.ts";
2
+ export type RunLifecycleState = "running" | "interrupted" | "resumed" | "failed-to-launch";
3
+ export interface RunState {
4
+ ticket: string;
5
+ repository: string;
6
+ model: string;
7
+ worktreeDir: string;
8
+ branchName: string;
9
+ workspaceName: string;
10
+ state: RunLifecycleState;
11
+ createdAt: string;
12
+ updatedAt: string;
13
+ resumeCount: number;
14
+ reason?: string;
15
+ detail?: string;
16
+ }
17
+ export interface RunStateDraft {
18
+ ticket: string;
19
+ repository: string;
20
+ model: string;
21
+ worktreeDir: string;
22
+ branchName: string;
23
+ workspaceName: string;
24
+ state: RunLifecycleState;
25
+ reason?: string;
26
+ detail?: string;
27
+ resumeCount?: number;
28
+ }
29
+ export interface RecordRunStateInput {
30
+ config: ResolvedConfig;
31
+ state: RunStateDraft;
32
+ }
33
+ export interface UpdateRunStateInput {
34
+ config: ResolvedConfig;
35
+ ticket: string;
36
+ patch: Partial<Omit<RunState, "createdAt" | "ticket">> & {
37
+ state: RunLifecycleState;
38
+ };
39
+ }
40
+ export declare function runStateDirectory(config: Pick<ResolvedConfig, "logging">): string;
41
+ export declare function runStatePath(config: Pick<ResolvedConfig, "logging">, ticket: string): string;
42
+ export declare function readRunState(config: ResolvedConfig, ticket: string): RunState | undefined;
43
+ export declare function recordRunState(input: RecordRunStateInput): RunState;
44
+ export declare function updateRunState(input: UpdateRunStateInput): RunState | undefined;
45
+ export declare function removeRunState(config: ResolvedConfig, ticket: string): void;
46
+ //# sourceMappingURL=runState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runState.d.ts","sourceRoot":"","sources":["../../src/lib/runState.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,kBAAkB,CAAC;AAE3F,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,GAAG,QAAQ,CAAC,CAAC,GAAG;QACvD,KAAK,EAAE,iBAAiB,CAAC;KAC1B,CAAC;CACH;AAaD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,GAAG,MAAM,CAEjF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE5F;AA+ED,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAYzF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,CAmBnE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,QAAQ,GAAG,SAAS,CAc/E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAE3E"}
@@ -0,0 +1,137 @@
1
+ import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ const TICKET_RE = /^[a-z][\da-z]*-\d+$/;
4
+ const RUN_STATE_DIRECTORY_NAME = "runs";
5
+ function ticketKey(ticket) {
6
+ const normalized = ticket.toLowerCase();
7
+ if (!TICKET_RE.test(normalized)) {
8
+ throw new Error(`Invalid ticket "${ticket}": must be a plain ticket id`);
9
+ }
10
+ return normalized;
11
+ }
12
+ export function runStateDirectory(config) {
13
+ return resolve(dirname(config.logging.file), RUN_STATE_DIRECTORY_NAME);
14
+ }
15
+ export function runStatePath(config, ticket) {
16
+ return resolve(runStateDirectory(config), `${ticketKey(ticket)}.json`);
17
+ }
18
+ function nowIso() {
19
+ return new Date().toISOString();
20
+ }
21
+ function isPlainObject(value) {
22
+ return typeof value === "object" && value !== null && !Array.isArray(value);
23
+ }
24
+ function stringField(value, key) {
25
+ const field = value[key];
26
+ return typeof field === "string" && field.length > 0 ? field : undefined;
27
+ }
28
+ function isRunLifecycleState(value) {
29
+ return (value === "running" ||
30
+ value === "interrupted" ||
31
+ value === "resumed" ||
32
+ value === "failed-to-launch");
33
+ }
34
+ function parseRunState(value) {
35
+ if (!isPlainObject(value)) {
36
+ return undefined;
37
+ }
38
+ const ticket = stringField(value, "ticket");
39
+ const repository = stringField(value, "repository");
40
+ const model = stringField(value, "model");
41
+ const worktreeDir = stringField(value, "worktreeDir");
42
+ const branchName = stringField(value, "branchName");
43
+ const workspaceName = stringField(value, "workspaceName");
44
+ const { state, resumeCount } = value;
45
+ const createdAt = stringField(value, "createdAt");
46
+ const updatedAt = stringField(value, "updatedAt");
47
+ const reason = stringField(value, "reason");
48
+ const detail = stringField(value, "detail");
49
+ if (ticket === undefined ||
50
+ repository === undefined ||
51
+ model === undefined ||
52
+ worktreeDir === undefined ||
53
+ branchName === undefined ||
54
+ workspaceName === undefined ||
55
+ !isRunLifecycleState(state) ||
56
+ createdAt === undefined ||
57
+ updatedAt === undefined ||
58
+ typeof resumeCount !== "number" ||
59
+ !Number.isInteger(resumeCount) ||
60
+ resumeCount < 0) {
61
+ return undefined;
62
+ }
63
+ return {
64
+ ticket,
65
+ repository,
66
+ model,
67
+ worktreeDir,
68
+ branchName,
69
+ workspaceName,
70
+ state,
71
+ createdAt,
72
+ updatedAt,
73
+ resumeCount,
74
+ ...(reason === undefined ? {} : { reason }),
75
+ ...(detail === undefined ? {} : { detail }),
76
+ };
77
+ }
78
+ function writeState(config, state) {
79
+ const path = runStatePath(config, state.ticket);
80
+ mkdirSync(dirname(path), { recursive: true });
81
+ const tmpPath = `${path}.${process.pid}.tmp`;
82
+ writeFileSync(tmpPath, `${JSON.stringify(state, undefined, 2)}\n`, { mode: 0o600 });
83
+ renameSync(tmpPath, path);
84
+ }
85
+ export function readRunState(config, ticket) {
86
+ let raw;
87
+ try {
88
+ raw = readFileSync(runStatePath(config, ticket), "utf8");
89
+ }
90
+ catch {
91
+ return undefined;
92
+ }
93
+ try {
94
+ return parseRunState(JSON.parse(raw));
95
+ }
96
+ catch {
97
+ return undefined;
98
+ }
99
+ }
100
+ export function recordRunState(input) {
101
+ const existing = readRunState(input.config, input.state.ticket);
102
+ const timestamp = nowIso();
103
+ const state = {
104
+ ticket: ticketKey(input.state.ticket),
105
+ repository: input.state.repository,
106
+ model: input.state.model,
107
+ worktreeDir: input.state.worktreeDir,
108
+ branchName: input.state.branchName,
109
+ workspaceName: input.state.workspaceName,
110
+ state: input.state.state,
111
+ createdAt: existing?.createdAt ?? timestamp,
112
+ updatedAt: timestamp,
113
+ resumeCount: input.state.resumeCount ?? existing?.resumeCount ?? 0,
114
+ ...(input.state.reason === undefined ? {} : { reason: input.state.reason }),
115
+ ...(input.state.detail === undefined ? {} : { detail: input.state.detail }),
116
+ };
117
+ writeState(input.config, state);
118
+ return state;
119
+ }
120
+ export function updateRunState(input) {
121
+ const existing = readRunState(input.config, input.ticket);
122
+ if (existing === undefined) {
123
+ return undefined;
124
+ }
125
+ const state = {
126
+ ...existing,
127
+ ...input.patch,
128
+ ticket: existing.ticket,
129
+ createdAt: existing.createdAt,
130
+ updatedAt: nowIso(),
131
+ };
132
+ writeState(input.config, state);
133
+ return state;
134
+ }
135
+ export function removeRunState(config, ticket) {
136
+ rmSync(runStatePath(config, ticket), { force: true });
137
+ }
@@ -0,0 +1,4 @@
1
+ import type { ResolvedConfig } from "./config.ts";
2
+ import type { WorktreeEntry } from "./worktrees.ts";
3
+ export declare function recordCleanedUpRuns(config: ResolvedConfig, entries: readonly WorktreeEntry[]): void;
4
+ //# sourceMappingURL=runStateCleanup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runStateCleanup.d.ts","sourceRoot":"","sources":["../../src/lib/runStateCleanup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,SAAS,aAAa,EAAE,GAChC,IAAI,CAQN"}
@@ -0,0 +1,12 @@
1
+ import { removeRunState } from "./runState.js";
2
+ import { errorMessage, log } from "./util.js";
3
+ export function recordCleanedUpRuns(config, entries) {
4
+ for (const entry of entries) {
5
+ try {
6
+ removeRunState(config, entry.ticket);
7
+ }
8
+ catch (error) {
9
+ log(`Run state cleanup failed for ${entry.ticket}: ${errorMessage(error)}`);
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,32 @@
1
+ import { type ResolvedConfig } from "./config.ts";
2
+ export interface StagedPrompt {
3
+ directory: string;
4
+ file: string;
5
+ }
6
+ interface PromptTemplateVariables {
7
+ ticket: string;
8
+ worktree: string;
9
+ title: string;
10
+ description: string;
11
+ }
12
+ export declare function stagePromptText(input: {
13
+ prefix: string;
14
+ ticket: string;
15
+ text: string;
16
+ }): StagedPrompt;
17
+ export declare function stagePromptFromTemplate(input: {
18
+ config: ResolvedConfig;
19
+ prefix: string;
20
+ ticket: string;
21
+ variables: PromptTemplateVariables;
22
+ }): StagedPrompt;
23
+ /**
24
+ * Stage a `KEY='value'` env file for any populated build-time secret so
25
+ * the launch command can source it. Returns `undefined` when groundcrew
26
+ * has nothing to forward, leaving the launch command unchanged.
27
+ */
28
+ export declare function stageBuildSecrets(promptDir: string): string | undefined;
29
+ export declare function stageWorkspaceLaunchCommand(promptDir: string, command: string): string;
30
+ export declare function removeStagedPrompt(directory: string): void;
31
+ export {};
32
+ //# sourceMappingURL=stagedLaunch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stagedLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/stagedLaunch.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAItE,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,uBAAuB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAUD,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,YAAY,CAKf;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC7C,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,uBAAuB,CAAC;CACpC,GAAG,YAAY,CAMf;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAevE;AAQD,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtF;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE1D"}
@@ -0,0 +1,58 @@
1
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { BUILD_SECRET_NAMES } from "./config.js";
5
+ import { shellSingleQuote } from "./launchCommand.js";
6
+ import { readEnvironmentVariable } from "./util.js";
7
+ function renderPromptTemplate(template, variables) {
8
+ return template
9
+ .replaceAll("{{ticket}}", variables.ticket)
10
+ .replaceAll("{{worktree}}", variables.worktree)
11
+ .replaceAll("{{title}}", variables.title)
12
+ .replaceAll("{{description}}", variables.description);
13
+ }
14
+ export function stagePromptText(input) {
15
+ const promptDir = mkdtempSync(join(tmpdir(), `${input.prefix}-${input.ticket}-`));
16
+ const promptFile = join(promptDir, "prompt.txt");
17
+ writeFileSync(promptFile, input.text);
18
+ return { directory: promptDir, file: promptFile };
19
+ }
20
+ export function stagePromptFromTemplate(input) {
21
+ return stagePromptText({
22
+ prefix: input.prefix,
23
+ ticket: input.ticket,
24
+ text: renderPromptTemplate(input.config.prompts.initial, input.variables),
25
+ });
26
+ }
27
+ /**
28
+ * Stage a `KEY='value'` env file for any populated build-time secret so
29
+ * the launch command can source it. Returns `undefined` when groundcrew
30
+ * has nothing to forward, leaving the launch command unchanged.
31
+ */
32
+ export function stageBuildSecrets(promptDir) {
33
+ const lines = [];
34
+ for (const name of BUILD_SECRET_NAMES) {
35
+ const value = readEnvironmentVariable(name);
36
+ if (value === undefined || value.length === 0) {
37
+ continue;
38
+ }
39
+ lines.push(`${name}=${shellSingleQuote(value)}`);
40
+ }
41
+ if (lines.length === 0) {
42
+ return undefined;
43
+ }
44
+ const secretsFile = join(promptDir, "secrets.env");
45
+ writeFileSync(secretsFile, `${lines.join("\n")}\n`, { mode: 0o600 });
46
+ return secretsFile;
47
+ }
48
+ function stageLaunchScript(promptDir, command) {
49
+ const launcherFile = join(promptDir, "launch.sh");
50
+ writeFileSync(launcherFile, `#!/usr/bin/env bash\n${command}\n`, { mode: 0o700 });
51
+ return launcherFile;
52
+ }
53
+ export function stageWorkspaceLaunchCommand(promptDir, command) {
54
+ return `bash ${shellSingleQuote(stageLaunchScript(promptDir, command))}`;
55
+ }
56
+ export function removeStagedPrompt(directory) {
57
+ rmSync(directory, { recursive: true, force: true });
58
+ }
@@ -2,7 +2,6 @@ import { LinearClient } from "@linear/sdk";
2
2
  export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
3
3
  export declare function writeOutput(message?: string): void;
4
4
  export declare function writeError(message: string): void;
5
- export declare function clearOutput(): void;
6
5
  export declare function setLogFile(path: string | undefined): void;
7
6
  export declare function log(message: string): void;
8
7
  type LogEventFieldValue = boolean | number | string | readonly string[] | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/lib/util.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E;AAED,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAIlD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGhD;AAED,wBAAgB,WAAW,IAAI,IAAI,CAGlC;AAOD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAEzD;AAkBD,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAKzC;AAED,KAAK,kBAAkB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;AAUpF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAWxF;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxE;AAED,QAAA,MAAM,sBAAsB,YAAI,2BAA2B,EAAE,gBAAgB,CAAU,CAAC;AAExF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzE,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAED,wBAAgB,mBAAmB,IAAI,oBAAoB,GAAG,SAAS,CAQtE;AAED,wBAAgB,eAAe,IAAI,YAAY,CAQ9C;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,YAAY,GAAG,MAAM,YAAY,CAMhF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAMzF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAcnD"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/lib/util.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E;AAED,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAIlD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGhD;AAOD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAEzD;AAkBD,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAKzC;AAED,KAAK,kBAAkB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;AAUpF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAWxF;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxE;AAED,QAAA,MAAM,sBAAsB,YAAI,2BAA2B,EAAE,gBAAgB,CAAU,CAAC;AAExF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzE,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAED,wBAAgB,mBAAmB,IAAI,oBAAoB,GAAG,SAAS,CAQtE;AAED,wBAAgB,eAAe,IAAI,YAAY,CAQ9C;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,YAAY,GAAG,MAAM,YAAY,CAMhF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAMzF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAcnD"}
package/dist/lib/util.js CHANGED
@@ -31,10 +31,6 @@ export function writeError(message) {
31
31
  // oxlint-disable-next-line no-console -- Centralized CLI stderr writer.
32
32
  console.error(message);
33
33
  }
34
- export function clearOutput() {
35
- // oxlint-disable-next-line no-console -- Centralized CLI screen clear.
36
- console.clear();
37
- }
38
34
  // Module-scoped sink for tee-ing log()/logEvent() to disk. Unset by default
39
35
  // so tests don't write to the host filesystem; the CLI arms it after
40
36
  // loadConfig() resolves `logging.file`.
@@ -41,6 +41,22 @@ export type WorkspaceProbe = {
41
41
  kind: "unavailable";
42
42
  error?: unknown;
43
43
  };
44
+ export type WorkspaceInterruptResult = {
45
+ kind: "interrupted";
46
+ } | {
47
+ kind: "missing";
48
+ } | {
49
+ kind: "unavailable";
50
+ error?: unknown;
51
+ };
52
+ export type WorkspaceCloseResult = {
53
+ kind: "closed";
54
+ } | {
55
+ kind: "missing";
56
+ } | {
57
+ kind: "unavailable";
58
+ error?: unknown;
59
+ };
44
60
  export interface WorkspaceResolution {
45
61
  requested: WorkspaceKindSetting;
46
62
  resolved: WorkspaceKind;
@@ -53,10 +69,12 @@ interface ResolveArguments {
53
69
  }
54
70
  export declare function resolveWorkspaceKind(arguments_: ResolveArguments): WorkspaceResolution;
55
71
  declare function probeWorkspaces(config: ResolvedConfig, signal?: AbortSignal): Promise<WorkspaceProbe>;
72
+ declare function interruptWorkspace(config: ResolvedConfig, name: string, signal?: AbortSignal): Promise<WorkspaceInterruptResult>;
56
73
  export declare const workspaces: {
57
74
  open(config: ResolvedConfig, spec: OpenSpec, signal?: AbortSignal): Promise<void>;
58
75
  probe: typeof probeWorkspaces;
59
- close(config: ResolvedConfig, name: string, signal?: AbortSignal): Promise<void>;
76
+ close(config: ResolvedConfig, name: string, signal?: AbortSignal): Promise<WorkspaceCloseResult>;
77
+ interrupt: typeof interruptWorkspace;
60
78
  accessHint(config: ResolvedConfig, name: string, signal?: AbortSignal): Promise<WorkspaceAccessHint | undefined>;
61
79
  };
62
80
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/lib/workspaces.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAI1E,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,MAAM,WAAW,SAAS;IACxB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAsU7C,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,oBAAoB,CAAC;IAChC,QAAQ,EAAE,aAAa,CAAC;IACxB,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,gBAAgB;IACxB,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,gBAAgB,GAAG,mBAAmB,CAUtF;AA+ND,iBAAe,eAAe,CAC5B,MAAM,EAAE,cAAc,EACtB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CAezB;AAED,eAAO,MAAM,UAAU;IACf,IAAI,SAAS,cAAc,QAAQ,QAAQ,WAAW,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvF,KAAK;IACC,KAAK,SAAS,cAAc,QAAQ,MAAM,WAAW,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhF,UAAU,SACN,cAAc,QAChB,MAAM,WACH,WAAW,GACnB,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;CAI5C,CAAC"}
1
+ {"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/lib/workspaces.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAI1E,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,MAAM,WAAW,SAAS;IACxB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAE7C,MAAM,MAAM,wBAAwB,GAChC;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAE7C,MAAM,MAAM,oBAAoB,GAC5B;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAwU7C,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,oBAAoB,CAAC;IAChC,QAAQ,EAAE,aAAa,CAAC;IACxB,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,gBAAgB;IACxB,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,gBAAgB,GAAG,mBAAmB,CAUtF;AAgOD,iBAAe,eAAe,CAC5B,MAAM,EAAE,cAAc,EACtB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CAezB;AAED,iBAAe,kBAAkB,CAC/B,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,wBAAwB,CAAC,CAenC;AAED,eAAO,MAAM,UAAU;IACf,IAAI,SAAS,cAAc,QAAQ,QAAQ,WAAW,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvF,KAAK;IACC,KAAK,SACD,cAAc,QAChB,MAAM,WACH,WAAW,GACnB,OAAO,CAAC,oBAAoB,CAAC;IAIhC,SAAS;IACH,UAAU,SACN,cAAc,QAChB,MAAM,WACH,WAAW,GACnB,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;CAI5C,CAAC"}