llm-party-cli 0.9.0 → 0.11.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.
package/dist/index.js CHANGED
@@ -3284,7 +3284,7 @@ var require_main = __commonJS((exports) => {
3284
3284
  });
3285
3285
 
3286
3286
  // src/index.ts
3287
- import { readFile as readFile5 } from "fs/promises";
3287
+ import { readFile as readFile6 } from "fs/promises";
3288
3288
  import path11 from "path";
3289
3289
  import { fileURLToPath as fileURLToPath3 } from "url";
3290
3290
 
@@ -36264,43 +36264,6 @@ function Show(props) {
36264
36264
  return props.fallback;
36265
36265
  }, undefined, undefined);
36266
36266
  }
36267
- function Switch(props) {
36268
- const chs = children(() => props.children);
36269
- const switchFunc = createMemo(() => {
36270
- const ch = chs();
36271
- const mps = Array.isArray(ch) ? ch : [ch];
36272
- let func = () => {
36273
- return;
36274
- };
36275
- for (let i = 0;i < mps.length; i++) {
36276
- const index = i;
36277
- const mp = mps[i];
36278
- const prevFunc = func;
36279
- const conditionValue = createMemo(() => prevFunc() ? undefined : mp.when, undefined, undefined);
36280
- const condition = mp.keyed ? conditionValue : createMemo(conditionValue, undefined, {
36281
- equals: (a, b2) => !a === !b2
36282
- });
36283
- func = () => prevFunc() || (condition() ? [index, conditionValue, mp] : undefined);
36284
- }
36285
- return func;
36286
- });
36287
- return createMemo(() => {
36288
- const sel = switchFunc()();
36289
- if (!sel)
36290
- return props.fallback;
36291
- const [index, conditionValue, mp] = sel;
36292
- const child = mp.children;
36293
- const fn = typeof child === "function" && child.length > 0;
36294
- return fn ? untrack(() => child(mp.keyed ? conditionValue() : () => {
36295
- if (untrack(switchFunc)()?.[0] !== index)
36296
- throw narrowedError("Match");
36297
- return conditionValue();
36298
- })) : child;
36299
- }, undefined, undefined);
36300
- }
36301
- function Match(props) {
36302
- return props;
36303
- }
36304
36267
 
36305
36268
  // node_modules/entities/dist/esm/decode-codepoint.js
36306
36269
  var _a;
@@ -37595,13 +37558,8 @@ class ClaudeBaseAdapter {
37595
37558
  }
37596
37559
  return { ...process.env, ...mapped, ...configEnv };
37597
37560
  }
37598
- async send(messages, signal) {
37599
- return await this.querySDK(formatTranscript(messages, this.name, this.humanName), signal);
37600
- }
37601
- async destroy() {
37602
- return;
37603
- }
37604
- async querySDK(transcript, signal) {
37561
+ async* stream(messages, signal) {
37562
+ const transcript = formatTranscript(messages, this.name, this.humanName);
37605
37563
  const executableOpt = this.claudeExecutable ? { pathToClaudeCodeExecutable: this.claudeExecutable } : {};
37606
37564
  const options = {
37607
37565
  cwd: process.cwd(),
@@ -37615,24 +37573,69 @@ class ClaudeBaseAdapter {
37615
37573
  ...this.sessionId ? { resume: this.sessionId } : {},
37616
37574
  ...executableOpt
37617
37575
  };
37576
+ yield { type: "activity", activity: "thinking" };
37618
37577
  try {
37619
37578
  for await (const message of query({ prompt: transcript, options })) {
37620
37579
  if (signal?.aborted) {
37621
- return `[Aborted] ${this.name} was cancelled`;
37580
+ yield { type: "error", message: `[Aborted] ${this.name} was cancelled` };
37581
+ return;
37622
37582
  }
37623
- if (message && typeof message === "object" && "type" in message && "subtype" in message && "session_id" in message && message.type === "system" && message.subtype === "init" && typeof message.session_id === "string") {
37583
+ if (!message || typeof message !== "object")
37584
+ continue;
37585
+ if ("type" in message && "subtype" in message && "session_id" in message && message.type === "system" && message.subtype === "init" && typeof message.session_id === "string") {
37624
37586
  this.sessionId = message.session_id;
37625
37587
  }
37626
- if (message && typeof message === "object" && "result" in message) {
37588
+ if ("type" in message && message.type === "assistant" && "message" in message) {
37589
+ const msg = message.message;
37590
+ const blocks = msg?.content;
37591
+ if (Array.isArray(blocks)) {
37592
+ for (const block of blocks) {
37593
+ if (block.type === "tool_use" && block.name) {
37594
+ const toolName = String(block.name).toLowerCase();
37595
+ if (toolName === "read" || toolName === "glob") {
37596
+ yield { type: "activity", activity: "reading", detail: block.name };
37597
+ } else if (toolName === "write" || toolName === "edit") {
37598
+ yield { type: "activity", activity: "writing", detail: block.name };
37599
+ } else if (toolName === "bash") {
37600
+ yield { type: "activity", activity: "running", detail: "shell" };
37601
+ } else if (toolName === "grep" || toolName === "search" || toolName === "websearch") {
37602
+ yield { type: "activity", activity: "searching", detail: block.name };
37603
+ } else {
37604
+ yield { type: "activity", activity: "thinking", detail: block.name };
37605
+ }
37606
+ } else if (block.type === "thinking") {
37607
+ yield { type: "activity", activity: "thinking" };
37608
+ }
37609
+ }
37610
+ }
37611
+ }
37612
+ if ("type" in message && message.type === "user") {
37613
+ yield { type: "activity", activity: "thinking" };
37614
+ }
37615
+ if ("result" in message) {
37627
37616
  const result = message.result;
37628
- return typeof result === "string" && result.length > 0 ? result : `[No text response from ${this.name}]`;
37617
+ const text = typeof result === "string" && result.length > 0 ? result : `[No text response from ${this.name}]`;
37618
+ yield { type: "activity", activity: "idle" };
37619
+ yield { type: "response", text };
37620
+ return;
37629
37621
  }
37630
37622
  }
37631
37623
  } catch (err) {
37632
37624
  console.log(`[${this.name}] SDK error:`, err);
37633
- throw err;
37625
+ yield { type: "error", message: err instanceof Error ? err.message : String(err) };
37626
+ return;
37634
37627
  }
37635
- return `[No text response from ${this.name}]`;
37628
+ yield { type: "activity", activity: "idle" };
37629
+ yield { type: "response", text: `[No text response from ${this.name}]` };
37630
+ }
37631
+ async destroy() {
37632
+ return;
37633
+ }
37634
+ getSdkSessionId() {
37635
+ return this.sessionId;
37636
+ }
37637
+ setSdkSessionId(id) {
37638
+ this.sessionId = id;
37636
37639
  }
37637
37640
  }
37638
37641
 
@@ -38082,6 +38085,8 @@ class CodexAdapter {
38082
38085
  humanName;
38083
38086
  codex;
38084
38087
  thread;
38088
+ threadId = "";
38089
+ threadOptions = {};
38085
38090
  constructor(name, model, humanName) {
38086
38091
  this.name = name;
38087
38092
  this.model = model;
@@ -38095,31 +38100,84 @@ class CodexAdapter {
38095
38100
  ...config.env?.OPENAI_API_KEY ? { apiKey: config.env.OPENAI_API_KEY } : {},
38096
38101
  ...systemPrompt ? { config: { developer_instructions: systemPrompt } } : {}
38097
38102
  });
38098
- this.thread = this.codex.startThread({
38103
+ this.threadOptions = {
38099
38104
  model: this.model,
38100
38105
  sandboxMode: "danger-full-access",
38101
38106
  workingDirectory: process.cwd(),
38102
38107
  approvalPolicy: "never",
38103
38108
  skipGitRepoCheck: true
38104
- });
38109
+ };
38110
+ if (this.threadId) {
38111
+ try {
38112
+ this.thread = this.codex.resumeThread(this.threadId, this.threadOptions);
38113
+ return;
38114
+ } catch (err) {
38115
+ console.error(`[${this.name}] resumeThread failed, starting fresh:`, err);
38116
+ this.threadId = "";
38117
+ }
38118
+ }
38119
+ this.thread = this.codex.startThread(this.threadOptions);
38120
+ this.threadId = this.thread.id ?? "";
38105
38121
  }
38106
- async send(messages, signal) {
38122
+ async* stream(messages, signal) {
38107
38123
  if (!this.thread) {
38108
- return "[Codex thread not initialized]";
38124
+ yield { type: "error", message: "[Codex thread not initialized]" };
38125
+ return;
38109
38126
  }
38110
38127
  if (signal?.aborted) {
38111
- return "[Aborted] Codex was cancelled";
38128
+ yield { type: "error", message: "[Aborted] Codex was cancelled" };
38129
+ return;
38112
38130
  }
38113
- const turn = await this.thread.run(formatTranscript(messages, this.name, this.humanName));
38114
- if (turn.finalResponse && turn.finalResponse.length > 0) {
38115
- return turn.finalResponse;
38131
+ yield { type: "activity", activity: "thinking" };
38132
+ try {
38133
+ const result = await this.thread.runStreamed(formatTranscript(messages, this.name, this.humanName));
38134
+ let lastAgentMessage = "";
38135
+ for await (const event of result.events) {
38136
+ if (signal?.aborted) {
38137
+ yield { type: "error", message: "[Aborted] Codex was cancelled" };
38138
+ return;
38139
+ }
38140
+ if (event.type === "item.started" || event.type === "item.updated") {
38141
+ const item = event.item;
38142
+ if (item.type === "command_execution") {
38143
+ yield { type: "activity", activity: "running", detail: item.command };
38144
+ } else if (item.type === "file_change") {
38145
+ yield { type: "activity", activity: "writing", detail: "file changes" };
38146
+ } else if (item.type === "reasoning") {
38147
+ yield { type: "activity", activity: "thinking", detail: "reasoning" };
38148
+ } else if (item.type === "web_search") {
38149
+ yield { type: "activity", activity: "searching", detail: item.query };
38150
+ } else if (item.type === "mcp_tool_call") {
38151
+ yield { type: "activity", activity: "running", detail: `${item.server}:${item.tool}` };
38152
+ }
38153
+ }
38154
+ if (event.type === "item.completed") {
38155
+ const item = event.item;
38156
+ if (item.type === "agent_message" && typeof item.text === "string") {
38157
+ lastAgentMessage = item.text;
38158
+ }
38159
+ }
38160
+ }
38161
+ const text = lastAgentMessage.length > 0 ? lastAgentMessage : `[No text response from ${this.name}]`;
38162
+ if (!this.threadId && this.thread?.id) {
38163
+ this.threadId = this.thread.id;
38164
+ }
38165
+ yield { type: "activity", activity: "idle" };
38166
+ yield { type: "response", text };
38167
+ } catch (err) {
38168
+ yield { type: "error", message: err instanceof Error ? err.message : String(err) };
38116
38169
  }
38117
- return "[No text response from Codex]";
38118
38170
  }
38119
38171
  async destroy() {
38120
38172
  this.thread = undefined;
38121
38173
  this.codex = undefined;
38122
38174
  }
38175
+ getSdkSessionId() {
38176
+ return this.threadId;
38177
+ }
38178
+ setSdkSessionId(id) {
38179
+ this.threadId = id;
38180
+ }
38123
38181
  }
38124
38182
 
38125
38183
  // node_modules/@github/copilot-sdk/dist/client.js
@@ -39302,6 +39360,7 @@ class CopilotAdapter {
39302
39360
  systemPrompt = "";
39303
39361
  cliPath;
39304
39362
  timeout = 600000;
39363
+ copilotSessionId = "";
39305
39364
  constructor(name, model, humanName) {
39306
39365
  this.name = name;
39307
39366
  this.model = model;
@@ -39315,64 +39374,180 @@ class CopilotAdapter {
39315
39374
  }
39316
39375
  await this.createSession();
39317
39376
  }
39318
- async send(messages, signal) {
39377
+ async* stream(messages, signal) {
39319
39378
  if (!this.session) {
39320
- return "[Copilot session not initialized]";
39379
+ yield { type: "error", message: "[Copilot session not initialized]" };
39380
+ return;
39321
39381
  }
39322
39382
  if (signal?.aborted) {
39323
- return "[Aborted] Copilot was cancelled";
39383
+ yield { type: "error", message: "[Aborted] Copilot was cancelled" };
39384
+ return;
39324
39385
  }
39386
+ yield { type: "activity", activity: "thinking" };
39325
39387
  const transcript = formatTranscript(messages, this.name, this.humanName);
39388
+ const eventQueue = [];
39389
+ let resolve4 = null;
39390
+ let done = false;
39391
+ const push = (event) => {
39392
+ eventQueue.push(event);
39393
+ if (resolve4) {
39394
+ resolve4();
39395
+ resolve4 = null;
39396
+ }
39397
+ };
39398
+ const waitForEvent = () => {
39399
+ if (eventQueue.length > 0 || done)
39400
+ return Promise.resolve();
39401
+ return new Promise((r) => {
39402
+ resolve4 = r;
39403
+ });
39404
+ };
39405
+ const timer = setTimeout(() => {
39406
+ push({ type: "error", message: `[Timeout] ${this.name} exceeded ${Math.floor(this.timeout / 1000)}s` });
39407
+ done = true;
39408
+ }, this.timeout);
39409
+ const unsubscribe = this.session.on((event) => {
39410
+ if (signal?.aborted) {
39411
+ push({ type: "error", message: `[Aborted] ${this.name} was cancelled` });
39412
+ done = true;
39413
+ return;
39414
+ }
39415
+ if (event.type === "tool.execution_start") {
39416
+ const toolName = String(event.data?.toolName ?? "").toLowerCase();
39417
+ if (toolName === "view" || toolName === "read" || toolName === "report_intent") {
39418
+ push({ type: "activity", activity: "reading", detail: event.data?.toolName });
39419
+ } else if (toolName === "create" || toolName === "edit" || toolName === "write") {
39420
+ push({ type: "activity", activity: "writing", detail: event.data?.toolName });
39421
+ } else if (toolName === "run" || toolName === "bash" || toolName === "shell") {
39422
+ push({ type: "activity", activity: "running", detail: event.data?.toolName });
39423
+ } else if (toolName === "search" || toolName === "grep" || toolName === "find") {
39424
+ push({ type: "activity", activity: "searching", detail: event.data?.toolName });
39425
+ } else {
39426
+ push({ type: "activity", activity: "thinking", detail: event.data?.toolName });
39427
+ }
39428
+ }
39429
+ if (event.type === "tool.execution_complete") {
39430
+ push({ type: "activity", activity: "thinking" });
39431
+ }
39432
+ if (event.type === "assistant.turn_start") {
39433
+ push({ type: "activity", activity: "thinking" });
39434
+ }
39435
+ if (event.type === "assistant.message") {
39436
+ const content = event.data?.content;
39437
+ if (typeof content === "string" && content.length > 0) {
39438
+ push({ type: "activity", activity: "idle" });
39439
+ push({ type: "response", text: content });
39440
+ done = true;
39441
+ }
39442
+ }
39443
+ if (event.type === "session.error") {
39444
+ push({ type: "error", message: event.data?.message ?? "Unknown Copilot error" });
39445
+ done = true;
39446
+ }
39447
+ });
39448
+ const sendSession = async () => {
39449
+ try {
39450
+ await this.session.send({ prompt: transcript });
39451
+ } catch (err) {
39452
+ const message = err instanceof Error ? err.message : String(err);
39453
+ if (message.includes("Session not found")) {
39454
+ await this.createSession();
39455
+ try {
39456
+ await this.session.send({ prompt: transcript });
39457
+ } catch (retryErr) {
39458
+ push({ type: "error", message: retryErr instanceof Error ? retryErr.message : String(retryErr) });
39459
+ done = true;
39460
+ }
39461
+ } else {
39462
+ push({ type: "error", message });
39463
+ done = true;
39464
+ }
39465
+ }
39466
+ };
39467
+ sendSession();
39326
39468
  try {
39327
- return await this.sendToSession(transcript);
39328
- } catch (err) {
39329
- const message = err instanceof Error ? err.message : String(err);
39330
- if (message.includes("Session not found")) {
39331
- await this.createSession();
39332
- return await this.sendToSession(transcript);
39469
+ while (!done || eventQueue.length > 0) {
39470
+ await waitForEvent();
39471
+ while (eventQueue.length > 0) {
39472
+ const event = eventQueue.shift();
39473
+ yield event;
39474
+ if (event.type === "response" || event.type === "error") {
39475
+ clearTimeout(timer);
39476
+ unsubscribe?.();
39477
+ return;
39478
+ }
39479
+ }
39333
39480
  }
39334
- throw err;
39481
+ } finally {
39482
+ clearTimeout(timer);
39483
+ unsubscribe?.();
39335
39484
  }
39485
+ yield { type: "activity", activity: "idle" };
39486
+ yield { type: "response", text: `[No text response from ${this.name}]` };
39336
39487
  }
39337
39488
  async destroy() {
39338
39489
  if (this.session) {
39339
- await this.session.disconnect();
39490
+ try {
39491
+ await this.session.disconnect();
39492
+ } catch (err) {
39493
+ console.error(`[${this.name}] disconnect:`, err);
39494
+ }
39340
39495
  }
39341
39496
  if (this.client) {
39342
- await this.client.stop();
39497
+ try {
39498
+ await this.client.stop();
39499
+ } catch (err) {
39500
+ console.error(`[${this.name}] stop:`, err);
39501
+ }
39343
39502
  }
39344
39503
  }
39504
+ getSdkSessionId() {
39505
+ return this.copilotSessionId;
39506
+ }
39507
+ setSdkSessionId(id) {
39508
+ this.copilotSessionId = id;
39509
+ }
39345
39510
  async createSession() {
39346
39511
  if (this.session) {
39347
39512
  try {
39348
39513
  await this.session.disconnect();
39349
- } catch {}
39514
+ } catch (err) {
39515
+ console.error(`[${this.name}] stale session disconnect:`, err);
39516
+ }
39350
39517
  }
39351
39518
  if (this.client) {
39352
39519
  try {
39353
39520
  await this.client.stop();
39354
- } catch {}
39521
+ } catch (err) {
39522
+ console.error(`[${this.name}] stale client stop:`, err);
39523
+ }
39355
39524
  }
39356
39525
  process.env.NODE_NO_WARNINGS = "1";
39357
39526
  this.client = new CopilotClient({
39358
39527
  ...this.cliPath ? { cliPath: this.cliPath } : {}
39359
39528
  });
39360
39529
  await this.client.start();
39530
+ if (this.copilotSessionId) {
39531
+ try {
39532
+ this.session = await this.client.resumeSession(this.copilotSessionId, {
39533
+ model: this.model,
39534
+ systemMessage: { content: this.systemPrompt },
39535
+ onPermissionRequest: approveAll
39536
+ });
39537
+ return;
39538
+ } catch (err) {
39539
+ console.error(`[${this.name}] resumeSession failed, creating new:`, err);
39540
+ this.copilotSessionId = "";
39541
+ }
39542
+ }
39543
+ const sessionId = `llm-party-${this.name}-${Date.now()}`;
39361
39544
  this.session = await this.client.createSession({
39545
+ sessionId,
39362
39546
  model: this.model,
39363
39547
  systemMessage: { content: this.systemPrompt },
39364
39548
  onPermissionRequest: approveAll
39365
39549
  });
39366
- }
39367
- async sendToSession(transcript) {
39368
- if (!this.session) {
39369
- return "[Copilot session not initialized]";
39370
- }
39371
- const response = await this.session.sendAndWait({ prompt: transcript }, this.timeout);
39372
- if (response && response.data && typeof response.data.content === "string" && response.data.content.length > 0) {
39373
- return response.data.content;
39374
- }
39375
- return "[No text response from Copilot]";
39550
+ this.copilotSessionId = sessionId;
39376
39551
  }
39377
39552
  }
39378
39553
 
@@ -39629,7 +39804,7 @@ async function loadConfig2(configPath) {
39629
39804
  }
39630
39805
 
39631
39806
  // src/orchestrator.ts
39632
- import { appendFile, mkdir as mkdir5, writeFile as writeFile5 } from "fs/promises";
39807
+ import { appendFile, mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
39633
39808
  import { randomBytes } from "crypto";
39634
39809
  import path9 from "path";
39635
39810
 
@@ -39654,6 +39829,88 @@ var DEFAULT_REMINDERS = [
39654
39829
  "Self-memory check: did you receive a correction this session? Write it to your agent file now.",
39655
39830
  "Global awareness: if this work affects other projects, write a one-liner to projects.yml history."
39656
39831
  ];
39832
+ var QUEUE_TTL_MS = 5 * 60 * 1000;
39833
+ var MAX_QUEUE_SIZE = 20;
39834
+
39835
+ class AgentQueueManager {
39836
+ queues = new Map;
39837
+ register(agentName) {
39838
+ this.queues.set(agentName, { processing: false, pending: [] });
39839
+ }
39840
+ enqueue(agentName, message, maxSize = MAX_QUEUE_SIZE) {
39841
+ const queue = this.queues.get(agentName);
39842
+ if (!queue)
39843
+ return false;
39844
+ if (queue.pending.length >= maxSize) {
39845
+ queue.pending.shift();
39846
+ }
39847
+ queue.pending.push(message);
39848
+ return true;
39849
+ }
39850
+ drain(agentName, ttlMs = QUEUE_TTL_MS) {
39851
+ const queue = this.queues.get(agentName);
39852
+ if (!queue)
39853
+ return [];
39854
+ const now = Date.now();
39855
+ const valid = queue.pending.filter((m2) => now - new Date(m2.queuedAt).getTime() < ttlMs);
39856
+ queue.pending = [];
39857
+ return valid;
39858
+ }
39859
+ isProcessing(agentName) {
39860
+ return this.queues.get(agentName)?.processing ?? false;
39861
+ }
39862
+ setProcessing(agentName, value) {
39863
+ const queue = this.queues.get(agentName);
39864
+ if (queue)
39865
+ queue.processing = value;
39866
+ }
39867
+ hasPending(agentName) {
39868
+ return (this.queues.get(agentName)?.pending.length ?? 0) > 0;
39869
+ }
39870
+ pendingCount(agentName) {
39871
+ return this.queues.get(agentName)?.pending.length ?? 0;
39872
+ }
39873
+ get anyProcessing() {
39874
+ for (const q2 of this.queues.values()) {
39875
+ if (q2.processing)
39876
+ return true;
39877
+ }
39878
+ return false;
39879
+ }
39880
+ setController(agentName, controller) {
39881
+ const queue = this.queues.get(agentName);
39882
+ if (queue)
39883
+ queue.controller = controller;
39884
+ }
39885
+ cancelled = new Set;
39886
+ abortAgent(agentName) {
39887
+ const queue = this.queues.get(agentName);
39888
+ if (!queue)
39889
+ return;
39890
+ this.cancelled.add(agentName);
39891
+ queue.controller?.abort();
39892
+ queue.pending = [];
39893
+ queue.processing = false;
39894
+ }
39895
+ wasCancelled(agentName) {
39896
+ return this.cancelled.has(agentName);
39897
+ }
39898
+ clearCancelled(agentName) {
39899
+ this.cancelled.delete(agentName);
39900
+ }
39901
+ abortAll() {
39902
+ for (const q2 of this.queues.values()) {
39903
+ q2.controller?.abort();
39904
+ q2.pending = [];
39905
+ }
39906
+ }
39907
+ clearAll() {
39908
+ for (const q2 of this.queues.values()) {
39909
+ q2.pending = [];
39910
+ q2.processing = false;
39911
+ }
39912
+ }
39913
+ }
39657
39914
 
39658
39915
  class Orchestrator {
39659
39916
  agents;
@@ -39669,7 +39926,16 @@ class Orchestrator {
39669
39926
  contextWindowSize;
39670
39927
  reminderInterval;
39671
39928
  reminderCursors = new Map;
39929
+ maxAutoHops;
39930
+ queueTtlMs;
39931
+ maxQueueSize;
39672
39932
  messageId = 0;
39933
+ stickyTargets;
39934
+ queueManager = new AgentQueueManager;
39935
+ manifestSavePromise = Promise.resolve();
39936
+ onMessage = () => {};
39937
+ onActivity = () => {};
39938
+ onSystem = () => {};
39673
39939
  constructor(agents, humanName = "USER", agentTags, humanTag, defaultTimeout = 600000, agentTimeouts, options) {
39674
39940
  this.agents = new Map(agents.map((agent) => [agent.name, agent]));
39675
39941
  this.agentTags = new Map(agents.map((agent) => [agent.name, agentTags?.[agent.name] ?? toTag(agent.name)]));
@@ -39679,12 +39945,21 @@ class Orchestrator {
39679
39945
  this.agentTimeouts = new Map(Object.entries(agentTimeouts ?? {}));
39680
39946
  this.contextWindowSize = options?.contextWindowSize ?? 16;
39681
39947
  this.reminderInterval = options?.reminderInterval ?? 8;
39948
+ this.maxAutoHops = options?.maxAutoHops ?? 15;
39949
+ this.queueTtlMs = options?.queueTtlMs ?? QUEUE_TTL_MS;
39950
+ this.maxQueueSize = options?.maxQueueSize ?? MAX_QUEUE_SIZE;
39682
39951
  this.sessionId = createSessionId();
39683
39952
  this.transcriptPath = path9.resolve(".llm-party", "sessions", `transcript-${this.sessionId}.jsonl`);
39684
39953
  for (const agent of agents) {
39685
39954
  this.lastSeenByAgent.set(agent.name, 0);
39955
+ this.queueManager.register(agent.name);
39686
39956
  }
39687
39957
  }
39958
+ setCallbacks(onMessage, onActivity, onSystem) {
39959
+ this.onMessage = onMessage;
39960
+ this.onActivity = onActivity;
39961
+ this.onSystem = onSystem;
39962
+ }
39688
39963
  getSessionId() {
39689
39964
  return this.sessionId;
39690
39965
  }
@@ -39697,12 +39972,29 @@ class Orchestrator {
39697
39972
  getHumanTag() {
39698
39973
  return this.humanTag;
39699
39974
  }
39975
+ get dispatching() {
39976
+ return this.queueManager.anyProcessing;
39977
+ }
39978
+ getStickyTarget() {
39979
+ return this.stickyTargets;
39980
+ }
39981
+ setStickyTarget(targets) {
39982
+ this.stickyTargets = targets;
39983
+ }
39984
+ getQueueStatus() {
39985
+ return this.listAgents().map((a) => ({
39986
+ name: a.name,
39987
+ processing: this.queueManager.isProcessing(a.name),
39988
+ pending: this.queueManager.pendingCount(a.name)
39989
+ }));
39990
+ }
39700
39991
  clearConversation() {
39701
39992
  this.conversation.length = 0;
39702
39993
  this.messageId = 0;
39703
39994
  for (const agent of this.agents.keys()) {
39704
39995
  this.lastSeenByAgent.set(agent, 0);
39705
39996
  }
39997
+ this.queueManager.clearAll();
39706
39998
  this.sessionId = createSessionId();
39707
39999
  this.transcriptPath = path9.resolve(".llm-party", "sessions", `transcript-${this.sessionId}.jsonl`);
39708
40000
  }
@@ -39739,62 +40031,165 @@ class Orchestrator {
39739
40031
  const tag = this.agentTags.get(agent.name) ?? toTag(agent.name);
39740
40032
  return agent.name.toLowerCase() === normalized || tag.toLowerCase() === normalized;
39741
40033
  }).map((agent) => agent.name);
39742
- if (byName.length > 0) {
40034
+ if (byName.length > 0)
39743
40035
  return byName;
39744
- }
39745
40036
  return Array.from(this.agents.values()).filter((agent) => agent.provider.toLowerCase() === normalized).map((agent) => agent.name);
39746
40037
  }
39747
- async fanOut(targetAgentNames) {
39748
- return this.fanOutWithProgress(targetAgentNames, () => {});
39749
- }
39750
- async fanOutWithProgress(targetAgentNames, onMessage) {
39751
- const requestedTargets = targetAgentNames && targetAgentNames.length > 0 ? targetAgentNames : Array.from(this.agents.keys());
39752
- const targets = requestedTargets.map((name) => this.agents.get(name)).filter((agent) => Boolean(agent));
39753
- const historyMaxId = this.messageId;
39754
- const settled = await Promise.allSettled(targets.map(async (agent) => {
39755
- const lastSeen = this.lastSeenByAgent.get(agent.name) ?? 0;
39756
- const unseen = this.conversation.filter((msg) => msg.id > lastSeen && msg.from.toUpperCase() !== agent.name.toUpperCase());
39757
- if (unseen.length === 0) {
39758
- this.lastSeenByAgent.set(agent.name, historyMaxId);
39759
- return null;
39760
- }
39761
- const inputMessages = this.buildInputForAgent(agent.name, unseen);
39762
- const responseText = await this.sendWithTimeout(agent, inputMessages, this.timeoutFor(agent.name));
39763
- const response = {
39764
- id: ++this.messageId,
39765
- from: agent.name.toUpperCase(),
39766
- text: responseText,
39767
- createdAt: new Date().toISOString()
39768
- };
39769
- this.lastSeenByAgent.set(agent.name, historyMaxId);
39770
- this.conversation.push(response);
39771
- await this.appendTranscript(response);
39772
- onMessage(response);
39773
- return response;
39774
- }));
39775
- const results = [];
39776
- for (let idx = 0;idx < settled.length; idx++) {
39777
- const item = settled[idx];
39778
- if (item.status === "fulfilled") {
39779
- if (item.value) {
39780
- results.push(item.value);
39781
- }
40038
+ dispatchToTargets(targetAgentNames, chainHops = 0) {
40039
+ for (const name of targetAgentNames) {
40040
+ const agent = this.agents.get(name);
40041
+ if (!agent)
39782
40042
  continue;
40043
+ if (this.queueManager.isProcessing(name)) {
40044
+ this.queueManager.enqueue(name, {
40045
+ from: "dispatch",
40046
+ text: "",
40047
+ queuedAt: new Date().toISOString(),
40048
+ chainHops
40049
+ }, this.maxQueueSize);
40050
+ this.onSystem(`Queued for ${name} (busy, ${this.queueManager.pendingCount(name)} pending)`);
40051
+ } else {
40052
+ this.processAgent(name, chainHops);
39783
40053
  }
39784
- const agent = targets[idx];
39785
- const response = {
39786
- id: ++this.messageId,
39787
- from: agent.name.toUpperCase(),
39788
- text: `[Adapter Error] ${item.reason instanceof Error ? item.reason.message : String(item.reason)}`,
39789
- createdAt: new Date().toISOString()
39790
- };
39791
- this.lastSeenByAgent.set(agent.name, historyMaxId);
39792
- this.conversation.push(response);
39793
- await this.appendTranscript(response);
39794
- onMessage(response);
39795
- results.push(response);
39796
40054
  }
39797
- return results;
40055
+ }
40056
+ async processAgent(agentName, chainHops) {
40057
+ const agent = this.agents.get(agentName);
40058
+ if (!agent)
40059
+ return;
40060
+ this.queueManager.clearCancelled(agentName);
40061
+ this.queueManager.setProcessing(agentName, true);
40062
+ this.onActivity(agentName, "thinking");
40063
+ const historyMaxId = this.messageId;
40064
+ const lastSeen = this.lastSeenByAgent.get(agentName) ?? 0;
40065
+ const unseen = this.conversation.filter((msg) => msg.id > lastSeen && msg.from.toUpperCase() !== agentName.toUpperCase());
40066
+ if (unseen.length === 0) {
40067
+ this.lastSeenByAgent.set(agentName, historyMaxId);
40068
+ this.queueManager.setProcessing(agentName, false);
40069
+ this.onActivity(agentName, "idle");
40070
+ this.drainQueue(agentName);
40071
+ return;
40072
+ }
40073
+ const inputMessages = this.buildInputForAgent(agentName, unseen);
40074
+ const responseText = await this.streamWithTimeout(agentName, agent, inputMessages, this.timeoutFor(agentName));
40075
+ if (this.queueManager.wasCancelled(agentName)) {
40076
+ this.lastSeenByAgent.set(agentName, historyMaxId);
40077
+ this.queueManager.setProcessing(agentName, false);
40078
+ this.onActivity(agentName, "idle");
40079
+ return;
40080
+ }
40081
+ const response = {
40082
+ id: ++this.messageId,
40083
+ from: agentName.toUpperCase(),
40084
+ text: responseText,
40085
+ createdAt: new Date().toISOString()
40086
+ };
40087
+ this.lastSeenByAgent.set(agentName, historyMaxId);
40088
+ this.conversation.push(response);
40089
+ await this.appendTranscript(response);
40090
+ this.onMessage(response);
40091
+ await this.saveManifest();
40092
+ this.queueManager.setProcessing(agentName, false);
40093
+ const isError = responseText.startsWith("[Timeout]") || responseText.startsWith("[Error]") || responseText.startsWith("[Adapter Error]");
40094
+ if (!isError) {
40095
+ this.processHandoffs(response, chainHops);
40096
+ }
40097
+ this.drainQueue(agentName);
40098
+ }
40099
+ processHandoffs(response, currentHops) {
40100
+ const selectors = extractNextSelectors([response]);
40101
+ if (selectors.length === 0)
40102
+ return;
40103
+ const humanTag = this.humanTag.toLowerCase();
40104
+ const humanName = this.humanName.toLowerCase();
40105
+ const agentSelectors = selectors.filter((s) => {
40106
+ const n = s.toLowerCase();
40107
+ return n !== humanTag && n !== humanName;
40108
+ });
40109
+ if (agentSelectors.length === 0)
40110
+ return;
40111
+ const resolvedTargets = Array.from(new Set(agentSelectors.flatMap((s) => this.resolveTargets(s))));
40112
+ if (resolvedTargets.length === 0)
40113
+ return;
40114
+ const nextHops = currentHops + 1;
40115
+ if (nextHops >= this.maxAutoHops) {
40116
+ this.onSystem(`Stopped auto-handoff after ${this.maxAutoHops} hops.`);
40117
+ return;
40118
+ }
40119
+ this.onSystem(`Auto handoff via @next to ${resolvedTargets.join(", ")}`);
40120
+ this.dispatchToTargets(resolvedTargets, nextHops);
40121
+ }
40122
+ drainQueue(agentName) {
40123
+ if (!this.queueManager.hasPending(agentName)) {
40124
+ this.onActivity(agentName, "idle");
40125
+ return;
40126
+ }
40127
+ const pending = this.queueManager.drain(agentName, this.queueTtlMs);
40128
+ if (pending.length === 0) {
40129
+ this.onActivity(agentName, "idle");
40130
+ return;
40131
+ }
40132
+ const maxHops = Math.max(...pending.map((m2) => m2.chainHops));
40133
+ if (maxHops >= this.maxAutoHops) {
40134
+ this.onSystem(`Stopped auto-handoff for ${agentName} after ${this.maxAutoHops} hops.`);
40135
+ this.onActivity(agentName, "idle");
40136
+ return;
40137
+ }
40138
+ this.processAgent(agentName, maxHops);
40139
+ }
40140
+ async streamWithTimeout(agentName, agent, messages, timeoutMs) {
40141
+ const controller = new AbortController;
40142
+ this.queueManager.setController(agentName, controller);
40143
+ let timer;
40144
+ let resolved = false;
40145
+ return new Promise((resolve4) => {
40146
+ timer = setTimeout(() => {
40147
+ if (!resolved) {
40148
+ resolved = true;
40149
+ controller.abort();
40150
+ this.onActivity(agentName, "error", "timeout");
40151
+ resolve4(`[Timeout] ${agentName} exceeded ${Math.floor(timeoutMs / 1000)}s`);
40152
+ }
40153
+ }, timeoutMs);
40154
+ (async () => {
40155
+ try {
40156
+ for await (const event of agent.stream(messages, controller.signal)) {
40157
+ if (resolved)
40158
+ return;
40159
+ if (event.type === "activity") {
40160
+ this.onActivity(agentName, event.activity, event.detail);
40161
+ } else if (event.type === "response") {
40162
+ resolved = true;
40163
+ if (timer)
40164
+ clearTimeout(timer);
40165
+ resolve4(event.text);
40166
+ return;
40167
+ } else if (event.type === "error") {
40168
+ resolved = true;
40169
+ if (timer)
40170
+ clearTimeout(timer);
40171
+ this.onActivity(agentName, "error");
40172
+ resolve4(`[Error] ${agentName}: ${event.message}`);
40173
+ return;
40174
+ }
40175
+ }
40176
+ if (!resolved) {
40177
+ resolved = true;
40178
+ if (timer)
40179
+ clearTimeout(timer);
40180
+ resolve4(`[No text response from ${agentName}]`);
40181
+ }
40182
+ } catch (err) {
40183
+ if (!resolved) {
40184
+ resolved = true;
40185
+ if (timer)
40186
+ clearTimeout(timer);
40187
+ this.onActivity(agentName, "error");
40188
+ resolve4(`[Error] ${agentName}: ${err instanceof Error ? err.message : String(err)}`);
40189
+ }
40190
+ }
40191
+ })();
40192
+ });
39798
40193
  }
39799
40194
  async appendTranscript(message) {
39800
40195
  try {
@@ -39807,23 +40202,91 @@ class Orchestrator {
39807
40202
  async saveHistory(targetPath) {
39808
40203
  await writeFile5(targetPath, JSON.stringify(this.conversation, null, 2), "utf8");
39809
40204
  }
39810
- async sendWithTimeout(agent, messages, timeoutMs) {
39811
- const controller = new AbortController;
39812
- let timer;
39813
- const timeoutPromise = new Promise((resolve4) => {
39814
- timer = setTimeout(() => {
39815
- controller.abort();
39816
- resolve4(`[Timeout] ${agent.name} exceeded ${Math.floor(timeoutMs / 1000)}s`);
39817
- }, timeoutMs);
40205
+ async loadTranscript(sessionId) {
40206
+ const transcriptFile = path9.resolve(".llm-party", "sessions", `transcript-${sessionId}.jsonl`);
40207
+ const content = await readFile5(transcriptFile, "utf8");
40208
+ const lines = content.trim().split(`
40209
+ `).filter(Boolean);
40210
+ const messages = lines.map((line) => JSON.parse(line));
40211
+ this.conversation.length = 0;
40212
+ this.conversation.push(...messages);
40213
+ const maxId = messages.length > 0 ? Math.max(...messages.map((m2) => m2.id)) : 0;
40214
+ this.messageId = maxId;
40215
+ for (const agent of this.agents.keys()) {
40216
+ this.lastSeenByAgent.set(agent, maxId);
40217
+ }
40218
+ this.sessionId = sessionId;
40219
+ this.transcriptPath = transcriptFile;
40220
+ await this.loadManifest();
40221
+ return messages;
40222
+ }
40223
+ hasMessages() {
40224
+ return this.conversation.length > 0;
40225
+ }
40226
+ manifestPath() {
40227
+ const dir = path9.dirname(this.transcriptPath);
40228
+ const base = path9.basename(this.transcriptPath, ".jsonl");
40229
+ return path9.join(dir, `${base}.manifest.json`);
40230
+ }
40231
+ async saveManifest() {
40232
+ this.manifestSavePromise = this.manifestSavePromise.then(async () => {
40233
+ try {
40234
+ const agents = {};
40235
+ for (const [name, adapter] of this.agents) {
40236
+ const sid = adapter.getSdkSessionId();
40237
+ agents[name] = {
40238
+ provider: adapter.provider,
40239
+ sdkSessionId: sid || "",
40240
+ lastSeenId: this.lastSeenByAgent.get(name) ?? 0
40241
+ };
40242
+ }
40243
+ const manifest = {
40244
+ orchestratorSessionId: this.sessionId,
40245
+ messageId: this.messageId,
40246
+ stickyTarget: this.stickyTargets,
40247
+ agents
40248
+ };
40249
+ await writeFile5(this.manifestPath(), JSON.stringify(manifest, null, 2), "utf8");
40250
+ } catch (err) {
40251
+ console.error("[manifest] write failed:", err);
40252
+ }
39818
40253
  });
40254
+ return this.manifestSavePromise;
40255
+ }
40256
+ async loadManifest() {
39819
40257
  try {
39820
- return await Promise.race([agent.send(messages, controller.signal), timeoutPromise]);
39821
- } finally {
39822
- if (timer) {
39823
- clearTimeout(timer);
40258
+ const content = await readFile5(this.manifestPath(), "utf8");
40259
+ const manifest = JSON.parse(content);
40260
+ if (Array.isArray(manifest.stickyTarget)) {
40261
+ this.stickyTargets = manifest.stickyTarget;
40262
+ }
40263
+ const agentMap = manifest.agents ?? {};
40264
+ for (const [name, data] of Object.entries(agentMap)) {
40265
+ const adapter = this.agents.get(name);
40266
+ const agentData = data;
40267
+ if (!adapter || !agentData)
40268
+ continue;
40269
+ if (typeof agentData.sdkSessionId === "string" && agentData.sdkSessionId) {
40270
+ adapter.setSdkSessionId(agentData.sdkSessionId);
40271
+ }
40272
+ if (typeof agentData.lastSeenId === "number") {
40273
+ this.lastSeenByAgent.set(name, agentData.lastSeenId);
40274
+ }
39824
40275
  }
40276
+ } catch (err) {
40277
+ console.error("[manifest] load failed (agents start fresh):", err);
39825
40278
  }
39826
40279
  }
40280
+ cancelAgents(names) {
40281
+ for (const name of names) {
40282
+ this.queueManager.abortAgent(name);
40283
+ this.onActivity(name, "idle");
40284
+ }
40285
+ }
40286
+ async abortAll() {
40287
+ this.queueManager.abortAll();
40288
+ await this.saveManifest();
40289
+ }
39827
40290
  timeoutFor(agentName) {
39828
40291
  return this.agentTimeouts.get(agentName) ?? this.defaultTimeout;
39829
40292
  }
@@ -39857,6 +40320,21 @@ function createSessionId() {
39857
40320
  const rand = randomBytes(4).toString("hex");
39858
40321
  return `${timestamp}-${process.pid}-${rand}`;
39859
40322
  }
40323
+ function extractNextSelectors(messages) {
40324
+ const selectors = [];
40325
+ for (const msg of messages) {
40326
+ const regex = /@next\s*:\s*([A-Za-z0-9_-]+)/gi;
40327
+ let match = null;
40328
+ while ((match = regex.exec(msg.text)) !== null) {
40329
+ selectors.push(match[1]);
40330
+ }
40331
+ const controlMatch = msg.text.match(/@control[\s\S]*?next\s*:\s*([A-Za-z0-9_-]+)[\s\S]*?@end/i);
40332
+ if (controlMatch?.[1]) {
40333
+ selectors.push(controlMatch[1]);
40334
+ }
40335
+ }
40336
+ return selectors;
40337
+ }
39860
40338
 
39861
40339
  // src/ui/App.tsx
39862
40340
  import { spawn as spawn4 } from "child_process";
@@ -39868,14 +40346,56 @@ function nextSystemId() {
39868
40346
  systemIdCounter -= 1;
39869
40347
  return systemIdCounter;
39870
40348
  }
39871
- function useOrchestrator(orchestrator, maxAutoHops) {
40349
+ function useOrchestrator(orchestrator) {
39872
40350
  const [messages, setMessages] = createSignal([]);
39873
40351
  const [agentStates, setAgentStates] = createSignal(new Map(orchestrator.listAgents().map((a) => [a.name, "idle"])));
39874
- const [stickyTarget, setStickyTarget] = createSignal(undefined);
40352
+ const [stickyTarget, setStickyTargetSignal] = createSignal(orchestrator.getStickyTarget());
40353
+ const setStickyTarget = (targets) => {
40354
+ setStickyTargetSignal(targets);
40355
+ orchestrator.setStickyTarget(targets);
40356
+ };
39875
40357
  const [dispatching, setDispatching] = createSignal(false);
40358
+ const [queueCounts, setQueueCounts] = createSignal(new Map(orchestrator.listAgents().map((a) => [a.name, 0])));
39876
40359
  let projectFolderReady = false;
39877
- let agentProviders = new Map(orchestrator.listAgents().map((a) => [a.name.toUpperCase(), a.provider]));
39878
- let agentTags = new Map(orchestrator.listAgents().map((a) => [a.name.toUpperCase(), a.tag]));
40360
+ const agentProviders = new Map(orchestrator.listAgents().map((a) => [a.name.toUpperCase(), a.provider]));
40361
+ const agentTagsMap = new Map(orchestrator.listAgents().map((a) => [a.name.toUpperCase(), a.tag]));
40362
+ const nameMap = new Map(orchestrator.listAgents().map((a) => [a.name.toUpperCase(), a.name]));
40363
+ const updateQueueCounts = () => {
40364
+ const status = orchestrator.getQueueStatus();
40365
+ setQueueCounts(new Map(status.map((s) => [s.name, s.pending])));
40366
+ };
40367
+ orchestrator.setCallbacks((msg) => {
40368
+ const provider = agentProviders.get(msg.from) ?? "";
40369
+ const tag = agentTagsMap.get(msg.from) ?? "";
40370
+ const display = {
40371
+ ...msg,
40372
+ type: "agent",
40373
+ provider,
40374
+ tag
40375
+ };
40376
+ setMessages((prev) => [...prev, display]);
40377
+ const originalName = nameMap.get(msg.from) ?? msg.from;
40378
+ setAgentStates((prev) => {
40379
+ const next = new Map(prev);
40380
+ const isErr = msg.text.startsWith("[Adapter Error]") || msg.text.startsWith("[Error]") || msg.text.startsWith("[Timeout]");
40381
+ next.set(originalName, isErr ? "error" : "idle");
40382
+ return next;
40383
+ });
40384
+ setDispatching(orchestrator.dispatching);
40385
+ updateQueueCounts();
40386
+ }, (agentName, activity) => {
40387
+ const originalName = nameMap.get(agentName.toUpperCase()) ?? agentName;
40388
+ setAgentStates((prev) => {
40389
+ const next = new Map(prev);
40390
+ next.set(originalName, activity);
40391
+ return next;
40392
+ });
40393
+ setDispatching(orchestrator.dispatching);
40394
+ updateQueueCounts();
40395
+ }, (text) => {
40396
+ addSystemMessage(text);
40397
+ updateQueueCounts();
40398
+ });
39879
40399
  const dispatch = async (line) => {
39880
40400
  if (!line.trim())
39881
40401
  return;
@@ -39889,7 +40409,7 @@ function useOrchestrator(orchestrator, maxAutoHops) {
39889
40409
  if (explicitTargets && explicitTargets.length > 0) {
39890
40410
  setStickyTarget(explicitTargets);
39891
40411
  }
39892
- const targets = explicitTargets ?? stickyTarget();
40412
+ const targets = explicitTargets ?? stickyTarget() ?? Array.from(orchestrator.listAgents().map((a) => a.name));
39893
40413
  if (!projectFolderReady) {
39894
40414
  await initProjectFolder(process.cwd());
39895
40415
  projectFolderReady = true;
@@ -39902,11 +40422,7 @@ function useOrchestrator(orchestrator, maxAutoHops) {
39902
40422
  };
39903
40423
  setMessages((prev) => [...prev, userDisplay]);
39904
40424
  setDispatching(true);
39905
- try {
39906
- await dispatchWithHandoffs(orchestrator, targets, maxAutoHops, agentProviders, agentTags, setMessages, setAgentStates);
39907
- } finally {
39908
- setDispatching(false);
39909
- }
40425
+ orchestrator.dispatchToTargets(targets);
39910
40426
  };
39911
40427
  const addSystemMessage = (text) => {
39912
40428
  const msg = {
@@ -39918,89 +40434,17 @@ function useOrchestrator(orchestrator, maxAutoHops) {
39918
40434
  };
39919
40435
  setMessages((prev) => [...prev, msg]);
39920
40436
  };
40437
+ const addDisplayMessage = (msg) => {
40438
+ setMessages((prev) => [...prev, msg]);
40439
+ };
39921
40440
  const clearMessages = () => {
39922
40441
  orchestrator.clearConversation();
39923
40442
  setMessages([]);
39924
40443
  };
39925
- return { messages, agentStates, stickyTarget, dispatching, dispatch, addSystemMessage, clearMessages };
39926
- }
39927
- async function dispatchWithHandoffs(orchestrator, initialTargets, maxHops, agentProviders, agentTags, setMessages, setAgentStates) {
39928
- let targets = initialTargets;
39929
- let hops = 0;
39930
- while (true) {
39931
- const targetNames = targets ?? Array.from(orchestrator.listAgents().map((a) => a.name));
39932
- setAgentStates((prev) => {
39933
- const next = new Map(prev);
39934
- for (const name of targetNames) {
39935
- next.set(name, "thinking");
39936
- }
39937
- return next;
39938
- });
39939
- const nameMap = new Map(orchestrator.listAgents().map((a) => [a.name.toUpperCase(), a.name]));
39940
- const batch = [];
39941
- await orchestrator.fanOutWithProgress(targets, (msg) => {
39942
- batch.push(msg);
39943
- const provider = agentProviders.get(msg.from) ?? "";
39944
- const tag = agentTags.get(msg.from) ?? "";
39945
- const display = {
39946
- ...msg,
39947
- type: "agent",
39948
- provider,
39949
- tag
39950
- };
39951
- setMessages((prev) => [...prev, display]);
39952
- const originalName = nameMap.get(msg.from) ?? msg.from;
39953
- setAgentStates((prev) => {
39954
- const next = new Map(prev);
39955
- next.set(originalName, msg.text.startsWith("[Adapter Error]") ? "error" : "idle");
39956
- return next;
39957
- });
39958
- });
39959
- setAgentStates((prev) => {
39960
- const next = new Map(prev);
39961
- for (const name of targetNames) {
39962
- if (next.get(name) === "thinking") {
39963
- next.set(name, "idle");
39964
- }
39965
- }
39966
- return next;
39967
- });
39968
- const nextSelectors = extractNextSelectors(batch);
39969
- if (nextSelectors.length === 0)
39970
- return;
39971
- const humanTag = orchestrator.getHumanTag().toLowerCase();
39972
- const humanName = orchestrator.getHumanName().toLowerCase();
39973
- const agentSelectors = nextSelectors.filter((s) => {
39974
- const n = s.toLowerCase();
39975
- return n !== humanTag && n !== humanName;
39976
- });
39977
- if (agentSelectors.length === 0)
39978
- return;
39979
- const resolvedTargets = Array.from(new Set(agentSelectors.flatMap((s) => orchestrator.resolveTargets(s))));
39980
- if (resolvedTargets.length === 0)
39981
- return;
39982
- hops += 1;
39983
- if (Number.isFinite(maxHops) && hops >= maxHops) {
39984
- const systemMsg = {
39985
- id: nextSystemId(),
39986
- from: "SYSTEM",
39987
- text: `Stopped auto-handoff after ${maxHops} hops.`,
39988
- createdAt: new Date().toISOString(),
39989
- type: "system"
39990
- };
39991
- setMessages((prev) => [...prev, systemMsg]);
39992
- return;
39993
- }
39994
- const handoffMsg = {
39995
- id: nextSystemId(),
39996
- from: "SYSTEM",
39997
- text: `Auto handoff via @next to ${resolvedTargets.join(", ")}`,
39998
- createdAt: new Date().toISOString(),
39999
- type: "system"
40000
- };
40001
- setMessages((prev) => [...prev, handoffMsg]);
40002
- targets = resolvedTargets;
40003
- }
40444
+ const refreshStickyTarget = () => {
40445
+ setStickyTargetSignal(orchestrator.getStickyTarget());
40446
+ };
40447
+ return { messages, agentStates, queueCounts, stickyTarget, dispatching, dispatch, addSystemMessage, addDisplayMessage, clearMessages, refreshStickyTarget };
40004
40448
  }
40005
40449
  function getChangedFiles() {
40006
40450
  return new Promise((resolve4) => {
@@ -40015,21 +40459,6 @@ function getChangedFiles() {
40015
40459
  });
40016
40460
  });
40017
40461
  }
40018
- function extractNextSelectors(messages) {
40019
- const selectors = [];
40020
- for (const msg of messages) {
40021
- const regex = /@next\s*:\s*([A-Za-z0-9_-]+)/gi;
40022
- let match = null;
40023
- while ((match = regex.exec(msg.text)) !== null) {
40024
- selectors.push(match[1]);
40025
- }
40026
- const controlMatch = msg.text.match(/@control[\s\S]*?next\s*:\s*([A-Za-z0-9_-]+)[\s\S]*?@end/i);
40027
- if (controlMatch?.[1]) {
40028
- selectors.push(controlMatch[1]);
40029
- }
40030
- }
40031
- return selectors;
40032
- }
40033
40462
  function parseRouting(line) {
40034
40463
  const mentionRegex = /(^|[^A-Za-z0-9_-])@([A-Za-z0-9_-]+)\b/g;
40035
40464
  const mentions = [];
@@ -40118,27 +40547,21 @@ function MessageBubble(props) {
40118
40547
  }
40119
40548
 
40120
40549
  // src/ui/constants.ts
40121
- var SPINNER_FRAMES = ["\u280B", "\u2819", "\u281A", "\u281E", "\u2816", "\u2826", "\u2834", "\u2832", "\u2833", "\u2813"];
40550
+ var SPINNER_FRAMES = "\u280B\u2819\u281A\u281E\u2816\u2826\u2834\u2832\u2833\u2813".split("");
40551
+ var ACTIVITY_SPINNERS = {
40552
+ thinking_bkp: "\u2733\u2734\u2736\u2735\u2737\u2738\u2739\u273A".split(""),
40553
+ thinking: " ..ooOO@@@@@@*".split(""),
40554
+ writing: "\u258F\u258E\u258D\u258C\u258B\u258A\u2589\u2588\u2589\u258A\u258B\u258C\u258D\u258E".split(""),
40555
+ reading: ["\u2801", "\u2809", "\u280B", "\u281B", "\u281F", "\u283F", "\u287F", "\u28FF"],
40556
+ running: [" ", "\u2591", "\u2592", "\u2593", "\u2588"],
40557
+ searching: "\u25D0\u25D3\u25D1\u25D2".split("")
40558
+ };
40559
+ var SUPERSCRIPT_DIGITS = ["\u2070", "\xB9", "\xB2", "\xB3", "\u2074", "\u2075", "\u2076", "\u2077", "\u2078", "\u2079"];
40122
40560
 
40123
40561
  // src/ui/StatusBar.tsx
40124
40562
  var PULSE_COLORS = ["#005F87", "#0087AF", "#00AFD7", "#00D7FF", "#5FF", "#00D7FF", "#0087AF"];
40125
- function useThinkingAnimation(active) {
40126
- const [frame, setFrame] = createSignal(0);
40127
- createEffect(() => {
40128
- if (!active()) {
40129
- setFrame(0);
40130
- return;
40131
- }
40132
- const interval = setInterval(() => {
40133
- setFrame((f) => (f + 1) % (SPINNER_FRAMES.length * PULSE_COLORS.length));
40134
- }, 80);
40135
- onCleanup(() => clearInterval(interval));
40136
- });
40137
- return {
40138
- spinner: () => active() ? SPINNER_FRAMES[frame() % SPINNER_FRAMES.length] : "",
40139
- color: () => active() ? PULSE_COLORS[frame() % PULSE_COLORS.length] : COLORS.textMuted
40140
- };
40141
- }
40563
+ var [globalTick, setGlobalTick] = createSignal(0);
40564
+ setInterval(() => setGlobalTick((t2) => t2 + 1), 80);
40142
40565
  function StatusBar(props) {
40143
40566
  const targetNames = () => props.stickyTarget ?? props.agents.map((a) => a.name);
40144
40567
  const isTargeted = (name) => targetNames().includes(name);
@@ -40186,65 +40609,59 @@ function StatusBar(props) {
40186
40609
  }), _el$4);
40187
40610
  insertNode(_el$4, createTextNode(`| /info`));
40188
40611
  setProp(_el$6, "flexDirection", "row");
40189
- setProp(_el$6, "gap", 2);
40190
40612
  insert(_el$6, createComponent2(For, {
40191
40613
  get each() {
40192
40614
  return props.agents;
40193
40615
  },
40194
- children: (a) => createComponent2(AgentChip, {
40616
+ children: (a, i) => [createComponent2(AgentChip, {
40195
40617
  get name() {
40196
40618
  return a.name;
40197
40619
  },
40198
- get state() {
40199
- return props.agentStates.get(a.name) ?? "idle";
40200
- }
40201
- })
40620
+ getState: () => props.agentStates.get(a.name) ?? "idle",
40621
+ getQueued: () => props.queueCounts?.get(a.name) ?? 0
40622
+ }), memo2(() => memo2(() => i() < props.agents.length - 1)() ? (() => {
40623
+ var _el$12 = createElement("text");
40624
+ insertNode(_el$12, createTextNode(` \u2502 `));
40625
+ effect((_$p) => setProp(_el$12, "fg", COLORS.textDim, _$p));
40626
+ return _el$12;
40627
+ })() : null)]
40202
40628
  }));
40203
40629
  effect((_$p) => setProp(_el$4, "fg", COLORS.textDim, _$p));
40204
40630
  return _el$;
40205
40631
  })();
40206
40632
  }
40633
+ function toSuperscript(n) {
40634
+ if (n <= 0)
40635
+ return "";
40636
+ return String(n).split("").map((d2) => SUPERSCRIPT_DIGITS[parseInt(d2, 10)] ?? d2).join("");
40637
+ }
40207
40638
  function AgentChip(props) {
40208
- const {
40209
- spinner,
40210
- color
40211
- } = useThinkingAnimation(() => props.state === "thinking");
40212
- return createComponent2(Switch, {
40213
- get fallback() {
40214
- return (() => {
40215
- var _el$16 = createElement("text");
40216
- insert(_el$16, () => props.name);
40217
- effect((_$p) => setProp(_el$16, "fg", COLORS.textMuted, _$p));
40218
- return _el$16;
40219
- })();
40220
- },
40221
- get children() {
40222
- return [createComponent2(Match, {
40223
- get when() {
40224
- return props.state === "error";
40225
- },
40226
- get children() {
40227
- var _el$12 = createElement("text"), _el$13 = createTextNode(` ERR`);
40228
- insertNode(_el$12, _el$13);
40229
- insert(_el$12, () => props.name, _el$13);
40230
- effect((_$p) => setProp(_el$12, "fg", COLORS.error, _$p));
40231
- return _el$12;
40232
- }
40233
- }), createComponent2(Match, {
40234
- get when() {
40235
- return props.state === "thinking";
40236
- },
40237
- get children() {
40238
- var _el$14 = createElement("text"), _el$15 = createTextNode(` `);
40239
- insertNode(_el$14, _el$15);
40240
- insert(_el$14, () => props.name, _el$15);
40241
- insert(_el$14, spinner, null);
40242
- effect((_$p) => setProp(_el$14, "fg", color(), _$p));
40243
- return _el$14;
40244
- }
40245
- })];
40246
- }
40247
- });
40639
+ const isActive = () => {
40640
+ const s = props.getState();
40641
+ return s !== "idle" && s !== "error" && s !== "queued";
40642
+ };
40643
+ const frames = () => ACTIVITY_SPINNERS[props.getState()] ?? SPINNER_FRAMES;
40644
+ const tick = () => globalTick();
40645
+ const spinner = () => isActive() ? frames()[tick() % frames().length] : " ";
40646
+ const pulseColor = () => isActive() ? PULSE_COLORS[tick() % PULSE_COLORS.length] : COLORS.textMuted;
40647
+ const queueSlot = () => props.getQueued() > 0 ? toSuperscript(props.getQueued()) : " ";
40648
+ const stateColor = () => {
40649
+ if (props.getState() === "error")
40650
+ return COLORS.error;
40651
+ if (isActive())
40652
+ return pulseColor();
40653
+ return COLORS.textMuted;
40654
+ };
40655
+ return (() => {
40656
+ var _el$14 = createElement("text"), _el$15 = createTextNode(` `);
40657
+ insertNode(_el$14, _el$15);
40658
+ insert(_el$14, queueSlot, _el$15);
40659
+ insert(_el$14, () => props.name, _el$15);
40660
+ insert(_el$14, spinner, null);
40661
+ insert(_el$14, () => props.getState() === "error" ? " ERR" : "", null);
40662
+ effect((_$p) => setProp(_el$14, "fg", stateColor(), _$p));
40663
+ return _el$14;
40664
+ })();
40248
40665
  }
40249
40666
 
40250
40667
  // src/ui/InputLine.tsx
@@ -40500,7 +40917,7 @@ import { userInfo as userInfo2 } from "os";
40500
40917
 
40501
40918
  // src/config/detector.ts
40502
40919
  import { spawn as spawn3 } from "child_process";
40503
- var DETECT_TIMEOUT = 5000;
40920
+ var DETECT_TIMEOUT = 1e4;
40504
40921
  function detectBinary(command) {
40505
40922
  return new Promise((resolve4) => {
40506
40923
  const timer = setTimeout(() => resolve4({ available: false }), DETECT_TIMEOUT);
@@ -42299,6 +42716,178 @@ function InfoPanel(props) {
42299
42716
  })();
42300
42717
  }
42301
42718
 
42719
+ // src/ui/CancelPanel.tsx
42720
+ function CancelPanel(props) {
42721
+ const items = () => props.activeAgents;
42722
+ const [cursor, setCursor] = createSignal(0);
42723
+ const [selected, setSelected] = createSignal(new Set);
42724
+ createEffect(() => {
42725
+ if (props.activeAgents.length === 0) {
42726
+ props.onClose();
42727
+ }
42728
+ });
42729
+ useKeyboard((key) => {
42730
+ if (key.name === "escape") {
42731
+ props.onClose();
42732
+ return;
42733
+ }
42734
+ if (key.name === "return") {
42735
+ const sel = selected();
42736
+ if (sel.size === 0) {
42737
+ props.onClose();
42738
+ return;
42739
+ }
42740
+ const names = [...sel];
42741
+ props.onCancel(names);
42742
+ return;
42743
+ }
42744
+ if (key.name === "up") {
42745
+ setCursor((c) => Math.max(0, c - 1));
42746
+ return;
42747
+ }
42748
+ if (key.name === "down") {
42749
+ setCursor((c) => Math.min(items().length - 1, c + 1));
42750
+ return;
42751
+ }
42752
+ if (key.name === "space" || key.sequence === " ") {
42753
+ const item = items()[cursor()];
42754
+ if (!item)
42755
+ return;
42756
+ setSelected((prev) => {
42757
+ const next = new Set(prev);
42758
+ if (next.has(item)) {
42759
+ next.delete(item);
42760
+ } else {
42761
+ next.add(item);
42762
+ }
42763
+ return next;
42764
+ });
42765
+ return;
42766
+ }
42767
+ });
42768
+ const totalW = 40;
42769
+ return (() => {
42770
+ var _el$ = createElement("box"), _el$2 = createElement("box"), _el$3 = createElement("box"), _el$4 = createElement("text"), _el$5 = createElement("strong"), _el$7 = createElement("text"), _el$9 = createElement("text"), _el$1 = createElement("text"), _el$10 = createElement("span"), _el$12 = createElement("span"), _el$14 = createElement("span"), _el$16 = createElement("span"), _el$18 = createElement("span"), _el$20 = createElement("span");
42771
+ insertNode(_el$, _el$2);
42772
+ setProp(_el$, "position", "absolute");
42773
+ setProp(_el$, "width", "100%");
42774
+ setProp(_el$, "height", "100%");
42775
+ setProp(_el$, "justifyContent", "center");
42776
+ setProp(_el$, "alignItems", "center");
42777
+ setProp(_el$, "zIndex", 10);
42778
+ insertNode(_el$2, _el$3);
42779
+ setProp(_el$2, "border", true);
42780
+ setProp(_el$2, "borderStyle", "rounded");
42781
+ setProp(_el$2, "paddingX", 3);
42782
+ setProp(_el$2, "paddingY", 1);
42783
+ insertNode(_el$3, _el$4);
42784
+ insertNode(_el$3, _el$7);
42785
+ insertNode(_el$3, _el$9);
42786
+ insertNode(_el$3, _el$1);
42787
+ setProp(_el$3, "flexDirection", "column");
42788
+ insertNode(_el$4, _el$5);
42789
+ setProp(_el$4, "alignSelf", "center");
42790
+ insertNode(_el$5, createTextNode(`Cancel Agents`));
42791
+ insertNode(_el$7, createTextNode(`\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`));
42792
+ setProp(_el$7, "marginTop", 1);
42793
+ insert(_el$3, createComponent2(For, {
42794
+ get each() {
42795
+ return items();
42796
+ },
42797
+ children: (item, i) => {
42798
+ const isSelected = () => selected().has(item);
42799
+ const isCursor = () => cursor() === i();
42800
+ const bullet = () => isSelected() ? "\u25A0" : "\u25A1";
42801
+ const bulletColor = () => isSelected() ? COLORS.error : COLORS.textDim;
42802
+ const labelColor = () => isCursor() ? COLORS.textPrimary : COLORS.textMuted;
42803
+ const bgColor = () => isCursor() ? COLORS.bgFocus : undefined;
42804
+ return (() => {
42805
+ var _el$22 = createElement("text"), _el$23 = createElement("span"), _el$24 = createTextNode(` `), _el$25 = createTextNode(` `), _el$26 = createElement("span");
42806
+ insertNode(_el$22, _el$23);
42807
+ insertNode(_el$22, _el$26);
42808
+ setProp(_el$22, "selectable", false);
42809
+ insertNode(_el$23, _el$24);
42810
+ insertNode(_el$23, _el$25);
42811
+ insert(_el$23, bullet, _el$25);
42812
+ insert(_el$26, item);
42813
+ effect((_p$) => {
42814
+ var _v$10 = bgColor(), _v$11 = {
42815
+ fg: bulletColor()
42816
+ }, _v$12 = {
42817
+ fg: labelColor()
42818
+ };
42819
+ _v$10 !== _p$.e && (_p$.e = setProp(_el$22, "bg", _v$10, _p$.e));
42820
+ _v$11 !== _p$.t && (_p$.t = setProp(_el$23, "style", _v$11, _p$.t));
42821
+ _v$12 !== _p$.a && (_p$.a = setProp(_el$26, "style", _v$12, _p$.a));
42822
+ return _p$;
42823
+ }, {
42824
+ e: undefined,
42825
+ t: undefined,
42826
+ a: undefined
42827
+ });
42828
+ return _el$22;
42829
+ })();
42830
+ }
42831
+ }), _el$9);
42832
+ insertNode(_el$9, createTextNode(`\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`));
42833
+ setProp(_el$9, "marginTop", 1);
42834
+ insertNode(_el$1, _el$10);
42835
+ insertNode(_el$1, _el$12);
42836
+ insertNode(_el$1, _el$14);
42837
+ insertNode(_el$1, _el$16);
42838
+ insertNode(_el$1, _el$18);
42839
+ insertNode(_el$1, _el$20);
42840
+ setProp(_el$1, "marginTop", 1);
42841
+ setProp(_el$1, "alignSelf", "center");
42842
+ insertNode(_el$10, createTextNode(`Space`));
42843
+ insertNode(_el$12, createTextNode(` toggle `));
42844
+ insertNode(_el$14, createTextNode(`Enter`));
42845
+ insertNode(_el$16, createTextNode(` kill `));
42846
+ insertNode(_el$18, createTextNode(`Esc`));
42847
+ insertNode(_el$20, createTextNode(` back`));
42848
+ effect((_p$) => {
42849
+ var _v$ = COLORS.error, _v$2 = COLORS.bgPanel, _v$3 = COLORS.error, _v$4 = COLORS.borderStrong, _v$5 = COLORS.borderStrong, _v$6 = {
42850
+ fg: COLORS.textFaint
42851
+ }, _v$7 = {
42852
+ fg: COLORS.textDim
42853
+ }, _v$8 = {
42854
+ fg: COLORS.error
42855
+ }, _v$9 = {
42856
+ fg: COLORS.textDim
42857
+ }, _v$0 = {
42858
+ fg: COLORS.textFaint
42859
+ }, _v$1 = {
42860
+ fg: COLORS.textDim
42861
+ };
42862
+ _v$ !== _p$.e && (_p$.e = setProp(_el$2, "borderColor", _v$, _p$.e));
42863
+ _v$2 !== _p$.t && (_p$.t = setProp(_el$2, "backgroundColor", _v$2, _p$.t));
42864
+ _v$3 !== _p$.a && (_p$.a = setProp(_el$4, "fg", _v$3, _p$.a));
42865
+ _v$4 !== _p$.o && (_p$.o = setProp(_el$7, "fg", _v$4, _p$.o));
42866
+ _v$5 !== _p$.i && (_p$.i = setProp(_el$9, "fg", _v$5, _p$.i));
42867
+ _v$6 !== _p$.n && (_p$.n = setProp(_el$10, "style", _v$6, _p$.n));
42868
+ _v$7 !== _p$.s && (_p$.s = setProp(_el$12, "style", _v$7, _p$.s));
42869
+ _v$8 !== _p$.h && (_p$.h = setProp(_el$14, "style", _v$8, _p$.h));
42870
+ _v$9 !== _p$.r && (_p$.r = setProp(_el$16, "style", _v$9, _p$.r));
42871
+ _v$0 !== _p$.d && (_p$.d = setProp(_el$18, "style", _v$0, _p$.d));
42872
+ _v$1 !== _p$.l && (_p$.l = setProp(_el$20, "style", _v$1, _p$.l));
42873
+ return _p$;
42874
+ }, {
42875
+ e: undefined,
42876
+ t: undefined,
42877
+ a: undefined,
42878
+ o: undefined,
42879
+ i: undefined,
42880
+ n: undefined,
42881
+ s: undefined,
42882
+ h: undefined,
42883
+ r: undefined,
42884
+ d: undefined,
42885
+ l: undefined
42886
+ });
42887
+ return _el$;
42888
+ })();
42889
+ }
42890
+
42302
42891
  // src/ui/App.tsx
42303
42892
  function copyToClipboard(text) {
42304
42893
  const proc = spawn4("pbcopy", [], {
@@ -42323,12 +42912,15 @@ function App(props) {
42323
42912
  const {
42324
42913
  messages,
42325
42914
  agentStates,
42915
+ queueCounts,
42326
42916
  stickyTarget,
42327
42917
  dispatching,
42328
42918
  dispatch,
42329
42919
  addSystemMessage,
42330
- clearMessages
42331
- } = useOrchestrator(props.orchestrator, props.maxAutoHops);
42920
+ addDisplayMessage,
42921
+ clearMessages,
42922
+ refreshStickyTarget
42923
+ } = useOrchestrator(props.orchestrator);
42332
42924
  const humanName = props.orchestrator.getHumanName();
42333
42925
  const agents = props.orchestrator.listAgents();
42334
42926
  let scrollRef = null;
@@ -42336,25 +42928,67 @@ function App(props) {
42336
42928
  const [freshConfig, setFreshConfig] = createSignal(props.config);
42337
42929
  const [showAgents, setShowAgents] = createSignal(false);
42338
42930
  const [showInfo, setShowInfo] = createSignal(false);
42339
- process.on("SIGINT", () => renderer.destroy());
42340
- process.on("SIGTERM", () => renderer.destroy());
42341
- process.on("SIGHUP", () => renderer.destroy());
42931
+ const [showCancel, setShowCancel] = createSignal(false);
42932
+ const resumeSession = async (sessionId) => {
42933
+ try {
42934
+ const restored = await props.orchestrator.loadTranscript(sessionId);
42935
+ const displayMsgs = restored.map((msg) => {
42936
+ const isHuman = msg.from.toUpperCase() === humanName.toUpperCase();
42937
+ const agentInfo = agents.find((a) => a.name.toUpperCase() === msg.from.toUpperCase());
42938
+ return {
42939
+ ...msg,
42940
+ type: isHuman ? "user" : "agent",
42941
+ provider: agentInfo?.provider ?? "",
42942
+ tag: agentInfo?.tag ?? ""
42943
+ };
42944
+ });
42945
+ for (const msg of displayMsgs) {
42946
+ addDisplayMessage(msg);
42947
+ }
42948
+ refreshStickyTarget();
42949
+ addSystemMessage(`Resumed session ${sessionId} (${restored.length} messages)`);
42950
+ } catch (err) {
42951
+ addSystemMessage(`Failed to resume: ${err instanceof Error ? err.message : String(err)}`);
42952
+ }
42953
+ };
42954
+ onMount(() => {
42955
+ if (props.resumeSessionId) {
42956
+ resumeSession(props.resumeSessionId);
42957
+ }
42958
+ });
42959
+ process.on("SIGINT", () => gracefulExit());
42960
+ process.on("SIGTERM", () => gracefulExit());
42961
+ process.on("SIGHUP", () => gracefulExit());
42342
42962
  process.on("SIGTSTP", () => {
42343
42963
  process.once("SIGCONT", () => renderer.resume());
42344
42964
  renderer.suspend();
42345
42965
  });
42346
- const gracefulExit = () => {
42966
+ const gracefulExit = async () => {
42967
+ await props.orchestrator.abortAll();
42347
42968
  renderer.destroy();
42348
42969
  const adapters = props.orchestrator.getAdapters();
42349
- Promise.allSettled(adapters.map((a) => a.destroy()));
42970
+ Promise.allSettled(adapters.map((a) => a.destroy())).finally(() => {
42971
+ process.exit(0);
42972
+ });
42973
+ setTimeout(() => process.exit(0), 2000);
42350
42974
  };
42351
42975
  useKeyboard((key) => {
42352
42976
  if (key.ctrl && key.name === "p") {
42353
42977
  setShowAgents((v2) => !v2);
42354
42978
  return;
42355
42979
  }
42356
- if (showAgents())
42980
+ if (showAgents() || showCancel() || showInfo())
42357
42981
  return;
42982
+ if (key.name === "escape") {
42983
+ const active = agents.filter((a) => {
42984
+ const state = agentStates().get(a.name);
42985
+ return state && state !== "idle" && state !== "error";
42986
+ }).map((a) => a.name);
42987
+ if (active.length > 0) {
42988
+ setShowCancel(true);
42989
+ }
42990
+ return;
42991
+ }
42358
42992
  if (key.ctrl && key.name === "c") {
42359
42993
  if (!copySelection(renderer)) {
42360
42994
  gracefulExit();
@@ -42425,6 +43059,19 @@ ${files.map((f) => ` ${f}`).join(`
42425
43059
  setShowInfo(true);
42426
43060
  return;
42427
43061
  }
43062
+ if (line.startsWith("/resume")) {
43063
+ const sid = line.replace("/resume", "").trim();
43064
+ if (!sid) {
43065
+ addSystemMessage("Usage: /resume <sessionId>");
43066
+ return;
43067
+ }
43068
+ if (props.orchestrator.hasMessages()) {
43069
+ addSystemMessage("Resume only works before the first message.");
43070
+ return;
43071
+ }
43072
+ await resumeSession(sid);
43073
+ return;
43074
+ }
42428
43075
  if (line.startsWith("/flood")) {
42429
43076
  const args = line.split(/\s+/);
42430
43077
  const count = parseInt(args[1], 10) || 50;
@@ -42489,16 +43136,19 @@ ${lines.join(`
42489
43136
  },
42490
43137
  get stickyTarget() {
42491
43138
  return stickyTarget();
43139
+ },
43140
+ get queueCounts() {
43141
+ return queueCounts();
42492
43142
  }
42493
43143
  }), null);
42494
43144
  insert(_el$, createComponent2(InputLine, {
42495
43145
  humanName,
42496
43146
  onSubmit: handleSubmit,
42497
43147
  get disabled() {
42498
- return dispatching() || showAgents() || showInfo();
43148
+ return showAgents() || showInfo() || showCancel();
42499
43149
  },
42500
43150
  get disabledMessage() {
42501
- return showAgents() ? "" : undefined;
43151
+ return showAgents() || showCancel() ? "" : undefined;
42502
43152
  }
42503
43153
  }), null);
42504
43154
  insert(_el$, (() => {
@@ -42521,6 +43171,24 @@ ${lines.join(`
42521
43171
  onClose: () => setShowInfo(false)
42522
43172
  });
42523
43173
  })(), null);
43174
+ insert(_el$, (() => {
43175
+ var _c$3 = memo2(() => !!showCancel());
43176
+ return () => _c$3() && createComponent2(CancelPanel, {
43177
+ get activeAgents() {
43178
+ return agents.filter((a) => {
43179
+ const state = agentStates().get(a.name);
43180
+ return state && state !== "idle" && state !== "error";
43181
+ }).map((a) => a.name);
43182
+ },
43183
+ onCancel: (names) => {
43184
+ props.orchestrator.cancelAgents(names);
43185
+ const label = names.length === agents.length ? "all agents" : names.join(", ");
43186
+ addSystemMessage(`Cancelled ${label}`);
43187
+ setShowCancel(false);
43188
+ },
43189
+ onClose: () => setShowCancel(false)
43190
+ });
43191
+ })(), null);
42524
43192
  effect((_$p) => setProp(_el$3, "fg", COLORS.primary, _$p));
42525
43193
  return _el$;
42526
43194
  }
@@ -42531,6 +43199,8 @@ ${lines.join(`
42531
43199
  async function main2() {
42532
43200
  const appRoot = path11.resolve(path11.dirname(fileURLToPath3(import.meta.url)), "..");
42533
43201
  await initLlmPartyHome(appRoot);
43202
+ const resumeIndex = process.argv.indexOf("--resume");
43203
+ const resumeSessionId = resumeIndex !== -1 ? process.argv[resumeIndex + 1] : undefined;
42534
43204
  const rendererConfig = {
42535
43205
  exitOnCtrlC: false,
42536
43206
  useMouse: true,
@@ -42541,14 +43211,14 @@ async function main2() {
42541
43211
  await render(() => ConfigWizard({
42542
43212
  isFirstRun: true,
42543
43213
  onComplete: async () => {
42544
- await bootApp(appRoot, rendererConfig);
43214
+ await bootApp(appRoot, rendererConfig, resumeSessionId);
42545
43215
  }
42546
43216
  }), rendererConfig);
42547
43217
  } else {
42548
- await bootApp(appRoot, rendererConfig);
43218
+ await bootApp(appRoot, rendererConfig, resumeSessionId);
42549
43219
  }
42550
43220
  }
42551
- async function bootApp(appRoot, rendererConfig) {
43221
+ async function bootApp(appRoot, rendererConfig, resumeSessionId) {
42552
43222
  const configPath = await resolveConfigPath(appRoot);
42553
43223
  const config = await loadConfig2(configPath);
42554
43224
  const humanName = config.humanName?.trim() || "USER";
@@ -42594,7 +43264,7 @@ async function bootApp(appRoot, rendererConfig) {
42594
43264
  const promptParts = [mergedBase];
42595
43265
  if (agent.prompts && agent.prompts.length > 0) {
42596
43266
  const extraPaths = agent.prompts.map((p) => resolveFromConfig(p));
42597
- const extraParts = await Promise.all(extraPaths.map((p) => readFile5(p, "utf8")));
43267
+ const extraParts = await Promise.all(extraPaths.map((p) => readFile6(p, "utf8")));
42598
43268
  promptParts.push(...extraParts);
42599
43269
  }
42600
43270
  const promptTemplate = promptParts.join(`
@@ -42638,8 +43308,8 @@ ${mySkills.map((s) => `- ${s}`).join(`
42638
43308
  }));
42639
43309
  const defaultTimeout = typeof config.timeout === "number" && config.timeout > 0 ? config.timeout * 1000 : 600000;
42640
43310
  const agentTimeouts = Object.fromEntries(activeAgents.filter((agent) => typeof agent.timeout === "number" && agent.timeout > 0).map((agent) => [agent.name, agent.timeout * 1000]));
42641
- const orchestrator = new Orchestrator(adapters, humanName, Object.fromEntries(activeAgents.map((agent) => [agent.name, agent.tag?.trim() || toTag(agent.name)])), humanTag, defaultTimeout, agentTimeouts, { reminderInterval: config.reminderInterval });
42642
- await render(() => App({ orchestrator, maxAutoHops, config, configPath }), rendererConfig);
43311
+ const orchestrator = new Orchestrator(adapters, humanName, Object.fromEntries(activeAgents.map((agent) => [agent.name, agent.tag?.trim() || toTag(agent.name)])), humanTag, defaultTimeout, agentTimeouts, { reminderInterval: config.reminderInterval, maxAutoHops });
43312
+ await render(() => App({ orchestrator, config, configPath, resumeSessionId }), rendererConfig);
42643
43313
  }
42644
43314
  function resolveMaxAutoHops(value) {
42645
43315
  if (typeof value === "number" && value === 0) {