iosm-cli 0.2.4 → 0.2.5

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 (65) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +23 -17
  3. package/dist/core/agent-profiles.d.ts +1 -0
  4. package/dist/core/agent-profiles.d.ts.map +1 -1
  5. package/dist/core/agent-profiles.js +10 -6
  6. package/dist/core/agent-profiles.js.map +1 -1
  7. package/dist/core/agent-session.d.ts +1 -1
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +6 -2
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/agent-teams.d.ts.map +1 -1
  12. package/dist/core/agent-teams.js +90 -19
  13. package/dist/core/agent-teams.js.map +1 -1
  14. package/dist/core/footer-data-provider.d.ts +6 -1
  15. package/dist/core/footer-data-provider.d.ts.map +1 -1
  16. package/dist/core/footer-data-provider.js +9 -0
  17. package/dist/core/footer-data-provider.js.map +1 -1
  18. package/dist/core/parallel-task-agent.d.ts +23 -1
  19. package/dist/core/parallel-task-agent.d.ts.map +1 -1
  20. package/dist/core/parallel-task-agent.js +110 -20
  21. package/dist/core/parallel-task-agent.js.map +1 -1
  22. package/dist/core/shared-memory.d.ts +2 -2
  23. package/dist/core/shared-memory.d.ts.map +1 -1
  24. package/dist/core/shared-memory.js +220 -91
  25. package/dist/core/shared-memory.js.map +1 -1
  26. package/dist/core/singular.d.ts.map +1 -1
  27. package/dist/core/singular.js +3 -1
  28. package/dist/core/singular.js.map +1 -1
  29. package/dist/core/subagents.d.ts +1 -1
  30. package/dist/core/subagents.d.ts.map +1 -1
  31. package/dist/core/subagents.js +11 -3
  32. package/dist/core/subagents.js.map +1 -1
  33. package/dist/core/swarm/planner.d.ts.map +1 -1
  34. package/dist/core/swarm/planner.js +200 -12
  35. package/dist/core/swarm/planner.js.map +1 -1
  36. package/dist/core/swarm/scheduler.d.ts +2 -0
  37. package/dist/core/swarm/scheduler.d.ts.map +1 -1
  38. package/dist/core/swarm/scheduler.js +87 -6
  39. package/dist/core/swarm/scheduler.js.map +1 -1
  40. package/dist/core/system-prompt.d.ts.map +1 -1
  41. package/dist/core/system-prompt.js +1 -0
  42. package/dist/core/system-prompt.js.map +1 -1
  43. package/dist/core/tools/ast-grep.d.ts.map +1 -1
  44. package/dist/core/tools/ast-grep.js +2 -0
  45. package/dist/core/tools/ast-grep.js.map +1 -1
  46. package/dist/core/tools/shared-memory.d.ts.map +1 -1
  47. package/dist/core/tools/shared-memory.js +34 -6
  48. package/dist/core/tools/shared-memory.js.map +1 -1
  49. package/dist/core/tools/task.d.ts.map +1 -1
  50. package/dist/core/tools/task.js +464 -73
  51. package/dist/core/tools/task.js.map +1 -1
  52. package/dist/core/tools/yq.d.ts.map +1 -1
  53. package/dist/core/tools/yq.js +2 -0
  54. package/dist/core/tools/yq.js.map +1 -1
  55. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  56. package/dist/modes/interactive/components/footer.js +2 -1
  57. package/dist/modes/interactive/components/footer.js.map +1 -1
  58. package/dist/modes/interactive/interactive-mode.d.ts +13 -0
  59. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  60. package/dist/modes/interactive/interactive-mode.js +756 -74
  61. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  62. package/docs/cli-reference.md +4 -0
  63. package/docs/interactive-mode.md +2 -0
  64. package/docs/orchestration-and-subagents.md +5 -0
  65. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"agent-teams.d.ts","sourceRoot":"","sources":["../../src/core/agent-teams.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,WAAW,CAAC;AAEpF,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,UAAU,GAAG,YAAY,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,cAAc,EAAE,CAAC;CACxB;AAeD,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,UAAU,GAAG,YAAY,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CAC5F,GAAG,aAAa,CA2BhB;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAQhF;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,aAAa,EAAE,CAkBrE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,cAAc,CAAC;CACvB,GAAG,aAAa,GAAG,SAAS,CAwC5B"}
1
+ {"version":3,"file":"agent-teams.d.ts","sourceRoot":"","sources":["../../src/core/agent-teams.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,WAAW,CAAC;AAEpF,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,cAAc,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,UAAU,GAAG,YAAY,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,cAAc,EAAE,CAAC;CACxB;AA6FD,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,UAAU,GAAG,YAAY,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CAC5F,GAAG,aAAa,CA2BhB;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAQhF;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,aAAa,EAAE,CAkBrE;AAyCD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,cAAc,CAAC;CACvB,GAAG,aAAa,GAAG,SAAS,CAY5B"}
@@ -1,5 +1,5 @@
1
1
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
2
+ import { join, resolve } from "node:path";
3
3
  import lockfile from "proper-lockfile";
4
4
  function getTeamsDir(cwd) {
5
5
  return join(cwd, ".iosm", "subagents", "teams");
@@ -7,9 +7,72 @@ function getTeamsDir(cwd) {
7
7
  function getTeamRunPath(cwd, runId) {
8
8
  return join(getTeamsDir(cwd), `${runId}.json`);
9
9
  }
10
- function sleepSync(ms) {
11
- const waiter = new Int32Array(new SharedArrayBuffer(4));
12
- Atomics.wait(waiter, 0, 0, ms);
10
+ const statusUpdateRetryDelayMs = 25;
11
+ const maxQueuedStatusAttempts = 50;
12
+ const pendingStatusUpdates = new Map();
13
+ let pendingStatusFlushTimer;
14
+ function isTerminalStatus(status) {
15
+ return status === "done" || status === "error" || status === "cancelled";
16
+ }
17
+ function shouldReplacePendingStatus(current, next) {
18
+ if (current === next)
19
+ return false;
20
+ if (isTerminalStatus(current))
21
+ return false;
22
+ if (isTerminalStatus(next))
23
+ return true;
24
+ if (current === "running" && next === "pending")
25
+ return false;
26
+ return true;
27
+ }
28
+ function pendingStatusKey(input) {
29
+ return `${resolve(input.cwd).toLowerCase()}::${input.runId}::${input.taskId}`;
30
+ }
31
+ function schedulePendingStatusFlush(delayMs = statusUpdateRetryDelayMs) {
32
+ if (pendingStatusFlushTimer)
33
+ return;
34
+ pendingStatusFlushTimer = setTimeout(() => {
35
+ pendingStatusFlushTimer = undefined;
36
+ flushPendingStatusUpdates();
37
+ }, delayMs);
38
+ }
39
+ function queuePendingStatusUpdate(input) {
40
+ const key = pendingStatusKey(input);
41
+ const existing = pendingStatusUpdates.get(key);
42
+ if (!existing) {
43
+ pendingStatusUpdates.set(key, { input, attempts: 0 });
44
+ schedulePendingStatusFlush();
45
+ return;
46
+ }
47
+ if (shouldReplacePendingStatus(existing.input.status, input.status)) {
48
+ pendingStatusUpdates.set(key, {
49
+ input,
50
+ attempts: existing.attempts,
51
+ });
52
+ }
53
+ schedulePendingStatusFlush();
54
+ }
55
+ function flushPendingStatusUpdates() {
56
+ if (pendingStatusUpdates.size === 0)
57
+ return;
58
+ for (const [key, pending] of Array.from(pendingStatusUpdates.entries())) {
59
+ if (pending.attempts >= maxQueuedStatusAttempts) {
60
+ pendingStatusUpdates.delete(key);
61
+ continue;
62
+ }
63
+ const result = tryUpdateTeamTaskStatus(pending.input);
64
+ if (result === "locked") {
65
+ pendingStatusUpdates.set(key, {
66
+ input: pending.input,
67
+ attempts: pending.attempts + 1,
68
+ });
69
+ continue;
70
+ }
71
+ pendingStatusUpdates.delete(key);
72
+ }
73
+ if (pendingStatusUpdates.size > 0) {
74
+ schedulePendingStatusFlush();
75
+ }
13
76
  }
14
77
  export function createTeamRun(input) {
15
78
  const runId = `team_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
@@ -71,32 +134,27 @@ export function listTeamRuns(cwd, limit = 20) {
71
134
  }
72
135
  return runs;
73
136
  }
74
- export function updateTeamTaskStatus(input) {
137
+ function tryUpdateTeamTaskStatus(input) {
75
138
  const runPath = getTeamRunPath(input.cwd, input.runId);
76
139
  if (!existsSync(runPath))
77
140
  return undefined;
78
141
  let release;
79
142
  try {
80
143
  mkdirSync(getTeamsDir(input.cwd), { recursive: true });
81
- const maxLockAttempts = 12;
82
- for (let attempt = 1; attempt <= maxLockAttempts; attempt += 1) {
83
- try {
84
- release = lockfile.lockSync(runPath, { realpath: false });
85
- break;
86
- }
87
- catch (error) {
88
- const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
89
- if (code !== "ELOCKED" || attempt === maxLockAttempts) {
90
- return undefined;
91
- }
92
- sleepSync(Math.min(120, 10 + attempt * 10));
93
- }
144
+ try {
145
+ release = lockfile.lockSync(runPath, { realpath: false });
146
+ }
147
+ catch (error) {
148
+ const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
149
+ if (code === "ELOCKED")
150
+ return "locked";
151
+ return undefined;
94
152
  }
95
153
  if (!release)
96
154
  return undefined;
97
155
  const raw = readFileSync(runPath, "utf8");
98
156
  const existing = JSON.parse(raw);
99
- const nextTasks = existing.tasks.map((task) => task.id === input.taskId ? { ...task, status: input.status } : task);
157
+ const nextTasks = existing.tasks.map((task) => (task.id === input.taskId ? { ...task, status: input.status } : task));
100
158
  if (!nextTasks.some((task) => task.id === input.taskId)) {
101
159
  return undefined;
102
160
  }
@@ -114,4 +172,17 @@ export function updateTeamTaskStatus(input) {
114
172
  release?.();
115
173
  }
116
174
  }
175
+ export function updateTeamTaskStatus(input) {
176
+ const result = tryUpdateTeamTaskStatus(input);
177
+ if (result === "locked") {
178
+ // Non-blocking reliability path: queue status update for retry instead of dropping lifecycle transitions.
179
+ queuePendingStatusUpdate(input);
180
+ return undefined;
181
+ }
182
+ const key = pendingStatusKey(input);
183
+ if (pendingStatusUpdates.has(key) && result) {
184
+ pendingStatusUpdates.delete(key);
185
+ }
186
+ return result;
187
+ }
117
188
  //# sourceMappingURL=agent-teams.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-teams.js","sourceRoot":"","sources":["../../src/core/agent-teams.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport lockfile from \"proper-lockfile\";\n\nexport type TeamTaskStatus = \"pending\" | \"running\" | \"done\" | \"error\" | \"cancelled\";\n\nexport interface TeamTaskRecord {\n\tid: string;\n\tagentIndex: number;\n\tprofile: string;\n\tcwd: string;\n\tlockKey?: string;\n\tdependsOn: string[];\n\tstatus: TeamTaskStatus;\n}\n\nexport interface TeamRunRecord {\n\trunId: string;\n\tcreatedAt: string;\n\tmode: \"parallel\" | \"sequential\";\n\tagents: number;\n\tmaxParallel?: number;\n\ttask: string;\n\ttasks: TeamTaskRecord[];\n}\n\nfunction getTeamsDir(cwd: string): string {\n\treturn join(cwd, \".iosm\", \"subagents\", \"teams\");\n}\n\nfunction getTeamRunPath(cwd: string, runId: string): string {\n\treturn join(getTeamsDir(cwd), `${runId}.json`);\n}\n\nfunction sleepSync(ms: number): void {\n\tconst waiter = new Int32Array(new SharedArrayBuffer(4));\n\tAtomics.wait(waiter, 0, 0, ms);\n}\n\nexport function createTeamRun(input: {\n\tcwd: string;\n\tmode: \"parallel\" | \"sequential\";\n\tagents: number;\n\tmaxParallel?: number;\n\ttask: string;\n\tassignments: Array<{ profile: string; cwd: string; lockKey?: string; dependsOn: number[] }>;\n}): TeamRunRecord {\n\tconst runId = `team_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;\n\tconst tasks: TeamTaskRecord[] = input.assignments.map((assignment, index) => {\n\t\tconst id = `task_${index + 1}`;\n\t\tconst dependsOn = assignment.dependsOn.map((dep) => `task_${dep}`);\n\t\treturn {\n\t\t\tid,\n\t\t\tagentIndex: index + 1,\n\t\t\tprofile: assignment.profile,\n\t\t\tcwd: assignment.cwd,\n\t\t\tlockKey: assignment.lockKey,\n\t\t\tdependsOn,\n\t\t\tstatus: \"pending\",\n\t\t};\n\t});\n\tconst record: TeamRunRecord = {\n\t\trunId,\n\t\tcreatedAt: new Date().toISOString(),\n\t\tmode: input.mode,\n\t\tagents: input.agents,\n\t\tmaxParallel: input.maxParallel,\n\t\ttask: input.task,\n\t\ttasks,\n\t};\n\tmkdirSync(getTeamsDir(input.cwd), { recursive: true });\n\twriteFileSync(getTeamRunPath(input.cwd, runId), JSON.stringify(record, null, 2), \"utf8\");\n\treturn record;\n}\n\nexport function getTeamRun(cwd: string, runId: string): TeamRunRecord | undefined {\n\tconst path = getTeamRunPath(cwd, runId);\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\treturn JSON.parse(readFileSync(path, \"utf8\")) as TeamRunRecord;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function listTeamRuns(cwd: string, limit = 20): TeamRunRecord[] {\n\tconst dir = getTeamsDir(cwd);\n\tif (!existsSync(dir)) return [];\n\tconst names = readdirSync(dir)\n\t\t.filter((name) => name.toLowerCase().endsWith(\".json\"))\n\t\t.sort()\n\t\t.reverse()\n\t\t.slice(0, Math.max(1, limit));\n\tconst runs: TeamRunRecord[] = [];\n\tfor (const name of names) {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(join(dir, name), \"utf8\")) as TeamRunRecord;\n\t\t\truns.push(parsed);\n\t\t} catch {\n\t\t\t// ignore malformed files\n\t\t}\n\t}\n\treturn runs;\n}\n\nexport function updateTeamTaskStatus(input: {\n\tcwd: string;\n\trunId: string;\n\ttaskId: string;\n\tstatus: TeamTaskStatus;\n}): TeamRunRecord | undefined {\n\tconst runPath = getTeamRunPath(input.cwd, input.runId);\n\tif (!existsSync(runPath)) return undefined;\n\tlet release: (() => void) | undefined;\n\ttry {\n\t\tmkdirSync(getTeamsDir(input.cwd), { recursive: true });\n\t\tconst maxLockAttempts = 12;\n\t\tfor (let attempt = 1; attempt <= maxLockAttempts; attempt += 1) {\n\t\t\ttry {\n\t\t\t\trelease = lockfile.lockSync(runPath, { realpath: false });\n\t\t\t\tbreak;\n\t\t\t} catch (error) {\n\t\t\t\tconst code = error && typeof error === \"object\" && \"code\" in error ? String(error.code) : \"\";\n\t\t\t\tif (code !== \"ELOCKED\" || attempt === maxLockAttempts) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tsleepSync(Math.min(120, 10 + attempt * 10));\n\t\t\t}\n\t\t}\n\t\tif (!release) return undefined;\n\t\tconst raw = readFileSync(runPath, \"utf8\");\n\t\tconst existing = JSON.parse(raw) as TeamRunRecord;\n\t\tconst nextTasks = existing.tasks.map((task) =>\n\t\t\ttask.id === input.taskId ? { ...task, status: input.status } : task,\n\t\t);\n\t\tif (!nextTasks.some((task) => task.id === input.taskId)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst next: TeamRunRecord = {\n\t\t\t...existing,\n\t\t\ttasks: nextTasks,\n\t\t};\n\t\twriteFileSync(runPath, JSON.stringify(next, null, 2), \"utf8\");\n\t\treturn next;\n\t} catch {\n\t\treturn undefined;\n\t} finally {\n\t\trelease?.();\n\t}\n}\n"]}
1
+ {"version":3,"file":"agent-teams.js","sourceRoot":"","sources":["../../src/core/agent-teams.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport lockfile from \"proper-lockfile\";\n\nexport type TeamTaskStatus = \"pending\" | \"running\" | \"done\" | \"error\" | \"cancelled\";\n\nexport interface TeamTaskRecord {\n\tid: string;\n\tagentIndex: number;\n\tprofile: string;\n\tcwd: string;\n\tlockKey?: string;\n\tdependsOn: string[];\n\tstatus: TeamTaskStatus;\n}\n\nexport interface TeamRunRecord {\n\trunId: string;\n\tcreatedAt: string;\n\tmode: \"parallel\" | \"sequential\";\n\tagents: number;\n\tmaxParallel?: number;\n\ttask: string;\n\ttasks: TeamTaskRecord[];\n}\n\nfunction getTeamsDir(cwd: string): string {\n\treturn join(cwd, \".iosm\", \"subagents\", \"teams\");\n}\n\nfunction getTeamRunPath(cwd: string, runId: string): string {\n\treturn join(getTeamsDir(cwd), `${runId}.json`);\n}\n\ntype PendingStatusUpdate = {\n\tinput: {\n\t\tcwd: string;\n\t\trunId: string;\n\t\ttaskId: string;\n\t\tstatus: TeamTaskStatus;\n\t};\n\tattempts: number;\n};\n\nconst statusUpdateRetryDelayMs = 25;\nconst maxQueuedStatusAttempts = 50;\nconst pendingStatusUpdates = new Map<string, PendingStatusUpdate>();\nlet pendingStatusFlushTimer: ReturnType<typeof setTimeout> | undefined;\n\nfunction isTerminalStatus(status: TeamTaskStatus): boolean {\n\treturn status === \"done\" || status === \"error\" || status === \"cancelled\";\n}\n\nfunction shouldReplacePendingStatus(current: TeamTaskStatus, next: TeamTaskStatus): boolean {\n\tif (current === next) return false;\n\tif (isTerminalStatus(current)) return false;\n\tif (isTerminalStatus(next)) return true;\n\tif (current === \"running\" && next === \"pending\") return false;\n\treturn true;\n}\n\nfunction pendingStatusKey(input: { cwd: string; runId: string; taskId: string }): string {\n\treturn `${resolve(input.cwd).toLowerCase()}::${input.runId}::${input.taskId}`;\n}\n\nfunction schedulePendingStatusFlush(delayMs = statusUpdateRetryDelayMs): void {\n\tif (pendingStatusFlushTimer) return;\n\tpendingStatusFlushTimer = setTimeout(() => {\n\t\tpendingStatusFlushTimer = undefined;\n\t\tflushPendingStatusUpdates();\n\t}, delayMs);\n}\n\nfunction queuePendingStatusUpdate(input: {\n\tcwd: string;\n\trunId: string;\n\ttaskId: string;\n\tstatus: TeamTaskStatus;\n}): void {\n\tconst key = pendingStatusKey(input);\n\tconst existing = pendingStatusUpdates.get(key);\n\tif (!existing) {\n\t\tpendingStatusUpdates.set(key, { input, attempts: 0 });\n\t\tschedulePendingStatusFlush();\n\t\treturn;\n\t}\n\tif (shouldReplacePendingStatus(existing.input.status, input.status)) {\n\t\tpendingStatusUpdates.set(key, {\n\t\t\tinput,\n\t\t\tattempts: existing.attempts,\n\t\t});\n\t}\n\tschedulePendingStatusFlush();\n}\n\nfunction flushPendingStatusUpdates(): void {\n\tif (pendingStatusUpdates.size === 0) return;\n\tfor (const [key, pending] of Array.from(pendingStatusUpdates.entries())) {\n\t\tif (pending.attempts >= maxQueuedStatusAttempts) {\n\t\t\tpendingStatusUpdates.delete(key);\n\t\t\tcontinue;\n\t\t}\n\t\tconst result = tryUpdateTeamTaskStatus(pending.input);\n\t\tif (result === \"locked\") {\n\t\t\tpendingStatusUpdates.set(key, {\n\t\t\t\tinput: pending.input,\n\t\t\t\tattempts: pending.attempts + 1,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tpendingStatusUpdates.delete(key);\n\t}\n\tif (pendingStatusUpdates.size > 0) {\n\t\tschedulePendingStatusFlush();\n\t}\n}\n\nexport function createTeamRun(input: {\n\tcwd: string;\n\tmode: \"parallel\" | \"sequential\";\n\tagents: number;\n\tmaxParallel?: number;\n\ttask: string;\n\tassignments: Array<{ profile: string; cwd: string; lockKey?: string; dependsOn: number[] }>;\n}): TeamRunRecord {\n\tconst runId = `team_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;\n\tconst tasks: TeamTaskRecord[] = input.assignments.map((assignment, index) => {\n\t\tconst id = `task_${index + 1}`;\n\t\tconst dependsOn = assignment.dependsOn.map((dep) => `task_${dep}`);\n\t\treturn {\n\t\t\tid,\n\t\t\tagentIndex: index + 1,\n\t\t\tprofile: assignment.profile,\n\t\t\tcwd: assignment.cwd,\n\t\t\tlockKey: assignment.lockKey,\n\t\t\tdependsOn,\n\t\t\tstatus: \"pending\",\n\t\t};\n\t});\n\tconst record: TeamRunRecord = {\n\t\trunId,\n\t\tcreatedAt: new Date().toISOString(),\n\t\tmode: input.mode,\n\t\tagents: input.agents,\n\t\tmaxParallel: input.maxParallel,\n\t\ttask: input.task,\n\t\ttasks,\n\t};\n\tmkdirSync(getTeamsDir(input.cwd), { recursive: true });\n\twriteFileSync(getTeamRunPath(input.cwd, runId), JSON.stringify(record, null, 2), \"utf8\");\n\treturn record;\n}\n\nexport function getTeamRun(cwd: string, runId: string): TeamRunRecord | undefined {\n\tconst path = getTeamRunPath(cwd, runId);\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\treturn JSON.parse(readFileSync(path, \"utf8\")) as TeamRunRecord;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport function listTeamRuns(cwd: string, limit = 20): TeamRunRecord[] {\n\tconst dir = getTeamsDir(cwd);\n\tif (!existsSync(dir)) return [];\n\tconst names = readdirSync(dir)\n\t\t.filter((name) => name.toLowerCase().endsWith(\".json\"))\n\t\t.sort()\n\t\t.reverse()\n\t\t.slice(0, Math.max(1, limit));\n\tconst runs: TeamRunRecord[] = [];\n\tfor (const name of names) {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(join(dir, name), \"utf8\")) as TeamRunRecord;\n\t\t\truns.push(parsed);\n\t\t} catch {\n\t\t\t// ignore malformed files\n\t\t}\n\t}\n\treturn runs;\n}\n\nfunction tryUpdateTeamTaskStatus(input: {\n\tcwd: string;\n\trunId: string;\n\ttaskId: string;\n\tstatus: TeamTaskStatus;\n}): TeamRunRecord | \"locked\" | undefined {\n\tconst runPath = getTeamRunPath(input.cwd, input.runId);\n\tif (!existsSync(runPath)) return undefined;\n\tlet release: (() => void) | undefined;\n\ttry {\n\t\tmkdirSync(getTeamsDir(input.cwd), { recursive: true });\n\t\ttry {\n\t\t\trelease = lockfile.lockSync(runPath, { realpath: false });\n\t\t} catch (error) {\n\t\t\tconst code = error && typeof error === \"object\" && \"code\" in error ? String(error.code) : \"\";\n\t\t\tif (code === \"ELOCKED\") return \"locked\";\n\t\t\treturn undefined;\n\t\t}\n\t\tif (!release) return undefined;\n\t\tconst raw = readFileSync(runPath, \"utf8\");\n\t\tconst existing = JSON.parse(raw) as TeamRunRecord;\n\t\tconst nextTasks = existing.tasks.map((task) => (task.id === input.taskId ? { ...task, status: input.status } : task));\n\t\tif (!nextTasks.some((task) => task.id === input.taskId)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst next: TeamRunRecord = {\n\t\t\t...existing,\n\t\t\ttasks: nextTasks,\n\t\t};\n\t\twriteFileSync(runPath, JSON.stringify(next, null, 2), \"utf8\");\n\t\treturn next;\n\t} catch {\n\t\treturn undefined;\n\t} finally {\n\t\trelease?.();\n\t}\n}\n\nexport function updateTeamTaskStatus(input: {\n\tcwd: string;\n\trunId: string;\n\ttaskId: string;\n\tstatus: TeamTaskStatus;\n}): TeamRunRecord | undefined {\n\tconst result = tryUpdateTeamTaskStatus(input);\n\tif (result === \"locked\") {\n\t\t// Non-blocking reliability path: queue status update for retry instead of dropping lifecycle transitions.\n\t\tqueuePendingStatusUpdate(input);\n\t\treturn undefined;\n\t}\n\tconst key = pendingStatusKey(input);\n\tif (pendingStatusUpdates.has(key) && result) {\n\t\tpendingStatusUpdates.delete(key);\n\t}\n\treturn result;\n}\n"]}
@@ -8,6 +8,7 @@ export declare class FooterDataProvider {
8
8
  private gitWatcher;
9
9
  private branchChangeCallbacks;
10
10
  private availableProviderCount;
11
+ private swarmBusy;
11
12
  constructor();
12
13
  /** Current git branch, null if not in repo, "detached" if detached HEAD */
13
14
  getGitBranch(): string | null;
@@ -21,12 +22,16 @@ export declare class FooterDataProvider {
21
22
  clearExtensionStatuses(): void;
22
23
  /** Number of unique providers with available models (for footer display) */
23
24
  getAvailableProviderCount(): number;
25
+ /** True while a foreground /swarm run is executing. */
26
+ getSwarmBusy(): boolean;
24
27
  /** Internal: update available provider count */
25
28
  setAvailableProviderCount(count: number): void;
29
+ /** Internal: update swarm busy status for footer state rendering. */
30
+ setSwarmBusy(busy: boolean): void;
26
31
  /** Internal: cleanup */
27
32
  dispose(): void;
28
33
  private setupGitWatcher;
29
34
  }
30
35
  /** Read-only view for extensions - excludes setExtensionStatus, setAvailableProviderCount and dispose */
31
- export type ReadonlyFooterDataProvider = Pick<FooterDataProvider, "getGitBranch" | "getExtensionStatuses" | "getAvailableProviderCount" | "onBranchChange">;
36
+ export type ReadonlyFooterDataProvider = Pick<FooterDataProvider, "getGitBranch" | "getExtensionStatuses" | "getAvailableProviderCount" | "getSwarmBusy" | "onBranchChange">;
32
37
  //# sourceMappingURL=footer-data-provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"footer-data-provider.d.ts","sourceRoot":"","sources":["../../src/core/footer-data-provider.ts"],"names":[],"mappings":"AAmCA;;;GAGG;AACH,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,YAAY,CAAwC;IAC5D,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,qBAAqB,CAAyB;IACtD,OAAO,CAAC,sBAAsB,CAAK;;IAMnC,2EAA2E;IAC3E,YAAY,IAAI,MAAM,GAAG,IAAI;IAiB7B,wDAAwD;IACxD,oBAAoB,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC;IAInD,qEAAqE;IACrE,cAAc,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAKhD,qCAAqC;IACrC,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAQ/D,yCAAyC;IACzC,sBAAsB,IAAI,IAAI;IAI9B,4EAA4E;IAC5E,yBAAyB,IAAI,MAAM;IAInC,gDAAgD;IAChD,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI9C,wBAAwB;IACxB,OAAO,IAAI,IAAI;IAQf,OAAO,CAAC,eAAe;CAyBvB;AAED,yGAAyG;AACzG,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAC5C,kBAAkB,EAClB,cAAc,GAAG,sBAAsB,GAAG,2BAA2B,GAAG,gBAAgB,CACxF,CAAC"}
1
+ {"version":3,"file":"footer-data-provider.d.ts","sourceRoot":"","sources":["../../src/core/footer-data-provider.ts"],"names":[],"mappings":"AAmCA;;;GAGG;AACH,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,YAAY,CAAwC;IAC5D,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,qBAAqB,CAAyB;IACtD,OAAO,CAAC,sBAAsB,CAAK;IACnC,OAAO,CAAC,SAAS,CAAS;;IAM1B,2EAA2E;IAC3E,YAAY,IAAI,MAAM,GAAG,IAAI;IAiB7B,wDAAwD;IACxD,oBAAoB,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC;IAInD,qEAAqE;IACrE,cAAc,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAKhD,qCAAqC;IACrC,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAQ/D,yCAAyC;IACzC,sBAAsB,IAAI,IAAI;IAI9B,4EAA4E;IAC5E,yBAAyB,IAAI,MAAM;IAInC,uDAAuD;IACvD,YAAY,IAAI,OAAO;IAIvB,gDAAgD;IAChD,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI9C,qEAAqE;IACrE,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAIjC,wBAAwB;IACxB,OAAO,IAAI,IAAI;IAQf,OAAO,CAAC,eAAe;CAyBvB;AAED,yGAAyG;AACzG,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAC5C,kBAAkB,EAClB,cAAc,GAAG,sBAAsB,GAAG,2BAA2B,GAAG,cAAc,GAAG,gBAAgB,CACzG,CAAC"}
@@ -47,6 +47,7 @@ export class FooterDataProvider {
47
47
  this.gitWatcher = null;
48
48
  this.branchChangeCallbacks = new Set();
49
49
  this.availableProviderCount = 0;
50
+ this.swarmBusy = false;
50
51
  this.setupGitWatcher();
51
52
  }
52
53
  /** Current git branch, null if not in repo, "detached" if detached HEAD */
@@ -93,10 +94,18 @@ export class FooterDataProvider {
93
94
  getAvailableProviderCount() {
94
95
  return this.availableProviderCount;
95
96
  }
97
+ /** True while a foreground /swarm run is executing. */
98
+ getSwarmBusy() {
99
+ return this.swarmBusy;
100
+ }
96
101
  /** Internal: update available provider count */
97
102
  setAvailableProviderCount(count) {
98
103
  this.availableProviderCount = count;
99
104
  }
105
+ /** Internal: update swarm busy status for footer state rendering. */
106
+ setSwarmBusy(busy) {
107
+ this.swarmBusy = busy;
108
+ }
100
109
  /** Internal: cleanup */
101
110
  dispose() {
102
111
  if (this.gitWatcher) {
@@ -1 +1 @@
1
- {"version":3,"file":"footer-data-provider.js","sourceRoot":"","sources":["../../src/core/footer-data-provider.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, type FSWatcher, readFileSync, statSync, watch } from \"fs\";\nimport { dirname, join, resolve } from \"path\";\n\n/**\n * Find the git HEAD path by walking up from cwd.\n * Handles both regular git repos (.git is a directory) and worktrees (.git is a file).\n */\nfunction findGitHeadPath(): string | null {\n\tlet dir = process.cwd();\n\twhile (true) {\n\t\tconst gitPath = join(dir, \".git\");\n\t\tif (existsSync(gitPath)) {\n\t\t\ttry {\n\t\t\t\tconst stat = statSync(gitPath);\n\t\t\t\tif (stat.isFile()) {\n\t\t\t\t\tconst content = readFileSync(gitPath, \"utf8\").trim();\n\t\t\t\t\tif (content.startsWith(\"gitdir: \")) {\n\t\t\t\t\t\tconst gitDir = content.slice(8);\n\t\t\t\t\t\tconst headPath = resolve(dir, gitDir, \"HEAD\");\n\t\t\t\t\t\tif (existsSync(headPath)) return headPath;\n\t\t\t\t\t}\n\t\t\t\t} else if (stat.isDirectory()) {\n\t\t\t\t\tconst headPath = join(gitPath, \"HEAD\");\n\t\t\t\t\tif (existsSync(headPath)) return headPath;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) return null;\n\t\tdir = parent;\n\t}\n}\n\n/**\n * Provides git branch and extension statuses - data not otherwise accessible to extensions.\n * Token stats, model info available via ctx.sessionManager and ctx.model.\n */\nexport class FooterDataProvider {\n\tprivate extensionStatuses = new Map<string, string>();\n\tprivate cachedBranch: string | null | undefined = undefined;\n\tprivate gitWatcher: FSWatcher | null = null;\n\tprivate branchChangeCallbacks = new Set<() => void>();\n\tprivate availableProviderCount = 0;\n\n\tconstructor() {\n\t\tthis.setupGitWatcher();\n\t}\n\n\t/** Current git branch, null if not in repo, \"detached\" if detached HEAD */\n\tgetGitBranch(): string | null {\n\t\tif (this.cachedBranch !== undefined) return this.cachedBranch;\n\n\t\ttry {\n\t\t\tconst gitHeadPath = findGitHeadPath();\n\t\t\tif (!gitHeadPath) {\n\t\t\t\tthis.cachedBranch = null;\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst content = readFileSync(gitHeadPath, \"utf8\").trim();\n\t\t\tthis.cachedBranch = content.startsWith(\"ref: refs/heads/\") ? content.slice(16) : \"detached\";\n\t\t} catch {\n\t\t\tthis.cachedBranch = null;\n\t\t}\n\t\treturn this.cachedBranch;\n\t}\n\n\t/** Extension status texts set via ctx.ui.setStatus() */\n\tgetExtensionStatuses(): ReadonlyMap<string, string> {\n\t\treturn this.extensionStatuses;\n\t}\n\n\t/** Subscribe to git branch changes. Returns unsubscribe function. */\n\tonBranchChange(callback: () => void): () => void {\n\t\tthis.branchChangeCallbacks.add(callback);\n\t\treturn () => this.branchChangeCallbacks.delete(callback);\n\t}\n\n\t/** Internal: set extension status */\n\tsetExtensionStatus(key: string, text: string | undefined): void {\n\t\tif (text === undefined) {\n\t\t\tthis.extensionStatuses.delete(key);\n\t\t} else {\n\t\t\tthis.extensionStatuses.set(key, text);\n\t\t}\n\t}\n\n\t/** Internal: clear extension statuses */\n\tclearExtensionStatuses(): void {\n\t\tthis.extensionStatuses.clear();\n\t}\n\n\t/** Number of unique providers with available models (for footer display) */\n\tgetAvailableProviderCount(): number {\n\t\treturn this.availableProviderCount;\n\t}\n\n\t/** Internal: update available provider count */\n\tsetAvailableProviderCount(count: number): void {\n\t\tthis.availableProviderCount = count;\n\t}\n\n\t/** Internal: cleanup */\n\tdispose(): void {\n\t\tif (this.gitWatcher) {\n\t\t\tthis.gitWatcher.close();\n\t\t\tthis.gitWatcher = null;\n\t\t}\n\t\tthis.branchChangeCallbacks.clear();\n\t}\n\n\tprivate setupGitWatcher(): void {\n\t\tif (this.gitWatcher) {\n\t\t\tthis.gitWatcher.close();\n\t\t\tthis.gitWatcher = null;\n\t\t}\n\n\t\tconst gitHeadPath = findGitHeadPath();\n\t\tif (!gitHeadPath) return;\n\n\t\t// Watch the directory containing HEAD, not HEAD itself.\n\t\t// Git uses atomic writes (write temp, rename over HEAD), which changes the inode.\n\t\t// fs.watch on a file stops working after the inode changes.\n\t\tconst gitDir = dirname(gitHeadPath);\n\n\t\ttry {\n\t\t\tthis.gitWatcher = watch(gitDir, (_eventType, filename) => {\n\t\t\t\tif (filename === \"HEAD\") {\n\t\t\t\t\tthis.cachedBranch = undefined;\n\t\t\t\t\tfor (const cb of this.branchChangeCallbacks) cb();\n\t\t\t\t}\n\t\t\t});\n\t\t} catch {\n\t\t\t// Silently fail if we can't watch\n\t\t}\n\t}\n}\n\n/** Read-only view for extensions - excludes setExtensionStatus, setAvailableProviderCount and dispose */\nexport type ReadonlyFooterDataProvider = Pick<\n\tFooterDataProvider,\n\t\"getGitBranch\" | \"getExtensionStatuses\" | \"getAvailableProviderCount\" | \"onBranchChange\"\n>;\n"]}
1
+ {"version":3,"file":"footer-data-provider.js","sourceRoot":"","sources":["../../src/core/footer-data-provider.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, type FSWatcher, readFileSync, statSync, watch } from \"fs\";\nimport { dirname, join, resolve } from \"path\";\n\n/**\n * Find the git HEAD path by walking up from cwd.\n * Handles both regular git repos (.git is a directory) and worktrees (.git is a file).\n */\nfunction findGitHeadPath(): string | null {\n\tlet dir = process.cwd();\n\twhile (true) {\n\t\tconst gitPath = join(dir, \".git\");\n\t\tif (existsSync(gitPath)) {\n\t\t\ttry {\n\t\t\t\tconst stat = statSync(gitPath);\n\t\t\t\tif (stat.isFile()) {\n\t\t\t\t\tconst content = readFileSync(gitPath, \"utf8\").trim();\n\t\t\t\t\tif (content.startsWith(\"gitdir: \")) {\n\t\t\t\t\t\tconst gitDir = content.slice(8);\n\t\t\t\t\t\tconst headPath = resolve(dir, gitDir, \"HEAD\");\n\t\t\t\t\t\tif (existsSync(headPath)) return headPath;\n\t\t\t\t\t}\n\t\t\t\t} else if (stat.isDirectory()) {\n\t\t\t\t\tconst headPath = join(gitPath, \"HEAD\");\n\t\t\t\t\tif (existsSync(headPath)) return headPath;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) return null;\n\t\tdir = parent;\n\t}\n}\n\n/**\n * Provides git branch and extension statuses - data not otherwise accessible to extensions.\n * Token stats, model info available via ctx.sessionManager and ctx.model.\n */\nexport class FooterDataProvider {\n\tprivate extensionStatuses = new Map<string, string>();\n\tprivate cachedBranch: string | null | undefined = undefined;\n\tprivate gitWatcher: FSWatcher | null = null;\n\tprivate branchChangeCallbacks = new Set<() => void>();\n\tprivate availableProviderCount = 0;\n\tprivate swarmBusy = false;\n\n\tconstructor() {\n\t\tthis.setupGitWatcher();\n\t}\n\n\t/** Current git branch, null if not in repo, \"detached\" if detached HEAD */\n\tgetGitBranch(): string | null {\n\t\tif (this.cachedBranch !== undefined) return this.cachedBranch;\n\n\t\ttry {\n\t\t\tconst gitHeadPath = findGitHeadPath();\n\t\t\tif (!gitHeadPath) {\n\t\t\t\tthis.cachedBranch = null;\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst content = readFileSync(gitHeadPath, \"utf8\").trim();\n\t\t\tthis.cachedBranch = content.startsWith(\"ref: refs/heads/\") ? content.slice(16) : \"detached\";\n\t\t} catch {\n\t\t\tthis.cachedBranch = null;\n\t\t}\n\t\treturn this.cachedBranch;\n\t}\n\n\t/** Extension status texts set via ctx.ui.setStatus() */\n\tgetExtensionStatuses(): ReadonlyMap<string, string> {\n\t\treturn this.extensionStatuses;\n\t}\n\n\t/** Subscribe to git branch changes. Returns unsubscribe function. */\n\tonBranchChange(callback: () => void): () => void {\n\t\tthis.branchChangeCallbacks.add(callback);\n\t\treturn () => this.branchChangeCallbacks.delete(callback);\n\t}\n\n\t/** Internal: set extension status */\n\tsetExtensionStatus(key: string, text: string | undefined): void {\n\t\tif (text === undefined) {\n\t\t\tthis.extensionStatuses.delete(key);\n\t\t} else {\n\t\t\tthis.extensionStatuses.set(key, text);\n\t\t}\n\t}\n\n\t/** Internal: clear extension statuses */\n\tclearExtensionStatuses(): void {\n\t\tthis.extensionStatuses.clear();\n\t}\n\n\t/** Number of unique providers with available models (for footer display) */\n\tgetAvailableProviderCount(): number {\n\t\treturn this.availableProviderCount;\n\t}\n\n\t/** True while a foreground /swarm run is executing. */\n\tgetSwarmBusy(): boolean {\n\t\treturn this.swarmBusy;\n\t}\n\n\t/** Internal: update available provider count */\n\tsetAvailableProviderCount(count: number): void {\n\t\tthis.availableProviderCount = count;\n\t}\n\n\t/** Internal: update swarm busy status for footer state rendering. */\n\tsetSwarmBusy(busy: boolean): void {\n\t\tthis.swarmBusy = busy;\n\t}\n\n\t/** Internal: cleanup */\n\tdispose(): void {\n\t\tif (this.gitWatcher) {\n\t\t\tthis.gitWatcher.close();\n\t\t\tthis.gitWatcher = null;\n\t\t}\n\t\tthis.branchChangeCallbacks.clear();\n\t}\n\n\tprivate setupGitWatcher(): void {\n\t\tif (this.gitWatcher) {\n\t\t\tthis.gitWatcher.close();\n\t\t\tthis.gitWatcher = null;\n\t\t}\n\n\t\tconst gitHeadPath = findGitHeadPath();\n\t\tif (!gitHeadPath) return;\n\n\t\t// Watch the directory containing HEAD, not HEAD itself.\n\t\t// Git uses atomic writes (write temp, rename over HEAD), which changes the inode.\n\t\t// fs.watch on a file stops working after the inode changes.\n\t\tconst gitDir = dirname(gitHeadPath);\n\n\t\ttry {\n\t\t\tthis.gitWatcher = watch(gitDir, (_eventType, filename) => {\n\t\t\t\tif (filename === \"HEAD\") {\n\t\t\t\t\tthis.cachedBranch = undefined;\n\t\t\t\t\tfor (const cb of this.branchChangeCallbacks) cb();\n\t\t\t\t}\n\t\t\t});\n\t\t} catch {\n\t\t\t// Silently fail if we can't watch\n\t\t}\n\t}\n}\n\n/** Read-only view for extensions - excludes setExtensionStatus, setAvailableProviderCount and dispose */\nexport type ReadonlyFooterDataProvider = Pick<\n\tFooterDataProvider,\n\t\"getGitBranch\" | \"getExtensionStatuses\" | \"getAvailableProviderCount\" | \"getSwarmBusy\" | \"onBranchChange\"\n>;\n"]}
@@ -1,15 +1,37 @@
1
- import type { Agent } from "@mariozechner/pi-agent-core";
1
+ import type { Agent, AgentMessage, AgentTool } from "@mariozechner/pi-agent-core";
2
+ import { EventStream } from "@mariozechner/pi-ai";
2
3
  type ToolCallContent = {
3
4
  type: "toolCall";
4
5
  id: string;
5
6
  name: string;
6
7
  arguments: unknown;
7
8
  };
9
+ type ToolResultMessageLike = {
10
+ role: "toolResult";
11
+ toolCallId: string;
12
+ toolName: string;
13
+ content: Array<{
14
+ type: string;
15
+ text?: string;
16
+ data?: string;
17
+ mimeType?: string;
18
+ }>;
19
+ details: unknown;
20
+ isError: boolean;
21
+ timestamp: number;
22
+ };
8
23
  /**
9
24
  * Parallel policy: run tool calls concurrently only when all calls are `task`.
10
25
  * This preserves deterministic behavior for mutating filesystem tools.
11
26
  */
12
27
  export declare function shouldExecuteTaskCallsInParallel(toolCalls: ToolCallContent[]): boolean;
28
+ declare function executeToolCallsWithPolicy(tools: AgentTool<any>[] | undefined, assistantMessage: any, signal: AbortSignal, stream: EventStream<any, any>, getSteeringMessages?: () => Promise<AgentMessage[]>): Promise<{
29
+ toolResults: ToolResultMessageLike[];
30
+ steeringMessages?: AgentMessage[];
31
+ }>;
32
+ export declare const __parallelTaskAgentTestUtils: {
33
+ executeToolCallsWithPolicy: typeof executeToolCallsWithPolicy;
34
+ };
13
35
  /**
14
36
  * Patch a pi-agent-core Agent instance so multiple `task` tool calls in one assistant turn
15
37
  * execute concurrently.
@@ -1 +1 @@
1
- {"version":3,"file":"parallel-task-agent.d.ts","sourceRoot":"","sources":["../../src/core/parallel-task-agent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAA2B,MAAM,6BAA6B,CAAC;AAGlF,KAAK,eAAe,GAAG;IACtB,IAAI,EAAE,UAAU,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACnB,CAAC;AAYF;;;GAGG;AACH,wBAAgB,gCAAgC,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,OAAO,CAEtF;AA+ZD;;;GAGG;AACH,wBAAgB,kCAAkC,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CA8IrE"}
1
+ {"version":3,"file":"parallel-task-agent.d.ts","sourceRoot":"","sources":["../../src/core/parallel-task-agent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAClF,OAAO,EAAE,WAAW,EAAyB,MAAM,qBAAqB,CAAC;AAEzE,KAAK,eAAe,GAAG;IACtB,IAAI,EAAE,UAAU,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,KAAK,qBAAqB,GAAG;IAC5B,IAAI,EAAE,YAAY,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClF,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gCAAgC,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,OAAO,CAEtF;AAySD,iBAAe,0BAA0B,CACxC,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,SAAS,EACnC,gBAAgB,EAAE,GAAG,EACrB,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,EAC7B,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC,YAAY,EAAE,CAAC,GACjD,OAAO,CAAC;IAAE,WAAW,EAAE,qBAAqB,EAAE,CAAC;IAAC,gBAAgB,CAAC,EAAE,YAAY,EAAE,CAAA;CAAE,CAAC,CAMtF;AAED,eAAO,MAAM,4BAA4B;;CAExC,CAAC;AA2MF;;;GAGG;AACH,wBAAgB,kCAAkC,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CA8IrE"}
@@ -15,11 +15,57 @@ function toToolResultError(error) {
15
15
  details: {},
16
16
  };
17
17
  }
18
- function skipToolCall(toolCall, stream) {
19
- const result = {
20
- content: [{ type: "text", text: "Skipped due to queued user message." }],
21
- details: {},
18
+ const STEERING_SKIP_TEXT = "Skipped due to queued user message.";
19
+ const STEERING_POLL_INTERVAL_MS = 40;
20
+ function createSteeringSkipResult() {
21
+ return {
22
+ content: [{ type: "text", text: STEERING_SKIP_TEXT }],
23
+ details: { steeringSkipped: true },
24
+ };
25
+ }
26
+ function createLinkedAbortController(primary, secondary) {
27
+ const controller = new AbortController();
28
+ const cleanup = () => {
29
+ primary.removeEventListener("abort", onAbort);
30
+ secondary.removeEventListener("abort", onAbort);
22
31
  };
32
+ const onAbort = () => {
33
+ cleanup();
34
+ controller.abort();
35
+ };
36
+ if (primary.aborted || secondary.aborted) {
37
+ controller.abort();
38
+ return { signal: controller.signal, cleanup };
39
+ }
40
+ primary.addEventListener("abort", onAbort, { once: true });
41
+ secondary.addEventListener("abort", onAbort, { once: true });
42
+ return { signal: controller.signal, cleanup };
43
+ }
44
+ function waitForSteeringPoll(signal) {
45
+ return new Promise((resolve) => {
46
+ if (signal.aborted) {
47
+ resolve();
48
+ return;
49
+ }
50
+ const timer = setTimeout(() => {
51
+ signal.removeEventListener("abort", onAbort);
52
+ resolve();
53
+ }, STEERING_POLL_INTERVAL_MS);
54
+ const onAbort = () => {
55
+ clearTimeout(timer);
56
+ signal.removeEventListener("abort", onAbort);
57
+ resolve();
58
+ };
59
+ signal.addEventListener("abort", onAbort, { once: true });
60
+ });
61
+ }
62
+ function isAbortError(error) {
63
+ if (!(error instanceof Error))
64
+ return false;
65
+ return error.message.toLowerCase().includes("aborted");
66
+ }
67
+ function skipToolCall(toolCall, stream) {
68
+ const result = createSteeringSkipResult();
23
69
  stream.push({
24
70
  type: "tool_execution_start",
25
71
  toolCallId: toolCall.id,
@@ -31,15 +77,15 @@ function skipToolCall(toolCall, stream) {
31
77
  toolCallId: toolCall.id,
32
78
  toolName: toolCall.name,
33
79
  result,
34
- isError: true,
80
+ isError: false,
35
81
  });
36
82
  const toolResultMessage = {
37
83
  role: "toolResult",
38
84
  toolCallId: toolCall.id,
39
85
  toolName: toolCall.name,
40
86
  content: result.content,
41
- details: {},
42
- isError: true,
87
+ details: result.details,
88
+ isError: false,
43
89
  timestamp: Date.now(),
44
90
  };
45
91
  stream.push({ type: "message_start", message: toolResultMessage });
@@ -113,6 +159,29 @@ async function executeToolCallsSequential(tools, toolCalls, signal, stream, getS
113
159
  return { toolResults: results, steeringMessages };
114
160
  }
115
161
  async function executeToolCallsParallelTasksOnly(tools, toolCalls, signal, stream, getSteeringMessages) {
162
+ const steeringAbortController = new AbortController();
163
+ const linkedAbort = createLinkedAbortController(signal, steeringAbortController.signal);
164
+ const executionSignal = linkedAbort.signal;
165
+ let completedCount = 0;
166
+ let steeringMessages;
167
+ const steeringWatcher = getSteeringMessages
168
+ ? (async () => {
169
+ while (completedCount < toolCalls.length && !executionSignal.aborted) {
170
+ const steering = await getSteeringMessages();
171
+ if (steering.length > 0) {
172
+ steeringMessages = steering;
173
+ if (!signal.aborted && !steeringAbortController.signal.aborted) {
174
+ steeringAbortController.abort();
175
+ }
176
+ return;
177
+ }
178
+ if (completedCount >= toolCalls.length || executionSignal.aborted) {
179
+ return;
180
+ }
181
+ await waitForSteeringPoll(executionSignal);
182
+ }
183
+ })()
184
+ : undefined;
116
185
  const executions = toolCalls.map(async (toolCall, index) => {
117
186
  const tool = tools?.find((item) => item.name === toolCall.name);
118
187
  stream.push({
@@ -128,7 +197,7 @@ async function executeToolCallsParallelTasksOnly(tools, toolCalls, signal, strea
128
197
  throw new Error(`Tool ${toolCall.name} not found`);
129
198
  }
130
199
  const validatedArgs = validateToolArguments(tool, toolCall);
131
- result = await tool.execute(toolCall.id, validatedArgs, signal, (partialResult) => {
200
+ result = await tool.execute(toolCall.id, validatedArgs, executionSignal, (partialResult) => {
132
201
  stream.push({
133
202
  type: "tool_execution_update",
134
203
  toolCallId: toolCall.id,
@@ -139,8 +208,13 @@ async function executeToolCallsParallelTasksOnly(tools, toolCalls, signal, strea
139
208
  });
140
209
  }
141
210
  catch (error) {
142
- result = toToolResultError(error);
143
- isError = true;
211
+ const interruptedBySteering = steeringAbortController.signal.aborted && !signal.aborted;
212
+ const steeringSkip = interruptedBySteering && isAbortError(error);
213
+ result = steeringSkip ? createSteeringSkipResult() : toToolResultError(error);
214
+ isError = !steeringSkip;
215
+ }
216
+ finally {
217
+ completedCount += 1;
144
218
  }
145
219
  stream.push({
146
220
  type: "tool_execution_end",
@@ -162,16 +236,29 @@ async function executeToolCallsParallelTasksOnly(tools, toolCalls, signal, strea
162
236
  stream.push({ type: "message_end", message: toolResultMessage });
163
237
  return { index, toolResultMessage };
164
238
  });
165
- const completed = await Promise.all(executions);
166
- const orderedResults = completed
167
- .slice()
168
- .sort((left, right) => left.index - right.index)
169
- .map((item) => item.toolResultMessage);
170
- const steeringMessages = getSteeringMessages ? await getSteeringMessages() : undefined;
171
- return {
172
- toolResults: orderedResults,
173
- steeringMessages: steeringMessages && steeringMessages.length > 0 ? steeringMessages : undefined,
174
- };
239
+ try {
240
+ const completed = await Promise.all(executions);
241
+ if (steeringWatcher) {
242
+ await steeringWatcher;
243
+ }
244
+ const orderedResults = completed
245
+ .slice()
246
+ .sort((left, right) => left.index - right.index)
247
+ .map((item) => item.toolResultMessage);
248
+ if (!steeringMessages && getSteeringMessages) {
249
+ const trailingSteering = await getSteeringMessages();
250
+ if (trailingSteering.length > 0) {
251
+ steeringMessages = trailingSteering;
252
+ }
253
+ }
254
+ return {
255
+ toolResults: orderedResults,
256
+ steeringMessages: steeringMessages && steeringMessages.length > 0 ? steeringMessages : undefined,
257
+ };
258
+ }
259
+ finally {
260
+ linkedAbort.cleanup();
261
+ }
175
262
  }
176
263
  async function executeToolCallsWithPolicy(tools, assistantMessage, signal, stream, getSteeringMessages) {
177
264
  const toolCalls = assistantMessage.content.filter((content) => content.type === "toolCall");
@@ -180,6 +267,9 @@ async function executeToolCallsWithPolicy(tools, assistantMessage, signal, strea
180
267
  }
181
268
  return executeToolCallsSequential(tools, toolCalls, signal, stream, getSteeringMessages);
182
269
  }
270
+ export const __parallelTaskAgentTestUtils = {
271
+ executeToolCallsWithPolicy,
272
+ };
183
273
  async function streamAssistantResponse(context, config, signal, stream, streamFn) {
184
274
  let messages = context.messages;
185
275
  if (config.transformContext) {