llm-party-cli 0.9.0 → 0.10.1

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,72 @@ 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
+ abortAll() {
39886
+ for (const q2 of this.queues.values()) {
39887
+ q2.controller?.abort();
39888
+ q2.pending = [];
39889
+ }
39890
+ }
39891
+ clearAll() {
39892
+ for (const q2 of this.queues.values()) {
39893
+ q2.pending = [];
39894
+ q2.processing = false;
39895
+ }
39896
+ }
39897
+ }
39657
39898
 
39658
39899
  class Orchestrator {
39659
39900
  agents;
@@ -39669,7 +39910,16 @@ class Orchestrator {
39669
39910
  contextWindowSize;
39670
39911
  reminderInterval;
39671
39912
  reminderCursors = new Map;
39913
+ maxAutoHops;
39914
+ queueTtlMs;
39915
+ maxQueueSize;
39672
39916
  messageId = 0;
39917
+ stickyTargets;
39918
+ queueManager = new AgentQueueManager;
39919
+ manifestSavePromise = Promise.resolve();
39920
+ onMessage = () => {};
39921
+ onActivity = () => {};
39922
+ onSystem = () => {};
39673
39923
  constructor(agents, humanName = "USER", agentTags, humanTag, defaultTimeout = 600000, agentTimeouts, options) {
39674
39924
  this.agents = new Map(agents.map((agent) => [agent.name, agent]));
39675
39925
  this.agentTags = new Map(agents.map((agent) => [agent.name, agentTags?.[agent.name] ?? toTag(agent.name)]));
@@ -39679,12 +39929,21 @@ class Orchestrator {
39679
39929
  this.agentTimeouts = new Map(Object.entries(agentTimeouts ?? {}));
39680
39930
  this.contextWindowSize = options?.contextWindowSize ?? 16;
39681
39931
  this.reminderInterval = options?.reminderInterval ?? 8;
39932
+ this.maxAutoHops = options?.maxAutoHops ?? 15;
39933
+ this.queueTtlMs = options?.queueTtlMs ?? QUEUE_TTL_MS;
39934
+ this.maxQueueSize = options?.maxQueueSize ?? MAX_QUEUE_SIZE;
39682
39935
  this.sessionId = createSessionId();
39683
39936
  this.transcriptPath = path9.resolve(".llm-party", "sessions", `transcript-${this.sessionId}.jsonl`);
39684
39937
  for (const agent of agents) {
39685
39938
  this.lastSeenByAgent.set(agent.name, 0);
39939
+ this.queueManager.register(agent.name);
39686
39940
  }
39687
39941
  }
39942
+ setCallbacks(onMessage, onActivity, onSystem) {
39943
+ this.onMessage = onMessage;
39944
+ this.onActivity = onActivity;
39945
+ this.onSystem = onSystem;
39946
+ }
39688
39947
  getSessionId() {
39689
39948
  return this.sessionId;
39690
39949
  }
@@ -39697,12 +39956,29 @@ class Orchestrator {
39697
39956
  getHumanTag() {
39698
39957
  return this.humanTag;
39699
39958
  }
39959
+ get dispatching() {
39960
+ return this.queueManager.anyProcessing;
39961
+ }
39962
+ getStickyTarget() {
39963
+ return this.stickyTargets;
39964
+ }
39965
+ setStickyTarget(targets) {
39966
+ this.stickyTargets = targets;
39967
+ }
39968
+ getQueueStatus() {
39969
+ return this.listAgents().map((a) => ({
39970
+ name: a.name,
39971
+ processing: this.queueManager.isProcessing(a.name),
39972
+ pending: this.queueManager.pendingCount(a.name)
39973
+ }));
39974
+ }
39700
39975
  clearConversation() {
39701
39976
  this.conversation.length = 0;
39702
39977
  this.messageId = 0;
39703
39978
  for (const agent of this.agents.keys()) {
39704
39979
  this.lastSeenByAgent.set(agent, 0);
39705
39980
  }
39981
+ this.queueManager.clearAll();
39706
39982
  this.sessionId = createSessionId();
39707
39983
  this.transcriptPath = path9.resolve(".llm-party", "sessions", `transcript-${this.sessionId}.jsonl`);
39708
39984
  }
@@ -39739,62 +40015,158 @@ class Orchestrator {
39739
40015
  const tag = this.agentTags.get(agent.name) ?? toTag(agent.name);
39740
40016
  return agent.name.toLowerCase() === normalized || tag.toLowerCase() === normalized;
39741
40017
  }).map((agent) => agent.name);
39742
- if (byName.length > 0) {
40018
+ if (byName.length > 0)
39743
40019
  return byName;
39744
- }
39745
40020
  return Array.from(this.agents.values()).filter((agent) => agent.provider.toLowerCase() === normalized).map((agent) => agent.name);
39746
40021
  }
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
- }
40022
+ dispatchToTargets(targetAgentNames, chainHops = 0) {
40023
+ for (const name of targetAgentNames) {
40024
+ const agent = this.agents.get(name);
40025
+ if (!agent)
39782
40026
  continue;
40027
+ if (this.queueManager.isProcessing(name)) {
40028
+ this.queueManager.enqueue(name, {
40029
+ from: "dispatch",
40030
+ text: "",
40031
+ queuedAt: new Date().toISOString(),
40032
+ chainHops
40033
+ }, this.maxQueueSize);
40034
+ this.onSystem(`Queued for ${name} (busy, ${this.queueManager.pendingCount(name)} pending)`);
40035
+ } else {
40036
+ this.processAgent(name, chainHops);
39783
40037
  }
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
40038
  }
39797
- return results;
40039
+ }
40040
+ async processAgent(agentName, chainHops) {
40041
+ const agent = this.agents.get(agentName);
40042
+ if (!agent)
40043
+ return;
40044
+ this.queueManager.setProcessing(agentName, true);
40045
+ this.onActivity(agentName, "thinking");
40046
+ const historyMaxId = this.messageId;
40047
+ const lastSeen = this.lastSeenByAgent.get(agentName) ?? 0;
40048
+ const unseen = this.conversation.filter((msg) => msg.id > lastSeen && msg.from.toUpperCase() !== agentName.toUpperCase());
40049
+ if (unseen.length === 0) {
40050
+ this.lastSeenByAgent.set(agentName, historyMaxId);
40051
+ this.queueManager.setProcessing(agentName, false);
40052
+ this.onActivity(agentName, "idle");
40053
+ this.drainQueue(agentName);
40054
+ return;
40055
+ }
40056
+ const inputMessages = this.buildInputForAgent(agentName, unseen);
40057
+ const responseText = await this.streamWithTimeout(agentName, agent, inputMessages, this.timeoutFor(agentName));
40058
+ const response = {
40059
+ id: ++this.messageId,
40060
+ from: agentName.toUpperCase(),
40061
+ text: responseText,
40062
+ createdAt: new Date().toISOString()
40063
+ };
40064
+ this.lastSeenByAgent.set(agentName, historyMaxId);
40065
+ this.conversation.push(response);
40066
+ await this.appendTranscript(response);
40067
+ this.onMessage(response);
40068
+ await this.saveManifest();
40069
+ this.queueManager.setProcessing(agentName, false);
40070
+ const isError = responseText.startsWith("[Timeout]") || responseText.startsWith("[Error]") || responseText.startsWith("[Adapter Error]");
40071
+ if (!isError) {
40072
+ this.processHandoffs(response, chainHops);
40073
+ }
40074
+ this.drainQueue(agentName);
40075
+ }
40076
+ processHandoffs(response, currentHops) {
40077
+ const selectors = extractNextSelectors([response]);
40078
+ if (selectors.length === 0)
40079
+ return;
40080
+ const humanTag = this.humanTag.toLowerCase();
40081
+ const humanName = this.humanName.toLowerCase();
40082
+ const agentSelectors = selectors.filter((s) => {
40083
+ const n = s.toLowerCase();
40084
+ return n !== humanTag && n !== humanName;
40085
+ });
40086
+ if (agentSelectors.length === 0)
40087
+ return;
40088
+ const resolvedTargets = Array.from(new Set(agentSelectors.flatMap((s) => this.resolveTargets(s))));
40089
+ if (resolvedTargets.length === 0)
40090
+ return;
40091
+ const nextHops = currentHops + 1;
40092
+ if (nextHops >= this.maxAutoHops) {
40093
+ this.onSystem(`Stopped auto-handoff after ${this.maxAutoHops} hops.`);
40094
+ return;
40095
+ }
40096
+ this.onSystem(`Auto handoff via @next to ${resolvedTargets.join(", ")}`);
40097
+ this.dispatchToTargets(resolvedTargets, nextHops);
40098
+ }
40099
+ drainQueue(agentName) {
40100
+ if (!this.queueManager.hasPending(agentName)) {
40101
+ this.onActivity(agentName, "idle");
40102
+ return;
40103
+ }
40104
+ const pending = this.queueManager.drain(agentName, this.queueTtlMs);
40105
+ if (pending.length === 0) {
40106
+ this.onActivity(agentName, "idle");
40107
+ return;
40108
+ }
40109
+ const maxHops = Math.max(...pending.map((m2) => m2.chainHops));
40110
+ if (maxHops >= this.maxAutoHops) {
40111
+ this.onSystem(`Stopped auto-handoff for ${agentName} after ${this.maxAutoHops} hops.`);
40112
+ this.onActivity(agentName, "idle");
40113
+ return;
40114
+ }
40115
+ this.processAgent(agentName, maxHops);
40116
+ }
40117
+ async streamWithTimeout(agentName, agent, messages, timeoutMs) {
40118
+ const controller = new AbortController;
40119
+ this.queueManager.setController(agentName, controller);
40120
+ let timer;
40121
+ let resolved = false;
40122
+ return new Promise((resolve4) => {
40123
+ timer = setTimeout(() => {
40124
+ if (!resolved) {
40125
+ resolved = true;
40126
+ controller.abort();
40127
+ this.onActivity(agentName, "error", "timeout");
40128
+ resolve4(`[Timeout] ${agentName} exceeded ${Math.floor(timeoutMs / 1000)}s`);
40129
+ }
40130
+ }, timeoutMs);
40131
+ (async () => {
40132
+ try {
40133
+ for await (const event of agent.stream(messages, controller.signal)) {
40134
+ if (resolved)
40135
+ return;
40136
+ if (event.type === "activity") {
40137
+ this.onActivity(agentName, event.activity, event.detail);
40138
+ } else if (event.type === "response") {
40139
+ resolved = true;
40140
+ if (timer)
40141
+ clearTimeout(timer);
40142
+ resolve4(event.text);
40143
+ return;
40144
+ } else if (event.type === "error") {
40145
+ resolved = true;
40146
+ if (timer)
40147
+ clearTimeout(timer);
40148
+ this.onActivity(agentName, "error");
40149
+ resolve4(`[Error] ${agentName}: ${event.message}`);
40150
+ return;
40151
+ }
40152
+ }
40153
+ if (!resolved) {
40154
+ resolved = true;
40155
+ if (timer)
40156
+ clearTimeout(timer);
40157
+ resolve4(`[No text response from ${agentName}]`);
40158
+ }
40159
+ } catch (err) {
40160
+ if (!resolved) {
40161
+ resolved = true;
40162
+ if (timer)
40163
+ clearTimeout(timer);
40164
+ this.onActivity(agentName, "error");
40165
+ resolve4(`[Error] ${agentName}: ${err instanceof Error ? err.message : String(err)}`);
40166
+ }
40167
+ }
40168
+ })();
40169
+ });
39798
40170
  }
39799
40171
  async appendTranscript(message) {
39800
40172
  try {
@@ -39807,23 +40179,85 @@ class Orchestrator {
39807
40179
  async saveHistory(targetPath) {
39808
40180
  await writeFile5(targetPath, JSON.stringify(this.conversation, null, 2), "utf8");
39809
40181
  }
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);
40182
+ async loadTranscript(sessionId) {
40183
+ const transcriptFile = path9.resolve(".llm-party", "sessions", `transcript-${sessionId}.jsonl`);
40184
+ const content = await readFile5(transcriptFile, "utf8");
40185
+ const lines = content.trim().split(`
40186
+ `).filter(Boolean);
40187
+ const messages = lines.map((line) => JSON.parse(line));
40188
+ this.conversation.length = 0;
40189
+ this.conversation.push(...messages);
40190
+ const maxId = messages.length > 0 ? Math.max(...messages.map((m2) => m2.id)) : 0;
40191
+ this.messageId = maxId;
40192
+ for (const agent of this.agents.keys()) {
40193
+ this.lastSeenByAgent.set(agent, maxId);
40194
+ }
40195
+ this.sessionId = sessionId;
40196
+ this.transcriptPath = transcriptFile;
40197
+ await this.loadManifest();
40198
+ return messages;
40199
+ }
40200
+ hasMessages() {
40201
+ return this.conversation.length > 0;
40202
+ }
40203
+ manifestPath() {
40204
+ const dir = path9.dirname(this.transcriptPath);
40205
+ const base = path9.basename(this.transcriptPath, ".jsonl");
40206
+ return path9.join(dir, `${base}.manifest.json`);
40207
+ }
40208
+ async saveManifest() {
40209
+ this.manifestSavePromise = this.manifestSavePromise.then(async () => {
40210
+ try {
40211
+ const agents = {};
40212
+ for (const [name, adapter] of this.agents) {
40213
+ const sid = adapter.getSdkSessionId();
40214
+ agents[name] = {
40215
+ provider: adapter.provider,
40216
+ sdkSessionId: sid || "",
40217
+ lastSeenId: this.lastSeenByAgent.get(name) ?? 0
40218
+ };
40219
+ }
40220
+ const manifest = {
40221
+ orchestratorSessionId: this.sessionId,
40222
+ messageId: this.messageId,
40223
+ stickyTarget: this.stickyTargets,
40224
+ agents
40225
+ };
40226
+ await writeFile5(this.manifestPath(), JSON.stringify(manifest, null, 2), "utf8");
40227
+ } catch (err) {
40228
+ console.error("[manifest] write failed:", err);
40229
+ }
39818
40230
  });
40231
+ return this.manifestSavePromise;
40232
+ }
40233
+ async loadManifest() {
39819
40234
  try {
39820
- return await Promise.race([agent.send(messages, controller.signal), timeoutPromise]);
39821
- } finally {
39822
- if (timer) {
39823
- clearTimeout(timer);
40235
+ const content = await readFile5(this.manifestPath(), "utf8");
40236
+ const manifest = JSON.parse(content);
40237
+ if (Array.isArray(manifest.stickyTarget)) {
40238
+ this.stickyTargets = manifest.stickyTarget;
40239
+ }
40240
+ const agentMap = manifest.agents ?? {};
40241
+ for (const [name, data] of Object.entries(agentMap)) {
40242
+ const adapter = this.agents.get(name);
40243
+ const agentData = data;
40244
+ if (!adapter || !agentData)
40245
+ continue;
40246
+ if (typeof agentData.sdkSessionId === "string" && agentData.sdkSessionId) {
40247
+ adapter.setSdkSessionId(agentData.sdkSessionId);
40248
+ }
40249
+ if (typeof agentData.lastSeenId === "number") {
40250
+ this.lastSeenByAgent.set(name, agentData.lastSeenId);
40251
+ }
39824
40252
  }
40253
+ } catch (err) {
40254
+ console.error("[manifest] load failed (agents start fresh):", err);
39825
40255
  }
39826
40256
  }
40257
+ async abortAll() {
40258
+ this.queueManager.abortAll();
40259
+ await this.saveManifest();
40260
+ }
39827
40261
  timeoutFor(agentName) {
39828
40262
  return this.agentTimeouts.get(agentName) ?? this.defaultTimeout;
39829
40263
  }
@@ -39857,6 +40291,21 @@ function createSessionId() {
39857
40291
  const rand = randomBytes(4).toString("hex");
39858
40292
  return `${timestamp}-${process.pid}-${rand}`;
39859
40293
  }
40294
+ function extractNextSelectors(messages) {
40295
+ const selectors = [];
40296
+ for (const msg of messages) {
40297
+ const regex = /@next\s*:\s*([A-Za-z0-9_-]+)/gi;
40298
+ let match = null;
40299
+ while ((match = regex.exec(msg.text)) !== null) {
40300
+ selectors.push(match[1]);
40301
+ }
40302
+ const controlMatch = msg.text.match(/@control[\s\S]*?next\s*:\s*([A-Za-z0-9_-]+)[\s\S]*?@end/i);
40303
+ if (controlMatch?.[1]) {
40304
+ selectors.push(controlMatch[1]);
40305
+ }
40306
+ }
40307
+ return selectors;
40308
+ }
39860
40309
 
39861
40310
  // src/ui/App.tsx
39862
40311
  import { spawn as spawn4 } from "child_process";
@@ -39868,14 +40317,56 @@ function nextSystemId() {
39868
40317
  systemIdCounter -= 1;
39869
40318
  return systemIdCounter;
39870
40319
  }
39871
- function useOrchestrator(orchestrator, maxAutoHops) {
40320
+ function useOrchestrator(orchestrator) {
39872
40321
  const [messages, setMessages] = createSignal([]);
39873
40322
  const [agentStates, setAgentStates] = createSignal(new Map(orchestrator.listAgents().map((a) => [a.name, "idle"])));
39874
- const [stickyTarget, setStickyTarget] = createSignal(undefined);
40323
+ const [stickyTarget, setStickyTargetSignal] = createSignal(orchestrator.getStickyTarget());
40324
+ const setStickyTarget = (targets) => {
40325
+ setStickyTargetSignal(targets);
40326
+ orchestrator.setStickyTarget(targets);
40327
+ };
39875
40328
  const [dispatching, setDispatching] = createSignal(false);
40329
+ const [queueCounts, setQueueCounts] = createSignal(new Map(orchestrator.listAgents().map((a) => [a.name, 0])));
39876
40330
  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]));
40331
+ const agentProviders = new Map(orchestrator.listAgents().map((a) => [a.name.toUpperCase(), a.provider]));
40332
+ const agentTagsMap = new Map(orchestrator.listAgents().map((a) => [a.name.toUpperCase(), a.tag]));
40333
+ const nameMap = new Map(orchestrator.listAgents().map((a) => [a.name.toUpperCase(), a.name]));
40334
+ const updateQueueCounts = () => {
40335
+ const status = orchestrator.getQueueStatus();
40336
+ setQueueCounts(new Map(status.map((s) => [s.name, s.pending])));
40337
+ };
40338
+ orchestrator.setCallbacks((msg) => {
40339
+ const provider = agentProviders.get(msg.from) ?? "";
40340
+ const tag = agentTagsMap.get(msg.from) ?? "";
40341
+ const display = {
40342
+ ...msg,
40343
+ type: "agent",
40344
+ provider,
40345
+ tag
40346
+ };
40347
+ setMessages((prev) => [...prev, display]);
40348
+ const originalName = nameMap.get(msg.from) ?? msg.from;
40349
+ setAgentStates((prev) => {
40350
+ const next = new Map(prev);
40351
+ const isErr = msg.text.startsWith("[Adapter Error]") || msg.text.startsWith("[Error]") || msg.text.startsWith("[Timeout]");
40352
+ next.set(originalName, isErr ? "error" : "idle");
40353
+ return next;
40354
+ });
40355
+ setDispatching(orchestrator.dispatching);
40356
+ updateQueueCounts();
40357
+ }, (agentName, activity) => {
40358
+ const originalName = nameMap.get(agentName.toUpperCase()) ?? agentName;
40359
+ setAgentStates((prev) => {
40360
+ const next = new Map(prev);
40361
+ next.set(originalName, activity);
40362
+ return next;
40363
+ });
40364
+ setDispatching(orchestrator.dispatching);
40365
+ updateQueueCounts();
40366
+ }, (text) => {
40367
+ addSystemMessage(text);
40368
+ updateQueueCounts();
40369
+ });
39879
40370
  const dispatch = async (line) => {
39880
40371
  if (!line.trim())
39881
40372
  return;
@@ -39889,7 +40380,7 @@ function useOrchestrator(orchestrator, maxAutoHops) {
39889
40380
  if (explicitTargets && explicitTargets.length > 0) {
39890
40381
  setStickyTarget(explicitTargets);
39891
40382
  }
39892
- const targets = explicitTargets ?? stickyTarget();
40383
+ const targets = explicitTargets ?? stickyTarget() ?? Array.from(orchestrator.listAgents().map((a) => a.name));
39893
40384
  if (!projectFolderReady) {
39894
40385
  await initProjectFolder(process.cwd());
39895
40386
  projectFolderReady = true;
@@ -39902,11 +40393,7 @@ function useOrchestrator(orchestrator, maxAutoHops) {
39902
40393
  };
39903
40394
  setMessages((prev) => [...prev, userDisplay]);
39904
40395
  setDispatching(true);
39905
- try {
39906
- await dispatchWithHandoffs(orchestrator, targets, maxAutoHops, agentProviders, agentTags, setMessages, setAgentStates);
39907
- } finally {
39908
- setDispatching(false);
39909
- }
40396
+ orchestrator.dispatchToTargets(targets);
39910
40397
  };
39911
40398
  const addSystemMessage = (text) => {
39912
40399
  const msg = {
@@ -39918,89 +40405,17 @@ function useOrchestrator(orchestrator, maxAutoHops) {
39918
40405
  };
39919
40406
  setMessages((prev) => [...prev, msg]);
39920
40407
  };
40408
+ const addDisplayMessage = (msg) => {
40409
+ setMessages((prev) => [...prev, msg]);
40410
+ };
39921
40411
  const clearMessages = () => {
39922
40412
  orchestrator.clearConversation();
39923
40413
  setMessages([]);
39924
40414
  };
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
- }
40415
+ const refreshStickyTarget = () => {
40416
+ setStickyTargetSignal(orchestrator.getStickyTarget());
40417
+ };
40418
+ return { messages, agentStates, queueCounts, stickyTarget, dispatching, dispatch, addSystemMessage, addDisplayMessage, clearMessages, refreshStickyTarget };
40004
40419
  }
40005
40420
  function getChangedFiles() {
40006
40421
  return new Promise((resolve4) => {
@@ -40015,21 +40430,6 @@ function getChangedFiles() {
40015
40430
  });
40016
40431
  });
40017
40432
  }
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
40433
  function parseRouting(line) {
40034
40434
  const mentionRegex = /(^|[^A-Za-z0-9_-])@([A-Za-z0-9_-]+)\b/g;
40035
40435
  const mentions = [];
@@ -40118,27 +40518,21 @@ function MessageBubble(props) {
40118
40518
  }
40119
40519
 
40120
40520
  // src/ui/constants.ts
40121
- var SPINNER_FRAMES = ["\u280B", "\u2819", "\u281A", "\u281E", "\u2816", "\u2826", "\u2834", "\u2832", "\u2833", "\u2813"];
40521
+ var SPINNER_FRAMES = "\u280B\u2819\u281A\u281E\u2816\u2826\u2834\u2832\u2833\u2813".split("");
40522
+ var ACTIVITY_SPINNERS = {
40523
+ thinking_bkp: "\u2733\u2734\u2736\u2735\u2737\u2738\u2739\u273A".split(""),
40524
+ thinking: " ..ooOO@@@@@@*".split(""),
40525
+ writing: "\u258F\u258E\u258D\u258C\u258B\u258A\u2589\u2588\u2589\u258A\u258B\u258C\u258D\u258E".split(""),
40526
+ reading: ["\u2801", "\u2809", "\u280B", "\u281B", "\u281F", "\u283F", "\u287F", "\u28FF"],
40527
+ running: [" ", "\u2591", "\u2592", "\u2593", "\u2588"],
40528
+ searching: "\u25D0\u25D3\u25D1\u25D2".split("")
40529
+ };
40530
+ var SUPERSCRIPT_DIGITS = ["\u2070", "\xB9", "\xB2", "\xB3", "\u2074", "\u2075", "\u2076", "\u2077", "\u2078", "\u2079"];
40122
40531
 
40123
40532
  // src/ui/StatusBar.tsx
40124
40533
  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
- }
40534
+ var [globalTick, setGlobalTick] = createSignal(0);
40535
+ setInterval(() => setGlobalTick((t2) => t2 + 1), 80);
40142
40536
  function StatusBar(props) {
40143
40537
  const targetNames = () => props.stickyTarget ?? props.agents.map((a) => a.name);
40144
40538
  const isTargeted = (name) => targetNames().includes(name);
@@ -40186,65 +40580,59 @@ function StatusBar(props) {
40186
40580
  }), _el$4);
40187
40581
  insertNode(_el$4, createTextNode(`| /info`));
40188
40582
  setProp(_el$6, "flexDirection", "row");
40189
- setProp(_el$6, "gap", 2);
40190
40583
  insert(_el$6, createComponent2(For, {
40191
40584
  get each() {
40192
40585
  return props.agents;
40193
40586
  },
40194
- children: (a) => createComponent2(AgentChip, {
40587
+ children: (a, i) => [createComponent2(AgentChip, {
40195
40588
  get name() {
40196
40589
  return a.name;
40197
40590
  },
40198
- get state() {
40199
- return props.agentStates.get(a.name) ?? "idle";
40200
- }
40201
- })
40591
+ getState: () => props.agentStates.get(a.name) ?? "idle",
40592
+ getQueued: () => props.queueCounts?.get(a.name) ?? 0
40593
+ }), memo2(() => memo2(() => i() < props.agents.length - 1)() ? (() => {
40594
+ var _el$12 = createElement("text");
40595
+ insertNode(_el$12, createTextNode(` \u2502 `));
40596
+ effect((_$p) => setProp(_el$12, "fg", COLORS.textDim, _$p));
40597
+ return _el$12;
40598
+ })() : null)]
40202
40599
  }));
40203
40600
  effect((_$p) => setProp(_el$4, "fg", COLORS.textDim, _$p));
40204
40601
  return _el$;
40205
40602
  })();
40206
40603
  }
40604
+ function toSuperscript(n) {
40605
+ if (n <= 0)
40606
+ return "";
40607
+ return String(n).split("").map((d2) => SUPERSCRIPT_DIGITS[parseInt(d2, 10)] ?? d2).join("");
40608
+ }
40207
40609
  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
- });
40610
+ const isActive = () => {
40611
+ const s = props.getState();
40612
+ return s !== "idle" && s !== "error" && s !== "queued";
40613
+ };
40614
+ const frames = () => ACTIVITY_SPINNERS[props.getState()] ?? SPINNER_FRAMES;
40615
+ const tick = () => globalTick();
40616
+ const spinner = () => isActive() ? frames()[tick() % frames().length] : " ";
40617
+ const pulseColor = () => isActive() ? PULSE_COLORS[tick() % PULSE_COLORS.length] : COLORS.textMuted;
40618
+ const queueSlot = () => props.getQueued() > 0 ? toSuperscript(props.getQueued()) : " ";
40619
+ const stateColor = () => {
40620
+ if (props.getState() === "error")
40621
+ return COLORS.error;
40622
+ if (isActive())
40623
+ return pulseColor();
40624
+ return COLORS.textMuted;
40625
+ };
40626
+ return (() => {
40627
+ var _el$14 = createElement("text"), _el$15 = createTextNode(` `);
40628
+ insertNode(_el$14, _el$15);
40629
+ insert(_el$14, queueSlot, _el$15);
40630
+ insert(_el$14, () => props.name, _el$15);
40631
+ insert(_el$14, spinner, null);
40632
+ insert(_el$14, () => props.getState() === "error" ? " ERR" : "", null);
40633
+ effect((_$p) => setProp(_el$14, "fg", stateColor(), _$p));
40634
+ return _el$14;
40635
+ })();
40248
40636
  }
40249
40637
 
40250
40638
  // src/ui/InputLine.tsx
@@ -40500,7 +40888,7 @@ import { userInfo as userInfo2 } from "os";
40500
40888
 
40501
40889
  // src/config/detector.ts
40502
40890
  import { spawn as spawn3 } from "child_process";
40503
- var DETECT_TIMEOUT = 5000;
40891
+ var DETECT_TIMEOUT = 1e4;
40504
40892
  function detectBinary(command) {
40505
40893
  return new Promise((resolve4) => {
40506
40894
  const timer = setTimeout(() => resolve4({ available: false }), DETECT_TIMEOUT);
@@ -42323,12 +42711,15 @@ function App(props) {
42323
42711
  const {
42324
42712
  messages,
42325
42713
  agentStates,
42714
+ queueCounts,
42326
42715
  stickyTarget,
42327
42716
  dispatching,
42328
42717
  dispatch,
42329
42718
  addSystemMessage,
42330
- clearMessages
42331
- } = useOrchestrator(props.orchestrator, props.maxAutoHops);
42719
+ addDisplayMessage,
42720
+ clearMessages,
42721
+ refreshStickyTarget
42722
+ } = useOrchestrator(props.orchestrator);
42332
42723
  const humanName = props.orchestrator.getHumanName();
42333
42724
  const agents = props.orchestrator.listAgents();
42334
42725
  let scrollRef = null;
@@ -42336,17 +42727,48 @@ function App(props) {
42336
42727
  const [freshConfig, setFreshConfig] = createSignal(props.config);
42337
42728
  const [showAgents, setShowAgents] = createSignal(false);
42338
42729
  const [showInfo, setShowInfo] = createSignal(false);
42339
- process.on("SIGINT", () => renderer.destroy());
42340
- process.on("SIGTERM", () => renderer.destroy());
42341
- process.on("SIGHUP", () => renderer.destroy());
42730
+ const resumeSession = async (sessionId) => {
42731
+ try {
42732
+ const restored = await props.orchestrator.loadTranscript(sessionId);
42733
+ const displayMsgs = restored.map((msg) => {
42734
+ const isHuman = msg.from.toUpperCase() === humanName.toUpperCase();
42735
+ const agentInfo = agents.find((a) => a.name.toUpperCase() === msg.from.toUpperCase());
42736
+ return {
42737
+ ...msg,
42738
+ type: isHuman ? "user" : "agent",
42739
+ provider: agentInfo?.provider ?? "",
42740
+ tag: agentInfo?.tag ?? ""
42741
+ };
42742
+ });
42743
+ for (const msg of displayMsgs) {
42744
+ addDisplayMessage(msg);
42745
+ }
42746
+ refreshStickyTarget();
42747
+ addSystemMessage(`Resumed session ${sessionId} (${restored.length} messages)`);
42748
+ } catch (err) {
42749
+ addSystemMessage(`Failed to resume: ${err instanceof Error ? err.message : String(err)}`);
42750
+ }
42751
+ };
42752
+ onMount(() => {
42753
+ if (props.resumeSessionId) {
42754
+ resumeSession(props.resumeSessionId);
42755
+ }
42756
+ });
42757
+ process.on("SIGINT", () => gracefulExit());
42758
+ process.on("SIGTERM", () => gracefulExit());
42759
+ process.on("SIGHUP", () => gracefulExit());
42342
42760
  process.on("SIGTSTP", () => {
42343
42761
  process.once("SIGCONT", () => renderer.resume());
42344
42762
  renderer.suspend();
42345
42763
  });
42346
- const gracefulExit = () => {
42764
+ const gracefulExit = async () => {
42765
+ await props.orchestrator.abortAll();
42347
42766
  renderer.destroy();
42348
42767
  const adapters = props.orchestrator.getAdapters();
42349
- Promise.allSettled(adapters.map((a) => a.destroy()));
42768
+ Promise.allSettled(adapters.map((a) => a.destroy())).finally(() => {
42769
+ process.exit(0);
42770
+ });
42771
+ setTimeout(() => process.exit(0), 2000);
42350
42772
  };
42351
42773
  useKeyboard((key) => {
42352
42774
  if (key.ctrl && key.name === "p") {
@@ -42425,6 +42847,19 @@ ${files.map((f) => ` ${f}`).join(`
42425
42847
  setShowInfo(true);
42426
42848
  return;
42427
42849
  }
42850
+ if (line.startsWith("/resume")) {
42851
+ const sid = line.replace("/resume", "").trim();
42852
+ if (!sid) {
42853
+ addSystemMessage("Usage: /resume <sessionId>");
42854
+ return;
42855
+ }
42856
+ if (props.orchestrator.hasMessages()) {
42857
+ addSystemMessage("Resume only works before the first message.");
42858
+ return;
42859
+ }
42860
+ await resumeSession(sid);
42861
+ return;
42862
+ }
42428
42863
  if (line.startsWith("/flood")) {
42429
42864
  const args = line.split(/\s+/);
42430
42865
  const count = parseInt(args[1], 10) || 50;
@@ -42489,13 +42924,16 @@ ${lines.join(`
42489
42924
  },
42490
42925
  get stickyTarget() {
42491
42926
  return stickyTarget();
42927
+ },
42928
+ get queueCounts() {
42929
+ return queueCounts();
42492
42930
  }
42493
42931
  }), null);
42494
42932
  insert(_el$, createComponent2(InputLine, {
42495
42933
  humanName,
42496
42934
  onSubmit: handleSubmit,
42497
42935
  get disabled() {
42498
- return dispatching() || showAgents() || showInfo();
42936
+ return showAgents() || showInfo();
42499
42937
  },
42500
42938
  get disabledMessage() {
42501
42939
  return showAgents() ? "" : undefined;
@@ -42531,6 +42969,8 @@ ${lines.join(`
42531
42969
  async function main2() {
42532
42970
  const appRoot = path11.resolve(path11.dirname(fileURLToPath3(import.meta.url)), "..");
42533
42971
  await initLlmPartyHome(appRoot);
42972
+ const resumeIndex = process.argv.indexOf("--resume");
42973
+ const resumeSessionId = resumeIndex !== -1 ? process.argv[resumeIndex + 1] : undefined;
42534
42974
  const rendererConfig = {
42535
42975
  exitOnCtrlC: false,
42536
42976
  useMouse: true,
@@ -42541,14 +42981,14 @@ async function main2() {
42541
42981
  await render(() => ConfigWizard({
42542
42982
  isFirstRun: true,
42543
42983
  onComplete: async () => {
42544
- await bootApp(appRoot, rendererConfig);
42984
+ await bootApp(appRoot, rendererConfig, resumeSessionId);
42545
42985
  }
42546
42986
  }), rendererConfig);
42547
42987
  } else {
42548
- await bootApp(appRoot, rendererConfig);
42988
+ await bootApp(appRoot, rendererConfig, resumeSessionId);
42549
42989
  }
42550
42990
  }
42551
- async function bootApp(appRoot, rendererConfig) {
42991
+ async function bootApp(appRoot, rendererConfig, resumeSessionId) {
42552
42992
  const configPath = await resolveConfigPath(appRoot);
42553
42993
  const config = await loadConfig2(configPath);
42554
42994
  const humanName = config.humanName?.trim() || "USER";
@@ -42594,7 +43034,7 @@ async function bootApp(appRoot, rendererConfig) {
42594
43034
  const promptParts = [mergedBase];
42595
43035
  if (agent.prompts && agent.prompts.length > 0) {
42596
43036
  const extraPaths = agent.prompts.map((p) => resolveFromConfig(p));
42597
- const extraParts = await Promise.all(extraPaths.map((p) => readFile5(p, "utf8")));
43037
+ const extraParts = await Promise.all(extraPaths.map((p) => readFile6(p, "utf8")));
42598
43038
  promptParts.push(...extraParts);
42599
43039
  }
42600
43040
  const promptTemplate = promptParts.join(`
@@ -42638,8 +43078,8 @@ ${mySkills.map((s) => `- ${s}`).join(`
42638
43078
  }));
42639
43079
  const defaultTimeout = typeof config.timeout === "number" && config.timeout > 0 ? config.timeout * 1000 : 600000;
42640
43080
  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);
43081
+ 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 });
43082
+ await render(() => App({ orchestrator, config, configPath, resumeSessionId }), rendererConfig);
42643
43083
  }
42644
43084
  function resolveMaxAutoHops(value) {
42645
43085
  if (typeof value === "number" && value === 0) {