@oratis/lisa 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/dist/autostart/install.d.ts +28 -0
  2. package/dist/autostart/install.d.ts.map +1 -0
  3. package/dist/autostart/install.js +180 -0
  4. package/dist/autostart/install.js.map +1 -0
  5. package/dist/cli.js +49 -1
  6. package/dist/cli.js.map +1 -1
  7. package/dist/heartbeat/install.d.ts.map +1 -1
  8. package/dist/heartbeat/install.js +1 -43
  9. package/dist/heartbeat/install.js.map +1 -1
  10. package/dist/heartbeat/runner.d.ts.map +1 -1
  11. package/dist/heartbeat/runner.js +11 -0
  12. package/dist/heartbeat/runner.js.map +1 -1
  13. package/dist/integrations/aider/observer.d.ts +48 -0
  14. package/dist/integrations/aider/observer.d.ts.map +1 -0
  15. package/dist/integrations/aider/observer.js +196 -0
  16. package/dist/integrations/aider/observer.js.map +1 -0
  17. package/dist/integrations/comparisons.d.ts +23 -0
  18. package/dist/integrations/comparisons.d.ts.map +1 -0
  19. package/dist/integrations/comparisons.js +47 -0
  20. package/dist/integrations/comparisons.js.map +1 -0
  21. package/dist/integrations/github-pr/observer.d.ts +85 -0
  22. package/dist/integrations/github-pr/observer.d.ts.map +1 -0
  23. package/dist/integrations/github-pr/observer.js +261 -0
  24. package/dist/integrations/github-pr/observer.js.map +1 -0
  25. package/dist/integrations/hub.d.ts.map +1 -1
  26. package/dist/integrations/hub.js +10 -0
  27. package/dist/integrations/hub.js.map +1 -1
  28. package/dist/integrations/opencode/observer.d.ts +74 -0
  29. package/dist/integrations/opencode/observer.d.ts.map +1 -0
  30. package/dist/integrations/opencode/observer.js +207 -0
  31. package/dist/integrations/opencode/observer.js.map +1 -0
  32. package/dist/integrations/registry.d.ts.map +1 -1
  33. package/dist/integrations/registry.js +3 -1
  34. package/dist/integrations/registry.js.map +1 -1
  35. package/dist/integrations/scheduled-dispatch.d.ts +42 -0
  36. package/dist/integrations/scheduled-dispatch.d.ts.map +1 -0
  37. package/dist/integrations/scheduled-dispatch.js +140 -0
  38. package/dist/integrations/scheduled-dispatch.js.map +1 -0
  39. package/dist/launchd.d.ts +18 -0
  40. package/dist/launchd.d.ts.map +1 -0
  41. package/dist/launchd.js +60 -0
  42. package/dist/launchd.js.map +1 -0
  43. package/dist/orchestrator/journal.d.ts +46 -0
  44. package/dist/orchestrator/journal.d.ts.map +1 -0
  45. package/dist/orchestrator/journal.js +82 -0
  46. package/dist/orchestrator/journal.js.map +1 -0
  47. package/dist/orchestrator/recap.d.ts +38 -0
  48. package/dist/orchestrator/recap.d.ts.map +1 -0
  49. package/dist/orchestrator/recap.js +134 -0
  50. package/dist/orchestrator/recap.js.map +1 -0
  51. package/dist/screen_advisor/engine.d.ts +79 -0
  52. package/dist/screen_advisor/engine.d.ts.map +1 -0
  53. package/dist/screen_advisor/engine.js +128 -0
  54. package/dist/screen_advisor/engine.js.map +1 -0
  55. package/dist/soul/lock.d.ts +7 -4
  56. package/dist/soul/lock.d.ts.map +1 -1
  57. package/dist/soul/lock.js +33 -9
  58. package/dist/soul/lock.js.map +1 -1
  59. package/dist/tools/agent_recap.d.ts +21 -0
  60. package/dist/tools/agent_recap.d.ts.map +1 -0
  61. package/dist/tools/agent_recap.js +43 -0
  62. package/dist/tools/agent_recap.js.map +1 -0
  63. package/dist/tools/compare_agents.d.ts +16 -0
  64. package/dist/tools/compare_agents.d.ts.map +1 -0
  65. package/dist/tools/compare_agents.js +145 -0
  66. package/dist/tools/compare_agents.js.map +1 -0
  67. package/dist/tools/dispatch_agent.d.ts +13 -0
  68. package/dist/tools/dispatch_agent.d.ts.map +1 -1
  69. package/dist/tools/dispatch_agent.js +46 -46
  70. package/dist/tools/dispatch_agent.js.map +1 -1
  71. package/dist/tools/exec-util.d.ts +18 -0
  72. package/dist/tools/exec-util.d.ts.map +1 -0
  73. package/dist/tools/exec-util.js +72 -0
  74. package/dist/tools/exec-util.js.map +1 -0
  75. package/dist/tools/inspect_agent.d.ts +21 -0
  76. package/dist/tools/inspect_agent.d.ts.map +1 -0
  77. package/dist/tools/inspect_agent.js +87 -0
  78. package/dist/tools/inspect_agent.js.map +1 -0
  79. package/dist/tools/list_agents.d.ts +21 -0
  80. package/dist/tools/list_agents.d.ts.map +1 -0
  81. package/dist/tools/list_agents.js +88 -0
  82. package/dist/tools/list_agents.js.map +1 -0
  83. package/dist/tools/pr_status.d.ts +31 -0
  84. package/dist/tools/pr_status.d.ts.map +1 -0
  85. package/dist/tools/pr_status.js +90 -0
  86. package/dist/tools/pr_status.js.map +1 -0
  87. package/dist/tools/registry.d.ts.map +1 -1
  88. package/dist/tools/registry.js +19 -0
  89. package/dist/tools/registry.js.map +1 -1
  90. package/dist/tools/repo_digest.d.ts +33 -0
  91. package/dist/tools/repo_digest.d.ts.map +1 -0
  92. package/dist/tools/repo_digest.js +113 -0
  93. package/dist/tools/repo_digest.js.map +1 -0
  94. package/dist/tools/review_diff.d.ts +28 -0
  95. package/dist/tools/review_diff.d.ts.map +1 -0
  96. package/dist/tools/review_diff.js +68 -0
  97. package/dist/tools/review_diff.js.map +1 -0
  98. package/dist/tools/run_checks.d.ts +15 -0
  99. package/dist/tools/run_checks.d.ts.map +1 -0
  100. package/dist/tools/run_checks.js +117 -0
  101. package/dist/tools/run_checks.js.map +1 -0
  102. package/dist/tools/scheduled_dispatch.d.ts +27 -0
  103. package/dist/tools/scheduled_dispatch.d.ts.map +1 -0
  104. package/dist/tools/scheduled_dispatch.js +57 -0
  105. package/dist/tools/scheduled_dispatch.js.map +1 -0
  106. package/dist/web/island.d.ts +1 -1
  107. package/dist/web/island.d.ts.map +1 -1
  108. package/dist/web/island.js +67 -4
  109. package/dist/web/island.js.map +1 -1
  110. package/dist/web/lisa-html.d.ts +1 -1
  111. package/dist/web/lisa-html.d.ts.map +1 -1
  112. package/dist/web/lisa-html.js +23 -2
  113. package/dist/web/lisa-html.js.map +1 -1
  114. package/dist/web/server.d.ts.map +1 -1
  115. package/dist/web/server.js +136 -0
  116. package/dist/web/server.js.map +1 -1
  117. package/package.json +1 -1
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Screen advisor — periodic, opt-in screen-aware coaching.
3
+ *
4
+ * When enabled, the server captures a full-screen screenshot every N minutes,
5
+ * asks the model for the single best next coding step grounded in what's on
6
+ * screen, and pushes it to the Lisa Island as a suggestion card. Clicking it
7
+ * prefills the suggestion into the chat composer so the user can confirm before
8
+ * any coding agent is dispatched.
9
+ *
10
+ * PRIVACY (this is a sensitive capability — treat it as such):
11
+ * - DISABLED by default. Nothing is captured until the user turns it on in
12
+ * Settings.
13
+ * - The screenshot only leaves the machine for the single analysis call to
14
+ * the model the user already uses; the image is never persisted by LISA
15
+ * (capture writes a temp PNG that the capture layer deletes), and only the
16
+ * short text suggestion is kept in memory.
17
+ * - The suggestion never auto-runs anything: clicking it only prefills chat.
18
+ *
19
+ * This module is provider-agnostic and side-effect-light so the parsing and
20
+ * config logic can be unit-tested without a real LLM or a real screen.
21
+ */
22
+ export interface ScreenAdvisorConfig {
23
+ /** Master switch. Off by default — capture only happens when true. */
24
+ enabled: boolean;
25
+ /** Minutes between captures. Clamped to [MIN, MAX]. */
26
+ intervalMinutes: number;
27
+ }
28
+ export declare const DEFAULT_SCREEN_ADVISOR_CONFIG: ScreenAdvisorConfig;
29
+ export declare const MIN_INTERVAL_MINUTES = 2;
30
+ export declare const MAX_INTERVAL_MINUTES = 240;
31
+ export declare const SCREEN_ADVISOR_CONFIG_PATH: string;
32
+ /** Coerce arbitrary input into a valid config (clamped interval, boolean enabled). */
33
+ export declare function normalizeConfig(raw: Partial<ScreenAdvisorConfig> | null | undefined): ScreenAdvisorConfig;
34
+ export declare function loadScreenAdvisorConfig(p?: string): Promise<ScreenAdvisorConfig>;
35
+ export declare function saveScreenAdvisorConfig(cfg: ScreenAdvisorConfig, p?: string): Promise<ScreenAdvisorConfig>;
36
+ /** A single screen-grounded next-step suggestion. */
37
+ export interface ScreenSuggestion {
38
+ /** ≤8-word imperative headline. */
39
+ title: string;
40
+ /** 1–2 sentences citing what was on screen. */
41
+ rationale: string;
42
+ /** Self-contained prompt a coding agent could act on. */
43
+ task: string;
44
+ /** ISO timestamp, stamped by the caller. */
45
+ at: string;
46
+ }
47
+ export declare const SCREEN_ADVISOR_SYSTEM = "You are Lisa's screen advisor. You are shown ONE screenshot of the user's screen. Suggest the single highest-value next CODING step, grounded in what is actually visible \u2014 an error, a failing test, a TODO/FIXME, an open file, a diff, a PR, a design. Be specific and actionable: prefer something a coding agent could pick up and finish.\n\nRespond with ONLY a compact JSON object \u2014 no prose, no markdown, no code fences:\n{\"title\":\"<=8-word imperative headline\",\"rationale\":\"1-2 sentences citing what you saw\",\"task\":\"a self-contained prompt for a coding agent: exactly what to do, and in which file/dir if visible\"}\n\nIf the screen shows nothing actionable for coding (a video, a chat, the desktop, unrelated browsing), respond with exactly: {\"skip\":true}";
48
+ /**
49
+ * Tolerantly parse the model's reply into a suggestion. Strips code fences,
50
+ * extracts the first JSON object, and returns null for {"skip":true} or any
51
+ * malformed / incomplete reply (so the loop simply surfaces nothing).
52
+ */
53
+ export declare function parseSuggestion(text: string | null | undefined): Omit<ScreenSuggestion, "at"> | null;
54
+ /** Minimal provider surface the analyzer needs (keeps the engine testable). */
55
+ export interface SuggestionProvider {
56
+ runTurn(opts: {
57
+ systemPrompt: string;
58
+ messages: Array<{
59
+ role: "user" | "assistant";
60
+ content: unknown;
61
+ }>;
62
+ tools: unknown[];
63
+ model: string;
64
+ maxTokens?: number;
65
+ }): Promise<{
66
+ content: Array<{
67
+ type: string;
68
+ text?: string;
69
+ }>;
70
+ }>;
71
+ }
72
+ /** Run one screenshot through the model and parse the suggestion (or null). */
73
+ export declare function analyzeScreenshot(opts: {
74
+ provider: SuggestionProvider;
75
+ model: string;
76
+ imageBase64: string;
77
+ mediaType?: string;
78
+ }): Promise<Omit<ScreenSuggestion, "at"> | null>;
79
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/screen_advisor/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,MAAM,WAAW,mBAAmB;IAClC,sEAAsE;IACtE,OAAO,EAAE,OAAO,CAAC;IACjB,uDAAuD;IACvD,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,6BAA6B,EAAE,mBAG3C,CAAC;AAEF,eAAO,MAAM,oBAAoB,IAAI,CAAC;AACtC,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC,eAAO,MAAM,0BAA0B,QAA8C,CAAC;AAEtF,sFAAsF;AACtF,wBAAgB,eAAe,CAC7B,GAAG,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,IAAI,GAAG,SAAS,GACnD,mBAAmB,CAQrB;AAED,wBAAsB,uBAAuB,CAC3C,CAAC,GAAE,MAAmC,GACrC,OAAO,CAAC,mBAAmB,CAAC,CAO9B;AAED,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,mBAAmB,EACxB,CAAC,GAAE,MAAmC,GACrC,OAAO,CAAC,mBAAmB,CAAC,CAK9B;AAED,qDAAqD;AACrD,MAAM,WAAW,gBAAgB;IAC/B,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,eAAO,MAAM,qBAAqB,ixBAKwG,CAAC;AAE3I;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,GAAG,IAAI,CAwBpG;AAED,+EAA+E;AAC/E,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,IAAI,EAAE;QACZ,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;YAAC,OAAO,EAAE,OAAO,CAAA;SAAE,CAAC,CAAC;QAClE,KAAK,EAAE,OAAO,EAAE,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAC;CAClE;AAED,+EAA+E;AAC/E,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,CAyB/C"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Screen advisor — periodic, opt-in screen-aware coaching.
3
+ *
4
+ * When enabled, the server captures a full-screen screenshot every N minutes,
5
+ * asks the model for the single best next coding step grounded in what's on
6
+ * screen, and pushes it to the Lisa Island as a suggestion card. Clicking it
7
+ * prefills the suggestion into the chat composer so the user can confirm before
8
+ * any coding agent is dispatched.
9
+ *
10
+ * PRIVACY (this is a sensitive capability — treat it as such):
11
+ * - DISABLED by default. Nothing is captured until the user turns it on in
12
+ * Settings.
13
+ * - The screenshot only leaves the machine for the single analysis call to
14
+ * the model the user already uses; the image is never persisted by LISA
15
+ * (capture writes a temp PNG that the capture layer deletes), and only the
16
+ * short text suggestion is kept in memory.
17
+ * - The suggestion never auto-runs anything: clicking it only prefills chat.
18
+ *
19
+ * This module is provider-agnostic and side-effect-light so the parsing and
20
+ * config logic can be unit-tested without a real LLM or a real screen.
21
+ */
22
+ import fsp from "node:fs/promises";
23
+ import path from "node:path";
24
+ import { LISA_HOME } from "../paths.js";
25
+ export const DEFAULT_SCREEN_ADVISOR_CONFIG = {
26
+ enabled: false,
27
+ intervalMinutes: 10,
28
+ };
29
+ export const MIN_INTERVAL_MINUTES = 2;
30
+ export const MAX_INTERVAL_MINUTES = 240;
31
+ export const SCREEN_ADVISOR_CONFIG_PATH = path.join(LISA_HOME, "screen-advisor.json");
32
+ /** Coerce arbitrary input into a valid config (clamped interval, boolean enabled). */
33
+ export function normalizeConfig(raw) {
34
+ const enabled = raw?.enabled === true;
35
+ let n = Number(raw?.intervalMinutes);
36
+ if (!Number.isFinite(n))
37
+ n = DEFAULT_SCREEN_ADVISOR_CONFIG.intervalMinutes;
38
+ n = Math.round(n);
39
+ if (n < MIN_INTERVAL_MINUTES)
40
+ n = MIN_INTERVAL_MINUTES;
41
+ if (n > MAX_INTERVAL_MINUTES)
42
+ n = MAX_INTERVAL_MINUTES;
43
+ return { enabled, intervalMinutes: n };
44
+ }
45
+ export async function loadScreenAdvisorConfig(p = SCREEN_ADVISOR_CONFIG_PATH) {
46
+ try {
47
+ const raw = JSON.parse(await fsp.readFile(p, "utf8"));
48
+ return normalizeConfig(raw);
49
+ }
50
+ catch {
51
+ return { ...DEFAULT_SCREEN_ADVISOR_CONFIG };
52
+ }
53
+ }
54
+ export async function saveScreenAdvisorConfig(cfg, p = SCREEN_ADVISOR_CONFIG_PATH) {
55
+ const norm = normalizeConfig(cfg);
56
+ await fsp.mkdir(path.dirname(p), { recursive: true });
57
+ await fsp.writeFile(p, JSON.stringify(norm, null, 2));
58
+ return norm;
59
+ }
60
+ export const SCREEN_ADVISOR_SYSTEM = `You are Lisa's screen advisor. You are shown ONE screenshot of the user's screen. Suggest the single highest-value next CODING step, grounded in what is actually visible — an error, a failing test, a TODO/FIXME, an open file, a diff, a PR, a design. Be specific and actionable: prefer something a coding agent could pick up and finish.
61
+
62
+ Respond with ONLY a compact JSON object — no prose, no markdown, no code fences:
63
+ {"title":"<=8-word imperative headline","rationale":"1-2 sentences citing what you saw","task":"a self-contained prompt for a coding agent: exactly what to do, and in which file/dir if visible"}
64
+
65
+ If the screen shows nothing actionable for coding (a video, a chat, the desktop, unrelated browsing), respond with exactly: {"skip":true}`;
66
+ /**
67
+ * Tolerantly parse the model's reply into a suggestion. Strips code fences,
68
+ * extracts the first JSON object, and returns null for {"skip":true} or any
69
+ * malformed / incomplete reply (so the loop simply surfaces nothing).
70
+ */
71
+ export function parseSuggestion(text) {
72
+ if (!text)
73
+ return null;
74
+ let s = text.trim();
75
+ // Drop a leading ```json / ``` fence and a trailing fence if present.
76
+ s = s.replace(/^```[a-zA-Z]*\s*/, "").replace(/\s*```$/, "").trim();
77
+ const a = s.indexOf("{");
78
+ const b = s.lastIndexOf("}");
79
+ if (a < 0 || b <= a)
80
+ return null;
81
+ let obj;
82
+ try {
83
+ obj = JSON.parse(s.slice(a, b + 1));
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ if (obj.skip === true)
89
+ return null;
90
+ const title = typeof obj.title === "string" ? obj.title.trim() : "";
91
+ const task = typeof obj.task === "string" ? obj.task.trim() : "";
92
+ if (!title || !task)
93
+ return null;
94
+ const rationale = typeof obj.rationale === "string" ? obj.rationale.trim() : "";
95
+ return {
96
+ title: title.slice(0, 120),
97
+ rationale: rationale.slice(0, 400),
98
+ task: task.slice(0, 2000),
99
+ };
100
+ }
101
+ /** Run one screenshot through the model and parse the suggestion (or null). */
102
+ export async function analyzeScreenshot(opts) {
103
+ const result = await opts.provider.runTurn({
104
+ systemPrompt: SCREEN_ADVISOR_SYSTEM,
105
+ messages: [
106
+ {
107
+ role: "user",
108
+ content: [
109
+ { type: "text", text: "Here is my screen right now. What is the single best next coding step?" },
110
+ {
111
+ type: "image",
112
+ source: {
113
+ type: "base64",
114
+ media_type: opts.mediaType ?? "image/png",
115
+ data: opts.imageBase64,
116
+ },
117
+ },
118
+ ],
119
+ },
120
+ ],
121
+ tools: [],
122
+ model: opts.model,
123
+ maxTokens: 600,
124
+ });
125
+ const textBlock = result.content.find((b) => b.type === "text");
126
+ return parseSuggestion(textBlock?.text ?? "");
127
+ }
128
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/screen_advisor/engine.ts"],"names":[],"mappings":"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"}
@@ -9,10 +9,13 @@
9
9
  * - lose a desire-progress append (read-modify-write race), or
10
10
  * - collide on .git/index.lock (one commit fails).
11
11
  *
12
- * withFileLock implements a portable advisory mutex via exclusive file
13
- * creation (`open(..., "wx")` succeeds only if the file doesn't exist). It
14
- * self-heals from a crashed holder via a staleness timeout + a best-effort
15
- * liveness check on the recorded pid.
12
+ * withFileLock implements a portable advisory mutex by writing the lock body to
13
+ * a private temp file and then `link()`-ing it into place link() fails if the
14
+ * target exists, so the lock is created exclusively AND already holds its full
15
+ * content (no window where a contender can read a half-written file and mistake
16
+ * it for stale). It self-heals from a crashed holder via a staleness timeout +
17
+ * a best-effort liveness check on the recorded pid, stealing atomically by
18
+ * renaming the stale file away so racing contenders can't clobber each other.
16
19
  */
17
20
  export interface FileLockOpts {
18
21
  /** Treat a held lock as abandoned after this many ms (crashed holder). */
@@ -1 +1 @@
1
- {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/soul/lock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,MAAM,WAAW,YAAY;IAC3B,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAoCD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAClC,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,CAAC,CAAC,CAmCZ;AAED,0CAA0C;AAC1C,eAAO,MAAM,cAAc,QAAqC,CAAC;AAEjE;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,CAAC,CAAC,CAErF"}
1
+ {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/soul/lock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAOH,MAAM,WAAW,YAAY;IAC3B,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAyCD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAClC,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,CAAC,CAAC,CAkDZ;AAED,0CAA0C;AAC1C,eAAO,MAAM,cAAc,QAAqC,CAAC;AAEjE;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,CAAC,CAAC,CAErF"}
package/dist/soul/lock.js CHANGED
@@ -9,10 +9,13 @@
9
9
  * - lose a desire-progress append (read-modify-write race), or
10
10
  * - collide on .git/index.lock (one commit fails).
11
11
  *
12
- * withFileLock implements a portable advisory mutex via exclusive file
13
- * creation (`open(..., "wx")` succeeds only if the file doesn't exist). It
14
- * self-heals from a crashed holder via a staleness timeout + a best-effort
15
- * liveness check on the recorded pid.
12
+ * withFileLock implements a portable advisory mutex by writing the lock body to
13
+ * a private temp file and then `link()`-ing it into place link() fails if the
14
+ * target exists, so the lock is created exclusively AND already holds its full
15
+ * content (no window where a contender can read a half-written file and mistake
16
+ * it for stale). It self-heals from a crashed holder via a staleness timeout +
17
+ * a best-effort liveness check on the recorded pid, stealing atomically by
18
+ * renaming the stale file away so racing contenders can't clobber each other.
16
19
  */
17
20
  import fsp from "node:fs/promises";
18
21
  import path from "node:path";
@@ -28,7 +31,13 @@ async function isStale(lockPath, staleMs) {
28
31
  try {
29
32
  body = JSON.parse(await fsp.readFile(lockPath, "utf8"));
30
33
  }
31
- catch {
34
+ catch (e) {
35
+ // The file vanished between the failed create and our read — the holder
36
+ // released it. There's nothing to steal; let the caller retry the create
37
+ // race. (Treating this as "stale" would race a concurrent acquirer into an
38
+ // unconditional rm that could delete a *different* holder's fresh lock.)
39
+ if (e.code === "ENOENT")
40
+ return false;
32
41
  // Unreadable / malformed lock file → treat as stale so we can recover.
33
42
  return true;
34
43
  }
@@ -58,22 +67,37 @@ export async function withFileLock(lockPath, fn, opts = {}) {
58
67
  const deadline = Date.now() + timeoutMs;
59
68
  // ── acquire ──
60
69
  for (;;) {
70
+ // Write the lock body to a private temp file first, then atomically link it
71
+ // into place. link() fails with EEXIST if the lock already exists, so
72
+ // exclusive creation and "the lock file always has complete content" hold
73
+ // simultaneously — a contender can never observe a half-written lock and
74
+ // mistake it for malformed/stale. The temp name is unique per attempt so
75
+ // concurrent acquirers in the *same* process don't clobber each other.
76
+ const tmp = `${lockPath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
61
77
  try {
62
- const fh = await fsp.open(lockPath, "wx");
78
+ await fsp.writeFile(tmp, JSON.stringify({ pid: process.pid, ts: Date.now() }));
63
79
  try {
64
- await fh.writeFile(JSON.stringify({ pid: process.pid, ts: Date.now() }));
80
+ await fsp.link(tmp, lockPath);
65
81
  }
66
82
  finally {
67
- await fh.close();
83
+ await fsp.rm(tmp, { force: true }).catch(() => { });
68
84
  }
69
85
  break; // acquired
70
86
  }
71
87
  catch (e) {
88
+ await fsp.rm(tmp, { force: true }).catch(() => { });
72
89
  if (e.code !== "EEXIST")
73
90
  throw e;
74
91
  // Held by someone. Steal if stale, else wait.
75
92
  if (await isStale(lockPath, staleMs)) {
76
- await fsp.rm(lockPath, { force: true }).catch(() => { });
93
+ // Steal atomically: rename the stale file to a unique name so only one
94
+ // contender can claim it. Losers get ENOENT and just retry the link
95
+ // race — nobody unconditionally rm's a path that may already hold a
96
+ // fresh lock from a concurrent acquirer.
97
+ await fsp
98
+ .rename(lockPath, tmp)
99
+ .then(() => fsp.rm(tmp, { force: true }))
100
+ .catch(() => { });
77
101
  continue; // retry immediately
78
102
  }
79
103
  if (Date.now() >= deadline) {
@@ -1 +1 @@
1
- {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/soul/lock.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/soul/lock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * agent_recap (L6) — the cross-agent "while you were away" digest.
3
+ *
4
+ * Answers "what happened across all my agents since I left?" by synthesizing
5
+ * the orchestrator journal (every observed session's state transitions over
6
+ * time) into a readable, project-grouped recap: who ran where, what finished,
7
+ * what errored, what's still going. Complements advise_now (relevance-gated
8
+ * alerts, now) and list_agents (the current roster) with the temporal story —
9
+ * including sessions that have since ended and dropped out of the live list.
10
+ *
11
+ * Structural metadata only (agent, project, state, tool/file names, errors) —
12
+ * never prompts, replies, or file contents. Read-only.
13
+ */
14
+ import type { ToolDefinition } from "../types.js";
15
+ interface RecapInput {
16
+ /** Look back this many minutes (default 120, clamped 1–1440). */
17
+ sinceMinutes?: number;
18
+ }
19
+ export declare const agentRecapTool: ToolDefinition<RecapInput, string>;
20
+ export {};
21
+ //# sourceMappingURL=agent_recap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent_recap.d.ts","sourceRoot":"","sources":["../../src/tools/agent_recap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAIlD,UAAU,UAAU;IAClB,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,cAAc,EAAE,cAAc,CAAC,UAAU,EAAE,MAAM,CA2B7D,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * agent_recap (L6) — the cross-agent "while you were away" digest.
3
+ *
4
+ * Answers "what happened across all my agents since I left?" by synthesizing
5
+ * the orchestrator journal (every observed session's state transitions over
6
+ * time) into a readable, project-grouped recap: who ran where, what finished,
7
+ * what errored, what's still going. Complements advise_now (relevance-gated
8
+ * alerts, now) and list_agents (the current roster) with the temporal story —
9
+ * including sessions that have since ended and dropped out of the live list.
10
+ *
11
+ * Structural metadata only (agent, project, state, tool/file names, errors) —
12
+ * never prompts, replies, or file contents. Read-only.
13
+ */
14
+ import { eventsSince } from "../orchestrator/journal.js";
15
+ import { buildRecap, formatRecap } from "../orchestrator/recap.js";
16
+ export const agentRecapTool = {
17
+ name: "agent_recap",
18
+ description: "Summarize what happened across ALL the coding agents LISA observes (Claude Code, " +
19
+ "Codex, OpenCode, Aider, GitHub PRs) over a recent time window — the cross-agent " +
20
+ '"while you were away" recap. Use when the user asks what their agents did, what ' +
21
+ "happened since they were gone, or for an end-of-session wrap-up. Groups by project " +
22
+ "and reports what finished, errored, or is still running. Structural metadata only — " +
23
+ "never conversation content. Read-only.",
24
+ inputSchema: {
25
+ type: "object",
26
+ properties: {
27
+ sinceMinutes: {
28
+ type: "number",
29
+ description: "Window to look back over, in minutes (default 120; max 1440).",
30
+ },
31
+ },
32
+ additionalProperties: false,
33
+ },
34
+ async execute(input) {
35
+ const mins = Math.max(1, Math.min(1440, Math.round(input.sinceMinutes ?? 120)));
36
+ const now = Date.now();
37
+ const sinceMs = now - mins * 60_000;
38
+ const recap = buildRecap(eventsSince(sinceMs), sinceMs, now);
39
+ const window = mins >= 60 ? `${Math.round(mins / 60)}h` : `${mins}m`;
40
+ return `Cross-agent recap (last ${window}):\n${formatRecap(recap)}`;
41
+ },
42
+ };
43
+ //# sourceMappingURL=agent_recap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent_recap.js","sourceRoot":"","sources":["../../src/tools/agent_recap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAOnE,MAAM,CAAC,MAAM,cAAc,GAAuC;IAChE,IAAI,EAAE,aAAa;IACnB,WAAW,EACT,mFAAmF;QACnF,kFAAkF;QAClF,kFAAkF;QAClF,qFAAqF;QACrF,sFAAsF;QACtF,wCAAwC;IAC1C,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,YAAY,EAAE;gBACZ,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,+DAA+D;aAC7E;SACF;QACD,oBAAoB,EAAE,KAAK;KAC5B;IACD,KAAK,CAAC,OAAO,CAAC,KAAK;QACjB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;QAChF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,GAAG,GAAG,IAAI,GAAG,MAAM,CAAC;QACpC,MAAM,KAAK,GAAG,UAAU,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC;QACrE,OAAO,2BAA2B,MAAM,OAAO,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;IACtE,CAAC;CACF,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { ToolDefinition } from "../types.js";
2
+ import { type ComparisonJob, type ComparisonEntry, type CompareAgentKind } from "../integrations/comparisons.js";
3
+ interface CompareInput {
4
+ action?: "start" | "status" | "collect" | "cleanup" | "list";
5
+ task?: string;
6
+ agents?: CompareAgentKind[];
7
+ cwd?: string;
8
+ id?: string;
9
+ }
10
+ /** Pure: one line summarising an entry's live status. Exported for tests. */
11
+ export declare function formatStatusLine(e: ComparisonEntry, state: string | null, changed: number): string;
12
+ /** Pure: header line for a job. Exported for tests. */
13
+ export declare function formatJobHeader(j: ComparisonJob): string;
14
+ export declare const compareAgentsTool: ToolDefinition<CompareInput, string>;
15
+ export {};
16
+ //# sourceMappingURL=compare_agents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare_agents.d.ts","sourceRoot":"","sources":["../../src/tools/compare_agents.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAIlD,OAAO,EAOL,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,gCAAgC,CAAC;AAExC,UAAU,YAAY;IACpB,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAIlG;AAED,uDAAuD;AACvD,wBAAgB,eAAe,CAAC,CAAC,EAAE,aAAa,GAAG,MAAM,CAGxD;AAOD,eAAO,MAAM,iBAAiB,EAAE,cAAc,CAAC,YAAY,EAAE,MAAM,CA8GlE,CAAC"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * compare_agents (vibe-coding, workflow form) — run the SAME task across two or
3
+ * more agents and compare what each produced, so you can pick the best result.
4
+ *
5
+ * It's a workflow, not a one-shot, because agents run for minutes:
6
+ * start → make an isolated git worktree per agent (off the repo, so they
7
+ * don't clobber each other), launch each agent on the task, record
8
+ * the job. Returns immediately.
9
+ * status → for each agent: live session state + how many files it has changed.
10
+ * collect → diff stat per agent's worktree, side by side, so you choose.
11
+ * cleanup → remove the worktrees + scratch branches once you've picked.
12
+ * list → existing comparison jobs.
13
+ */
14
+ import path from "node:path";
15
+ import { mkdir } from "node:fs/promises";
16
+ import { getCurrentHub } from "../integrations/current-hub.js";
17
+ import { launchAgent } from "./dispatch_agent.js";
18
+ import { runIn, gitRoot } from "./exec-util.js";
19
+ import { loadComparisons, addComparison, getComparison, removeComparison, newJobId, COMPARE_ROOT, } from "../integrations/comparisons.js";
20
+ /** Pure: one line summarising an entry's live status. Exported for tests. */
21
+ export function formatStatusLine(e, state, changed) {
22
+ if (e.launchError)
23
+ return ` ${e.agent}: failed to launch — ${e.launchError}`;
24
+ const st = state ?? "no session yet";
25
+ return ` ${e.agent}: ${st} · ${changed} file(s) changed [${e.branch}]`;
26
+ }
27
+ /** Pure: header line for a job. Exported for tests. */
28
+ export function formatJobHeader(j) {
29
+ const task = j.task.length > 70 ? j.task.slice(0, 67) + "…" : j.task;
30
+ return `compare ${j.id} (${j.entries.length} agents) in ${j.repo}\n task: "${task}"`;
31
+ }
32
+ async function changedCount(worktree, signal) {
33
+ const r = await runIn(worktree, "git", ["-C", worktree, "status", "--porcelain"], { timeoutMs: 8000, signal });
34
+ return r.code === 0 ? r.stdout.split("\n").filter((l) => l.trim()).length : 0;
35
+ }
36
+ export const compareAgentsTool = {
37
+ name: "compare_agents",
38
+ description: "Run the same task across multiple agents in isolated git worktrees and compare results — a " +
39
+ "workflow (agents run for minutes): action:'start' (task + agents, default [claude,codex]) launches " +
40
+ "them; 'status' (id) shows each one's live state + files changed; 'collect' (id) diffs each result " +
41
+ "side by side so you pick a winner; 'cleanup' (id) removes the worktrees; 'list' shows jobs. Use when " +
42
+ "the user wants to try an approach two ways, or isn't sure which agent does better. Spawns agents.",
43
+ inputSchema: {
44
+ type: "object",
45
+ properties: {
46
+ action: { type: "string", enum: ["start", "status", "collect", "cleanup", "list"] },
47
+ task: { type: "string", description: "The shared task to give every agent (for start).", minLength: 1 },
48
+ agents: { type: "array", items: { type: "string", enum: ["claude", "codex", "opencode", "aider"] }, description: "Agents to compare (for start). Default [claude, codex]." },
49
+ cwd: { type: "string", description: "Absolute path in the repo to branch worktrees from (for start). Defaults to current dir." },
50
+ id: { type: "string", description: "Comparison job id (for status/collect/cleanup)." },
51
+ },
52
+ additionalProperties: false,
53
+ },
54
+ async execute(input, ctx) {
55
+ const action = input.action ?? "start";
56
+ if (action === "list") {
57
+ const jobs = await loadComparisons();
58
+ if (jobs.length === 0)
59
+ return "(no comparison jobs)";
60
+ return jobs.map(formatJobHeader).join("\n\n");
61
+ }
62
+ if (action === "start") {
63
+ const task = input.task?.trim();
64
+ if (!task)
65
+ return "(start needs a task)";
66
+ const agents = input.agents && input.agents.length ? Array.from(new Set(input.agents)) : ["claude", "codex"];
67
+ if (agents.length < 2)
68
+ return "(comparing needs at least 2 agents)";
69
+ const cwd = input.cwd && input.cwd.startsWith("/") ? input.cwd : ctx.cwd;
70
+ const repo = await gitRoot(cwd, ctx.signal);
71
+ if (!repo)
72
+ return `(not a git repo: ${cwd} — compare_agents isolates each agent in a git worktree)`;
73
+ const id = newJobId();
74
+ await mkdir(path.join(COMPARE_ROOT, id), { recursive: true });
75
+ const entries = [];
76
+ for (const agent of agents) {
77
+ const branch = `lisa-compare/${id}-${agent}`;
78
+ const worktree = path.join(COMPARE_ROOT, id, agent);
79
+ const wt = await runIn(repo, "git", ["-C", repo, "worktree", "add", "-b", branch, worktree, "HEAD"], { timeoutMs: 20000, signal: ctx.signal });
80
+ if (wt.code !== 0) {
81
+ entries.push({ agent, worktree, branch, launchError: `worktree add failed: ${wt.stderr.trim().slice(0, 120)}` });
82
+ continue;
83
+ }
84
+ const r = await launchAgent(agent, task, worktree, ctx.log);
85
+ entries.push({ agent, worktree, branch, pid: r.pid, launchError: r.error });
86
+ }
87
+ const job = { id, task, repo, createdAt: Date.now(), entries };
88
+ await addComparison(job);
89
+ const launched = entries.filter((e) => !e.launchError).map((e) => e.agent);
90
+ const failed = entries.filter((e) => e.launchError);
91
+ return (`Started comparison ${id}: ${launched.join(" vs ")} on the same task in ${repo}.\n` +
92
+ entries.map((e) => ` ${e.agent} → ${e.launchError ? "FAILED: " + e.launchError : "pid " + e.pid + " in " + e.worktree}`).join("\n") +
93
+ (failed.length ? "" : "") +
94
+ `\n\nThey run in the background. Check progress with compare_agents action:'status' id:'${id}', ` +
95
+ `then action:'collect' id:'${id}' to diff the results, and 'cleanup' when done.`);
96
+ }
97
+ // status / collect / cleanup need a job
98
+ if (!input.id)
99
+ return `(${action} needs an id — from compare_agents action:'list')`;
100
+ const job = await getComparison(input.id);
101
+ if (!job)
102
+ return `(no comparison job matches "${input.id}")`;
103
+ if (action === "status") {
104
+ const hub = getCurrentHub();
105
+ const lines = [formatJobHeader(job)];
106
+ for (const e of job.entries) {
107
+ const session = hub?.list().find((s) => s.cwd === e.worktree);
108
+ const changed = await changedCount(e.worktree, ctx.signal);
109
+ lines.push(formatStatusLine(e, session ? session.state : null, changed));
110
+ }
111
+ return lines.join("\n");
112
+ }
113
+ if (action === "collect") {
114
+ const lines = [formatJobHeader(job), ""];
115
+ for (const e of job.entries) {
116
+ if (e.launchError) {
117
+ lines.push(`── ${e.agent}: (didn't launch: ${e.launchError})`);
118
+ continue;
119
+ }
120
+ const stat = await runIn(e.worktree, "git", ["-C", e.worktree, "diff", "HEAD", "--stat"], { timeoutMs: 10000, signal: ctx.signal });
121
+ // diff HEAD misses untracked new files — list those separately so a
122
+ // brand-new file an agent created isn't invisible in the comparison.
123
+ const st = await runIn(e.worktree, "git", ["-C", e.worktree, "status", "--porcelain"], { timeoutMs: 8000, signal: ctx.signal });
124
+ const untracked = st.code === 0 ? st.stdout.split("\n").filter((l) => l.startsWith("??")).map((l) => l.slice(3).trim()) : [];
125
+ let body = stat.code === 0 && stat.stdout.trim() ? stat.stdout.trim() : "";
126
+ if (untracked.length)
127
+ body += `\n new files: ${untracked.slice(0, 10).join(", ")}${untracked.length > 10 ? ` (+${untracked.length - 10})` : ""}`;
128
+ lines.push(`── ${e.agent} [${e.branch}] ──\n${body.trim() || "(no changes)"}`);
129
+ }
130
+ lines.push("", "Review one in full with review_diff cwd:'<worktree>' target:'head', then cleanup.");
131
+ return lines.join("\n");
132
+ }
133
+ // cleanup
134
+ const errs = [];
135
+ for (const e of job.entries) {
136
+ const rm = await runIn(job.repo, "git", ["-C", job.repo, "worktree", "remove", "--force", e.worktree], { timeoutMs: 15000, signal: ctx.signal });
137
+ if (rm.code !== 0 && !/not a working tree|No such file/i.test(rm.stderr))
138
+ errs.push(`${e.agent}: ${rm.stderr.trim().slice(0, 80)}`);
139
+ await runIn(job.repo, "git", ["-C", job.repo, "branch", "-D", e.branch], { timeoutMs: 8000, signal: ctx.signal });
140
+ }
141
+ await removeComparison(job.id);
142
+ return errs.length ? `Cleaned up ${job.id} with warnings:\n${errs.join("\n")}` : `Cleaned up comparison ${job.id} (worktrees + scratch branches removed).`;
143
+ },
144
+ };
145
+ //# sourceMappingURL=compare_agents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare_agents.js","sourceRoot":"","sources":["../../src/tools/compare_agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EACL,eAAe,EACf,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACR,YAAY,GAIb,MAAM,gCAAgC,CAAC;AAUxC,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB,CAAC,CAAkB,EAAE,KAAoB,EAAE,OAAe;IACxF,IAAI,CAAC,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC,CAAC,KAAK,wBAAwB,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9E,MAAM,EAAE,GAAG,KAAK,IAAI,gBAAgB,CAAC;IACrC,OAAO,KAAK,CAAC,CAAC,KAAK,KAAK,EAAE,MAAM,OAAO,sBAAsB,CAAC,CAAC,MAAM,GAAG,CAAC;AAC3E,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,eAAe,CAAC,CAAgB;IAC9C,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACrE,OAAO,WAAW,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,eAAe,CAAC,CAAC,IAAI,cAAc,IAAI,GAAG,CAAC;AACxF,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,QAAgB,EAAE,MAAoB;IAChE,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/G,OAAO,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAChF,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAyC;IACrE,IAAI,EAAE,gBAAgB;IACtB,WAAW,EACT,6FAA6F;QAC7F,qGAAqG;QACrG,oGAAoG;QACpG,uGAAuG;QACvG,mGAAmG;IACrG,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE;YACnF,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,kDAAkD,EAAE,SAAS,EAAE,CAAC,EAAE;YACvG,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE,WAAW,EAAE,yDAAyD,EAAE;YAC5K,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,0FAA0F,EAAE;YAChI,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,iDAAiD,EAAE;SACvF;QACD,oBAAoB,EAAE,KAAK;KAC5B;IACD,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG;QACtB,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,OAAO,CAAC;QAEvC,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACtB,MAAM,IAAI,GAAG,MAAM,eAAe,EAAE,CAAC;YACrC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,sBAAsB,CAAC;YACrD,OAAO,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAChD,CAAC;QAED,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;YAChC,IAAI,CAAC,IAAI;gBAAE,OAAO,sBAAsB,CAAC;YACzC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,QAAQ,EAAE,OAAO,CAAwB,CAAC;YACrI,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,qCAAqC,CAAC;YACpE,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;YACzE,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;YAC5C,IAAI,CAAC,IAAI;gBAAE,OAAO,oBAAoB,GAAG,0DAA0D,CAAC;YAEpG,MAAM,EAAE,GAAG,QAAQ,EAAE,CAAC;YACtB,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9D,MAAM,OAAO,GAAsB,EAAE,CAAC;YACtC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,MAAM,MAAM,GAAG,gBAAgB,EAAE,IAAI,KAAK,EAAE,CAAC;gBAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;gBACpD,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC/I,IAAI,EAAE,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;oBAClB,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,wBAAwB,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;oBACjH,SAAS;gBACX,CAAC;gBACD,MAAM,CAAC,GAAG,MAAM,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC5D,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;YAC9E,CAAC;YACD,MAAM,GAAG,GAAkB,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC;YAC9E,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC;YACzB,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC3E,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;YACpD,OAAO,CACL,sBAAsB,EAAE,KAAK,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,wBAAwB,IAAI,KAAK;gBACnF,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,GAAG,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;gBACpI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzB,0FAA0F,EAAE,KAAK;gBACjG,6BAA6B,EAAE,iDAAiD,CACjF,CAAC;QACJ,CAAC;QAED,wCAAwC;QACxC,IAAI,CAAC,KAAK,CAAC,EAAE;YAAE,OAAO,IAAI,MAAM,mDAAmD,CAAC;QACpF,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC1C,IAAI,CAAC,GAAG;YAAE,OAAO,+BAA+B,KAAK,CAAC,EAAE,IAAI,CAAC;QAE7D,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;YAC5B,MAAM,KAAK,GAAa,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC;YAC/C,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;gBAC5B,MAAM,OAAO,GAAG,GAAG,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC;gBAC9D,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC3D,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;YAC3E,CAAC;YACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;QAED,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,KAAK,GAAa,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;YACnD,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;gBAC5B,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;oBAClB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,qBAAqB,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC;oBAC/D,SAAS;gBACX,CAAC;gBACD,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;gBACpI,oEAAoE;gBACpE,qEAAqE;gBACrE,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,aAAa,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;gBAChI,MAAM,SAAS,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC7H,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3E,IAAI,SAAS,CAAC,MAAM;oBAAE,IAAI,IAAI,iBAAiB,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,SAAS,CAAC,MAAM,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;gBACjJ,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,MAAM,SAAS,IAAI,CAAC,IAAI,EAAE,IAAI,cAAc,EAAE,CAAC,CAAC;YACjF,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,mFAAmF,CAAC,CAAC;YACpG,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;QAED,UAAU;QACV,MAAM,IAAI,GAAa,EAAE,CAAC;QAC1B,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;YAC5B,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YACjJ,IAAI,EAAE,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,kCAAkC,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC;gBAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;YACpI,MAAM,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACpH,CAAC;QACD,MAAM,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,GAAG,CAAC,EAAE,oBAAoB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,yBAAyB,GAAG,CAAC,EAAE,0CAA0C,CAAC;IAC7J,CAAC;CACF,CAAC"}
@@ -33,6 +33,19 @@ export declare function buildDispatchArgv(agent: DispatchInput["agent"], task: s
33
33
  cmd: string;
34
34
  args: string[];
35
35
  };
36
+ export type DispatchAgentKind = DispatchInput["agent"];
37
+ export interface LaunchResult {
38
+ pid?: number;
39
+ /** Present when the launch failed (binary missing, spawn error). */
40
+ error?: string;
41
+ cmd: string;
42
+ }
43
+ /**
44
+ * Spawn a CLI agent headlessly, detached, and record it in the dispatch ledger.
45
+ * Shared by dispatch_agent (interactive), scheduled_dispatch (heartbeat-timed),
46
+ * and compare_agents (parallel). Never throws — returns {pid} or {error}.
47
+ */
48
+ export declare function launchAgent(agent: DispatchAgentKind, task: string, cwd: string, log?: (m: string) => void): Promise<LaunchResult>;
36
49
  export declare const dispatchAgentTool: ToolDefinition<DispatchInput, string>;
37
50
  export {};
38
51
  //# sourceMappingURL=dispatch_agent.d.ts.map