llm-party-cli 0.7.1 → 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;
@@ -37551,8 +37514,11 @@ var render = async (node, rendererOrConfig = {}) => {
37551
37514
  import { query } from "@anthropic-ai/claude-agent-sdk";
37552
37515
 
37553
37516
  // src/adapters/base.ts
37554
- function formatTranscript(messages) {
37555
- return messages.map((m2) => `[${m2.from}]: ${m2.text}`).join(`
37517
+ function formatTranscript(messages, agentName, humanName) {
37518
+ return messages.map((m2) => {
37519
+ const role = m2.from === agentName ? "you" : m2.from === humanName ? "user" : "agent";
37520
+ return `${m2.from} (${role}):: ${m2.text}`;
37521
+ }).join(`
37556
37522
 
37557
37523
  `);
37558
37524
  }
@@ -37561,13 +37527,15 @@ function formatTranscript(messages) {
37561
37527
  class ClaudeBaseAdapter {
37562
37528
  name;
37563
37529
  model;
37530
+ humanName;
37564
37531
  systemPrompt = "";
37565
37532
  sessionId = "";
37566
37533
  runtimeEnv = {};
37567
37534
  claudeExecutable;
37568
- constructor(name, model) {
37535
+ constructor(name, model, humanName) {
37569
37536
  this.name = name;
37570
37537
  this.model = model;
37538
+ this.humanName = humanName;
37571
37539
  }
37572
37540
  async init(config) {
37573
37541
  this.systemPrompt = config.resolvedPrompt ?? "";
@@ -37575,15 +37543,23 @@ class ClaudeBaseAdapter {
37575
37543
  this.claudeExecutable = config.executablePath ?? process.env.CLAUDE_CODE_EXECUTABLE;
37576
37544
  }
37577
37545
  async buildEnv(config) {
37578
- return { ...process.env, ...config.env ?? {} };
37579
- }
37580
- async send(messages, signal) {
37581
- return await this.querySDK(formatTranscript(messages), signal);
37582
- }
37583
- async destroy() {
37584
- return;
37546
+ const configEnv = config.env ?? {};
37547
+ const mapped = {};
37548
+ if (configEnv.AUTH_URL) {
37549
+ mapped.ANTHROPIC_BASE_URL = configEnv.AUTH_URL;
37550
+ }
37551
+ if (configEnv.AUTH_TOKEN) {
37552
+ mapped.ANTHROPIC_AUTH_TOKEN = configEnv.AUTH_TOKEN;
37553
+ }
37554
+ if (configEnv.AUTH_URL || configEnv.AUTH_TOKEN) {
37555
+ mapped.ANTHROPIC_DEFAULT_HAIKU_MODEL = this.model;
37556
+ mapped.ANTHROPIC_DEFAULT_SONNET_MODEL = this.model;
37557
+ mapped.ANTHROPIC_DEFAULT_OPUS_MODEL = this.model;
37558
+ }
37559
+ return { ...process.env, ...mapped, ...configEnv };
37585
37560
  }
37586
- async querySDK(transcript, signal) {
37561
+ async* stream(messages, signal) {
37562
+ const transcript = formatTranscript(messages, this.name, this.humanName);
37587
37563
  const executableOpt = this.claudeExecutable ? { pathToClaudeCodeExecutable: this.claudeExecutable } : {};
37588
37564
  const options = {
37589
37565
  cwd: process.cwd(),
@@ -37597,24 +37573,69 @@ class ClaudeBaseAdapter {
37597
37573
  ...this.sessionId ? { resume: this.sessionId } : {},
37598
37574
  ...executableOpt
37599
37575
  };
37576
+ yield { type: "activity", activity: "thinking" };
37600
37577
  try {
37601
37578
  for await (const message of query({ prompt: transcript, options })) {
37602
37579
  if (signal?.aborted) {
37603
- return `[Aborted] ${this.name} was cancelled`;
37580
+ yield { type: "error", message: `[Aborted] ${this.name} was cancelled` };
37581
+ return;
37604
37582
  }
37605
- 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") {
37606
37586
  this.sessionId = message.session_id;
37607
37587
  }
37608
- 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) {
37609
37616
  const result = message.result;
37610
- 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;
37611
37621
  }
37612
37622
  }
37613
37623
  } catch (err) {
37614
37624
  console.log(`[${this.name}] SDK error:`, err);
37615
- throw err;
37625
+ yield { type: "error", message: err instanceof Error ? err.message : String(err) };
37626
+ return;
37616
37627
  }
37617
- 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;
37618
37639
  }
37619
37640
  }
37620
37641
 
@@ -38061,11 +38082,15 @@ class CodexAdapter {
38061
38082
  name;
38062
38083
  provider = "codex";
38063
38084
  model;
38085
+ humanName;
38064
38086
  codex;
38065
38087
  thread;
38066
- constructor(name, model) {
38088
+ threadId = "";
38089
+ threadOptions = {};
38090
+ constructor(name, model, humanName) {
38067
38091
  this.name = name;
38068
38092
  this.model = model;
38093
+ this.humanName = humanName;
38069
38094
  }
38070
38095
  async init(config) {
38071
38096
  const cliPath = config.executablePath ?? process.env.CODEX_CLI_EXECUTABLE;
@@ -38075,31 +38100,84 @@ class CodexAdapter {
38075
38100
  ...config.env?.OPENAI_API_KEY ? { apiKey: config.env.OPENAI_API_KEY } : {},
38076
38101
  ...systemPrompt ? { config: { developer_instructions: systemPrompt } } : {}
38077
38102
  });
38078
- this.thread = this.codex.startThread({
38103
+ this.threadOptions = {
38079
38104
  model: this.model,
38080
38105
  sandboxMode: "danger-full-access",
38081
38106
  workingDirectory: process.cwd(),
38082
38107
  approvalPolicy: "never",
38083
38108
  skipGitRepoCheck: true
38084
- });
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 ?? "";
38085
38121
  }
38086
- async send(messages, signal) {
38122
+ async* stream(messages, signal) {
38087
38123
  if (!this.thread) {
38088
- return "[Codex thread not initialized]";
38124
+ yield { type: "error", message: "[Codex thread not initialized]" };
38125
+ return;
38089
38126
  }
38090
38127
  if (signal?.aborted) {
38091
- return "[Aborted] Codex was cancelled";
38128
+ yield { type: "error", message: "[Aborted] Codex was cancelled" };
38129
+ return;
38092
38130
  }
38093
- const turn = await this.thread.run(formatTranscript(messages));
38094
- if (turn.finalResponse && turn.finalResponse.length > 0) {
38095
- 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) };
38096
38169
  }
38097
- return "[No text response from Codex]";
38098
38170
  }
38099
38171
  async destroy() {
38100
38172
  this.thread = undefined;
38101
38173
  this.codex = undefined;
38102
38174
  }
38175
+ getSdkSessionId() {
38176
+ return this.threadId;
38177
+ }
38178
+ setSdkSessionId(id) {
38179
+ this.threadId = id;
38180
+ }
38103
38181
  }
38104
38182
 
38105
38183
  // node_modules/@github/copilot-sdk/dist/client.js
@@ -39276,14 +39354,17 @@ class CopilotAdapter {
39276
39354
  name;
39277
39355
  provider = "copilot";
39278
39356
  model;
39357
+ humanName;
39279
39358
  client;
39280
39359
  session;
39281
39360
  systemPrompt = "";
39282
39361
  cliPath;
39283
39362
  timeout = 600000;
39284
- constructor(name, model) {
39363
+ copilotSessionId = "";
39364
+ constructor(name, model, humanName) {
39285
39365
  this.name = name;
39286
39366
  this.model = model;
39367
+ this.humanName = humanName;
39287
39368
  }
39288
39369
  async init(config) {
39289
39370
  this.systemPrompt = config.resolvedPrompt ?? "";
@@ -39293,123 +39374,186 @@ class CopilotAdapter {
39293
39374
  }
39294
39375
  await this.createSession();
39295
39376
  }
39296
- async send(messages, signal) {
39377
+ async* stream(messages, signal) {
39297
39378
  if (!this.session) {
39298
- return "[Copilot session not initialized]";
39379
+ yield { type: "error", message: "[Copilot session not initialized]" };
39380
+ return;
39299
39381
  }
39300
39382
  if (signal?.aborted) {
39301
- return "[Aborted] Copilot was cancelled";
39383
+ yield { type: "error", message: "[Aborted] Copilot was cancelled" };
39384
+ return;
39302
39385
  }
39303
- const transcript = formatTranscript(messages);
39386
+ yield { type: "activity", activity: "thinking" };
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();
39304
39468
  try {
39305
- return await this.sendToSession(transcript);
39306
- } catch (err) {
39307
- const message = err instanceof Error ? err.message : String(err);
39308
- if (message.includes("Session not found")) {
39309
- await this.createSession();
39310
- 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
+ }
39311
39480
  }
39312
- throw err;
39481
+ } finally {
39482
+ clearTimeout(timer);
39483
+ unsubscribe?.();
39313
39484
  }
39485
+ yield { type: "activity", activity: "idle" };
39486
+ yield { type: "response", text: `[No text response from ${this.name}]` };
39314
39487
  }
39315
39488
  async destroy() {
39316
39489
  if (this.session) {
39317
- await this.session.disconnect();
39490
+ try {
39491
+ await this.session.disconnect();
39492
+ } catch (err) {
39493
+ console.error(`[${this.name}] disconnect:`, err);
39494
+ }
39318
39495
  }
39319
39496
  if (this.client) {
39320
- await this.client.stop();
39497
+ try {
39498
+ await this.client.stop();
39499
+ } catch (err) {
39500
+ console.error(`[${this.name}] stop:`, err);
39501
+ }
39321
39502
  }
39322
39503
  }
39504
+ getSdkSessionId() {
39505
+ return this.copilotSessionId;
39506
+ }
39507
+ setSdkSessionId(id) {
39508
+ this.copilotSessionId = id;
39509
+ }
39323
39510
  async createSession() {
39324
39511
  if (this.session) {
39325
39512
  try {
39326
39513
  await this.session.disconnect();
39327
- } catch {}
39514
+ } catch (err) {
39515
+ console.error(`[${this.name}] stale session disconnect:`, err);
39516
+ }
39328
39517
  }
39329
39518
  if (this.client) {
39330
39519
  try {
39331
39520
  await this.client.stop();
39332
- } catch {}
39521
+ } catch (err) {
39522
+ console.error(`[${this.name}] stale client stop:`, err);
39523
+ }
39333
39524
  }
39334
39525
  process.env.NODE_NO_WARNINGS = "1";
39335
39526
  this.client = new CopilotClient({
39336
39527
  ...this.cliPath ? { cliPath: this.cliPath } : {}
39337
39528
  });
39338
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()}`;
39339
39544
  this.session = await this.client.createSession({
39545
+ sessionId,
39340
39546
  model: this.model,
39341
39547
  systemMessage: { content: this.systemPrompt },
39342
39548
  onPermissionRequest: approveAll
39343
39549
  });
39344
- }
39345
- async sendToSession(transcript) {
39346
- if (!this.session) {
39347
- return "[Copilot session not initialized]";
39348
- }
39349
- const response = await this.session.sendAndWait({ prompt: transcript }, this.timeout);
39350
- if (response && response.data && typeof response.data.content === "string" && response.data.content.length > 0) {
39351
- return response.data.content;
39352
- }
39353
- return "[No text response from Copilot]";
39550
+ this.copilotSessionId = sessionId;
39354
39551
  }
39355
39552
  }
39356
39553
 
39357
- // src/adapters/glm.ts
39358
- import { spawn as spawn3 } from "child_process";
39359
- class GlmAdapter extends ClaudeBaseAdapter {
39360
- provider = "glm";
39361
- async buildEnv(config) {
39362
- const aliasEnv = await loadGlmAliasEnv();
39363
- return { ...process.env, ...aliasEnv, ...config.env ?? {} };
39364
- }
39365
- }
39366
- function detectShell() {
39367
- if (process.env.SHELL) {
39368
- return process.env.SHELL;
39369
- }
39370
- return "/bin/sh";
39371
- }
39372
- async function loadGlmAliasEnv() {
39373
- const shell = detectShell();
39374
- const isInteractive = shell.endsWith("zsh") || shell.endsWith("bash");
39375
- const args = isInteractive ? ["-ic", "alias glm"] : ["-c", "alias glm"];
39376
- return new Promise((resolve4) => {
39377
- const child = spawn3(shell, args, {
39378
- cwd: process.cwd(),
39379
- env: process.env,
39380
- stdio: ["ignore", "pipe", "pipe"]
39381
- });
39382
- let stdout = "";
39383
- const timeout = setTimeout(() => {
39384
- child.kill("SIGTERM");
39385
- resolve4({});
39386
- }, 5000);
39387
- child.stdout.on("data", (chunk) => {
39388
- stdout += String(chunk);
39389
- });
39390
- child.on("close", (code) => {
39391
- clearTimeout(timeout);
39392
- if (code !== 0) {
39393
- resolve4({});
39394
- return;
39395
- }
39396
- const env2 = {};
39397
- const tokens = stdout.match(/[A-Z_]+="[^"]*"/g) ?? [];
39398
- for (const token of tokens) {
39399
- const eqIdx = token.indexOf("=");
39400
- if (eqIdx === -1)
39401
- continue;
39402
- const key = token.slice(0, eqIdx);
39403
- const raw = token.slice(eqIdx + 1);
39404
- env2[key] = raw.replace(/^"|"$/g, "");
39405
- }
39406
- resolve4(env2);
39407
- });
39408
- child.on("error", () => {
39409
- clearTimeout(timeout);
39410
- resolve4({});
39411
- });
39412
- });
39554
+ // src/adapters/custom.ts
39555
+ class CustomAdapter extends ClaudeBaseAdapter {
39556
+ provider = "custom";
39413
39557
  }
39414
39558
 
39415
39559
  // src/config/loader.ts
@@ -39448,27 +39592,11 @@ var PROVIDERS = [
39448
39592
  defaultTag: "copilot",
39449
39593
  detectCommand: "copilot",
39450
39594
  detectType: "binary"
39451
- },
39452
- {
39453
- id: "glm",
39454
- displayName: "GLM",
39455
- description: "glm alias configured on Claude Code CLI",
39456
- unavailableHint: "glm shell alias not configured",
39457
- defaultModel: "glm-5",
39458
- defaultTag: "glm",
39459
- detectCommand: "glm",
39460
- detectType: "alias",
39461
- env: {
39462
- ANTHROPIC_BASE_URL: "https://api.z.ai/api/anthropic",
39463
- ANTHROPIC_DEFAULT_HAIKU_MODEL: "glm-4.5-air",
39464
- ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-4.5",
39465
- ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-5"
39466
- }
39467
39595
  }
39468
39596
  ];
39469
39597
 
39470
39598
  // src/config/loader.ts
39471
- var VALID_PROVIDER_IDS = new Set(PROVIDERS.map((p) => p.id));
39599
+ var VALID_PROVIDER_IDS = new Set([...PROVIDERS.map((p) => p.id), "custom"]);
39472
39600
  var LLM_PARTY_HOME = path8.join(homedir(), ".llm-party");
39473
39601
  var MIND_MAP_INDEX = `# Living Memory Neural Network
39474
39602
 
@@ -39654,15 +39782,29 @@ async function configExists() {
39654
39782
  return false;
39655
39783
  }
39656
39784
  }
39785
+ function migrateProviders(data) {
39786
+ if (!Array.isArray(data.agents))
39787
+ return;
39788
+ for (const agent of data.agents) {
39789
+ if (!agent || typeof agent !== "object")
39790
+ continue;
39791
+ if (typeof agent.provider === "string" && !VALID_PROVIDER_IDS.has(agent.provider)) {
39792
+ agent.provider = "custom";
39793
+ if (!agent.cli)
39794
+ agent.cli = "claude";
39795
+ }
39796
+ }
39797
+ }
39657
39798
  async function loadConfig2(configPath) {
39658
39799
  const raw = await readFile3(configPath, "utf8");
39659
39800
  const parsed = JSON.parse(raw);
39801
+ migrateProviders(parsed);
39660
39802
  validateConfig(parsed);
39661
39803
  return normalizeConfig(parsed);
39662
39804
  }
39663
39805
 
39664
39806
  // src/orchestrator.ts
39665
- 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";
39666
39808
  import { randomBytes } from "crypto";
39667
39809
  import path9 from "path";
39668
39810
 
@@ -39687,6 +39829,72 @@ var DEFAULT_REMINDERS = [
39687
39829
  "Self-memory check: did you receive a correction this session? Write it to your agent file now.",
39688
39830
  "Global awareness: if this work affects other projects, write a one-liner to projects.yml history."
39689
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
+ }
39690
39898
 
39691
39899
  class Orchestrator {
39692
39900
  agents;
@@ -39702,7 +39910,16 @@ class Orchestrator {
39702
39910
  contextWindowSize;
39703
39911
  reminderInterval;
39704
39912
  reminderCursors = new Map;
39913
+ maxAutoHops;
39914
+ queueTtlMs;
39915
+ maxQueueSize;
39705
39916
  messageId = 0;
39917
+ stickyTargets;
39918
+ queueManager = new AgentQueueManager;
39919
+ manifestSavePromise = Promise.resolve();
39920
+ onMessage = () => {};
39921
+ onActivity = () => {};
39922
+ onSystem = () => {};
39706
39923
  constructor(agents, humanName = "USER", agentTags, humanTag, defaultTimeout = 600000, agentTimeouts, options) {
39707
39924
  this.agents = new Map(agents.map((agent) => [agent.name, agent]));
39708
39925
  this.agentTags = new Map(agents.map((agent) => [agent.name, agentTags?.[agent.name] ?? toTag(agent.name)]));
@@ -39712,12 +39929,21 @@ class Orchestrator {
39712
39929
  this.agentTimeouts = new Map(Object.entries(agentTimeouts ?? {}));
39713
39930
  this.contextWindowSize = options?.contextWindowSize ?? 16;
39714
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;
39715
39935
  this.sessionId = createSessionId();
39716
39936
  this.transcriptPath = path9.resolve(".llm-party", "sessions", `transcript-${this.sessionId}.jsonl`);
39717
39937
  for (const agent of agents) {
39718
39938
  this.lastSeenByAgent.set(agent.name, 0);
39939
+ this.queueManager.register(agent.name);
39719
39940
  }
39720
39941
  }
39942
+ setCallbacks(onMessage, onActivity, onSystem) {
39943
+ this.onMessage = onMessage;
39944
+ this.onActivity = onActivity;
39945
+ this.onSystem = onSystem;
39946
+ }
39721
39947
  getSessionId() {
39722
39948
  return this.sessionId;
39723
39949
  }
@@ -39730,12 +39956,29 @@ class Orchestrator {
39730
39956
  getHumanTag() {
39731
39957
  return this.humanTag;
39732
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
+ }
39733
39975
  clearConversation() {
39734
39976
  this.conversation.length = 0;
39735
39977
  this.messageId = 0;
39736
39978
  for (const agent of this.agents.keys()) {
39737
39979
  this.lastSeenByAgent.set(agent, 0);
39738
39980
  }
39981
+ this.queueManager.clearAll();
39739
39982
  this.sessionId = createSessionId();
39740
39983
  this.transcriptPath = path9.resolve(".llm-party", "sessions", `transcript-${this.sessionId}.jsonl`);
39741
39984
  }
@@ -39772,62 +40015,158 @@ class Orchestrator {
39772
40015
  const tag = this.agentTags.get(agent.name) ?? toTag(agent.name);
39773
40016
  return agent.name.toLowerCase() === normalized || tag.toLowerCase() === normalized;
39774
40017
  }).map((agent) => agent.name);
39775
- if (byName.length > 0) {
40018
+ if (byName.length > 0)
39776
40019
  return byName;
39777
- }
39778
40020
  return Array.from(this.agents.values()).filter((agent) => agent.provider.toLowerCase() === normalized).map((agent) => agent.name);
39779
40021
  }
39780
- async fanOut(targetAgentNames) {
39781
- return this.fanOutWithProgress(targetAgentNames, () => {});
39782
- }
39783
- async fanOutWithProgress(targetAgentNames, onMessage) {
39784
- const requestedTargets = targetAgentNames && targetAgentNames.length > 0 ? targetAgentNames : Array.from(this.agents.keys());
39785
- const targets = requestedTargets.map((name) => this.agents.get(name)).filter((agent) => Boolean(agent));
39786
- const historyMaxId = this.messageId;
39787
- const settled = await Promise.allSettled(targets.map(async (agent) => {
39788
- const lastSeen = this.lastSeenByAgent.get(agent.name) ?? 0;
39789
- const unseen = this.conversation.filter((msg) => msg.id > lastSeen && msg.from.toUpperCase() !== agent.name.toUpperCase());
39790
- if (unseen.length === 0) {
39791
- this.lastSeenByAgent.set(agent.name, historyMaxId);
39792
- return null;
39793
- }
39794
- const inputMessages = this.buildInputForAgent(agent.name, unseen);
39795
- const responseText = await this.sendWithTimeout(agent, inputMessages, this.timeoutFor(agent.name));
39796
- const response = {
39797
- id: ++this.messageId,
39798
- from: agent.name.toUpperCase(),
39799
- text: responseText,
39800
- createdAt: new Date().toISOString()
39801
- };
39802
- this.lastSeenByAgent.set(agent.name, historyMaxId);
39803
- this.conversation.push(response);
39804
- await this.appendTranscript(response);
39805
- onMessage(response);
39806
- return response;
39807
- }));
39808
- const results = [];
39809
- for (let idx = 0;idx < settled.length; idx++) {
39810
- const item = settled[idx];
39811
- if (item.status === "fulfilled") {
39812
- if (item.value) {
39813
- results.push(item.value);
39814
- }
40022
+ dispatchToTargets(targetAgentNames, chainHops = 0) {
40023
+ for (const name of targetAgentNames) {
40024
+ const agent = this.agents.get(name);
40025
+ if (!agent)
39815
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);
39816
40037
  }
39817
- const agent = targets[idx];
39818
- const response = {
39819
- id: ++this.messageId,
39820
- from: agent.name.toUpperCase(),
39821
- text: `[Adapter Error] ${item.reason instanceof Error ? item.reason.message : String(item.reason)}`,
39822
- createdAt: new Date().toISOString()
39823
- };
39824
- this.lastSeenByAgent.set(agent.name, historyMaxId);
39825
- this.conversation.push(response);
39826
- await this.appendTranscript(response);
39827
- onMessage(response);
39828
- results.push(response);
39829
40038
  }
39830
- 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
+ });
39831
40170
  }
39832
40171
  async appendTranscript(message) {
39833
40172
  try {
@@ -39840,23 +40179,85 @@ class Orchestrator {
39840
40179
  async saveHistory(targetPath) {
39841
40180
  await writeFile5(targetPath, JSON.stringify(this.conversation, null, 2), "utf8");
39842
40181
  }
39843
- async sendWithTimeout(agent, messages, timeoutMs) {
39844
- const controller = new AbortController;
39845
- let timer;
39846
- const timeoutPromise = new Promise((resolve4) => {
39847
- timer = setTimeout(() => {
39848
- controller.abort();
39849
- resolve4(`[Timeout] ${agent.name} exceeded ${Math.floor(timeoutMs / 1000)}s`);
39850
- }, 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
+ }
39851
40230
  });
40231
+ return this.manifestSavePromise;
40232
+ }
40233
+ async loadManifest() {
39852
40234
  try {
39853
- return await Promise.race([agent.send(messages, controller.signal), timeoutPromise]);
39854
- } finally {
39855
- if (timer) {
39856
- 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
+ }
39857
40252
  }
40253
+ } catch (err) {
40254
+ console.error("[manifest] load failed (agents start fresh):", err);
39858
40255
  }
39859
40256
  }
40257
+ async abortAll() {
40258
+ this.queueManager.abortAll();
40259
+ await this.saveManifest();
40260
+ }
39860
40261
  timeoutFor(agentName) {
39861
40262
  return this.agentTimeouts.get(agentName) ?? this.defaultTimeout;
39862
40263
  }
@@ -39890,9 +40291,24 @@ function createSessionId() {
39890
40291
  const rand = randomBytes(4).toString("hex");
39891
40292
  return `${timestamp}-${process.pid}-${rand}`;
39892
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
+ }
39893
40309
 
39894
40310
  // src/ui/App.tsx
39895
- import { spawn as spawn5 } from "child_process";
40311
+ import { spawn as spawn4 } from "child_process";
39896
40312
 
39897
40313
  // src/ui/useOrchestrator.ts
39898
40314
  import { execFile } from "child_process";
@@ -39901,14 +40317,56 @@ function nextSystemId() {
39901
40317
  systemIdCounter -= 1;
39902
40318
  return systemIdCounter;
39903
40319
  }
39904
- function useOrchestrator(orchestrator, maxAutoHops) {
40320
+ function useOrchestrator(orchestrator) {
39905
40321
  const [messages, setMessages] = createSignal([]);
39906
40322
  const [agentStates, setAgentStates] = createSignal(new Map(orchestrator.listAgents().map((a) => [a.name, "idle"])));
39907
- const [stickyTarget, setStickyTarget] = createSignal(undefined);
40323
+ const [stickyTarget, setStickyTargetSignal] = createSignal(orchestrator.getStickyTarget());
40324
+ const setStickyTarget = (targets) => {
40325
+ setStickyTargetSignal(targets);
40326
+ orchestrator.setStickyTarget(targets);
40327
+ };
39908
40328
  const [dispatching, setDispatching] = createSignal(false);
40329
+ const [queueCounts, setQueueCounts] = createSignal(new Map(orchestrator.listAgents().map((a) => [a.name, 0])));
39909
40330
  let projectFolderReady = false;
39910
- let agentProviders = new Map(orchestrator.listAgents().map((a) => [a.name.toUpperCase(), a.provider]));
39911
- 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
+ });
39912
40370
  const dispatch = async (line) => {
39913
40371
  if (!line.trim())
39914
40372
  return;
@@ -39922,7 +40380,7 @@ function useOrchestrator(orchestrator, maxAutoHops) {
39922
40380
  if (explicitTargets && explicitTargets.length > 0) {
39923
40381
  setStickyTarget(explicitTargets);
39924
40382
  }
39925
- const targets = explicitTargets ?? stickyTarget();
40383
+ const targets = explicitTargets ?? stickyTarget() ?? Array.from(orchestrator.listAgents().map((a) => a.name));
39926
40384
  if (!projectFolderReady) {
39927
40385
  await initProjectFolder(process.cwd());
39928
40386
  projectFolderReady = true;
@@ -39935,11 +40393,7 @@ function useOrchestrator(orchestrator, maxAutoHops) {
39935
40393
  };
39936
40394
  setMessages((prev) => [...prev, userDisplay]);
39937
40395
  setDispatching(true);
39938
- try {
39939
- await dispatchWithHandoffs(orchestrator, targets, maxAutoHops, agentProviders, agentTags, setMessages, setAgentStates);
39940
- } finally {
39941
- setDispatching(false);
39942
- }
40396
+ orchestrator.dispatchToTargets(targets);
39943
40397
  };
39944
40398
  const addSystemMessage = (text) => {
39945
40399
  const msg = {
@@ -39951,89 +40405,17 @@ function useOrchestrator(orchestrator, maxAutoHops) {
39951
40405
  };
39952
40406
  setMessages((prev) => [...prev, msg]);
39953
40407
  };
40408
+ const addDisplayMessage = (msg) => {
40409
+ setMessages((prev) => [...prev, msg]);
40410
+ };
39954
40411
  const clearMessages = () => {
39955
40412
  orchestrator.clearConversation();
39956
40413
  setMessages([]);
39957
40414
  };
39958
- return { messages, agentStates, stickyTarget, dispatching, dispatch, addSystemMessage, clearMessages };
39959
- }
39960
- async function dispatchWithHandoffs(orchestrator, initialTargets, maxHops, agentProviders, agentTags, setMessages, setAgentStates) {
39961
- let targets = initialTargets;
39962
- let hops = 0;
39963
- while (true) {
39964
- const targetNames = targets ?? Array.from(orchestrator.listAgents().map((a) => a.name));
39965
- setAgentStates((prev) => {
39966
- const next = new Map(prev);
39967
- for (const name of targetNames) {
39968
- next.set(name, "thinking");
39969
- }
39970
- return next;
39971
- });
39972
- const nameMap = new Map(orchestrator.listAgents().map((a) => [a.name.toUpperCase(), a.name]));
39973
- const batch = [];
39974
- await orchestrator.fanOutWithProgress(targets, (msg) => {
39975
- batch.push(msg);
39976
- const provider = agentProviders.get(msg.from) ?? "";
39977
- const tag = agentTags.get(msg.from) ?? "";
39978
- const display = {
39979
- ...msg,
39980
- type: "agent",
39981
- provider,
39982
- tag
39983
- };
39984
- setMessages((prev) => [...prev, display]);
39985
- const originalName = nameMap.get(msg.from) ?? msg.from;
39986
- setAgentStates((prev) => {
39987
- const next = new Map(prev);
39988
- next.set(originalName, msg.text.startsWith("[Adapter Error]") ? "error" : "idle");
39989
- return next;
39990
- });
39991
- });
39992
- setAgentStates((prev) => {
39993
- const next = new Map(prev);
39994
- for (const name of targetNames) {
39995
- if (next.get(name) === "thinking") {
39996
- next.set(name, "idle");
39997
- }
39998
- }
39999
- return next;
40000
- });
40001
- const nextSelectors = extractNextSelectors(batch);
40002
- if (nextSelectors.length === 0)
40003
- return;
40004
- const humanTag = orchestrator.getHumanTag().toLowerCase();
40005
- const humanName = orchestrator.getHumanName().toLowerCase();
40006
- const agentSelectors = nextSelectors.filter((s) => {
40007
- const n = s.toLowerCase();
40008
- return n !== humanTag && n !== humanName;
40009
- });
40010
- if (agentSelectors.length === 0)
40011
- return;
40012
- const resolvedTargets = Array.from(new Set(agentSelectors.flatMap((s) => orchestrator.resolveTargets(s))));
40013
- if (resolvedTargets.length === 0)
40014
- return;
40015
- hops += 1;
40016
- if (Number.isFinite(maxHops) && hops >= maxHops) {
40017
- const systemMsg = {
40018
- id: nextSystemId(),
40019
- from: "SYSTEM",
40020
- text: `Stopped auto-handoff after ${maxHops} hops.`,
40021
- createdAt: new Date().toISOString(),
40022
- type: "system"
40023
- };
40024
- setMessages((prev) => [...prev, systemMsg]);
40025
- return;
40026
- }
40027
- const handoffMsg = {
40028
- id: nextSystemId(),
40029
- from: "SYSTEM",
40030
- text: `Auto handoff via @next to ${resolvedTargets.join(", ")}`,
40031
- createdAt: new Date().toISOString(),
40032
- type: "system"
40033
- };
40034
- setMessages((prev) => [...prev, handoffMsg]);
40035
- targets = resolvedTargets;
40036
- }
40415
+ const refreshStickyTarget = () => {
40416
+ setStickyTargetSignal(orchestrator.getStickyTarget());
40417
+ };
40418
+ return { messages, agentStates, queueCounts, stickyTarget, dispatching, dispatch, addSystemMessage, addDisplayMessage, clearMessages, refreshStickyTarget };
40037
40419
  }
40038
40420
  function getChangedFiles() {
40039
40421
  return new Promise((resolve4) => {
@@ -40048,21 +40430,6 @@ function getChangedFiles() {
40048
40430
  });
40049
40431
  });
40050
40432
  }
40051
- function extractNextSelectors(messages) {
40052
- const selectors = [];
40053
- for (const msg of messages) {
40054
- const regex = /@next\s*:\s*([A-Za-z0-9_-]+)/gi;
40055
- let match = null;
40056
- while ((match = regex.exec(msg.text)) !== null) {
40057
- selectors.push(match[1]);
40058
- }
40059
- const controlMatch = msg.text.match(/@control[\s\S]*?next\s*:\s*([A-Za-z0-9_-]+)[\s\S]*?@end/i);
40060
- if (controlMatch?.[1]) {
40061
- selectors.push(controlMatch[1]);
40062
- }
40063
- }
40064
- return selectors;
40065
- }
40066
40433
  function parseRouting(line) {
40067
40434
  const mentionRegex = /(^|[^A-Za-z0-9_-])@([A-Za-z0-9_-]+)\b/g;
40068
40435
  const mentions = [];
@@ -40151,27 +40518,21 @@ function MessageBubble(props) {
40151
40518
  }
40152
40519
 
40153
40520
  // src/ui/constants.ts
40154
- 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"];
40155
40531
 
40156
40532
  // src/ui/StatusBar.tsx
40157
40533
  var PULSE_COLORS = ["#005F87", "#0087AF", "#00AFD7", "#00D7FF", "#5FF", "#00D7FF", "#0087AF"];
40158
- function useThinkingAnimation(active) {
40159
- const [frame, setFrame] = createSignal(0);
40160
- createEffect(() => {
40161
- if (!active()) {
40162
- setFrame(0);
40163
- return;
40164
- }
40165
- const interval = setInterval(() => {
40166
- setFrame((f) => (f + 1) % (SPINNER_FRAMES.length * PULSE_COLORS.length));
40167
- }, 80);
40168
- onCleanup(() => clearInterval(interval));
40169
- });
40170
- return {
40171
- spinner: () => active() ? SPINNER_FRAMES[frame() % SPINNER_FRAMES.length] : "",
40172
- color: () => active() ? PULSE_COLORS[frame() % PULSE_COLORS.length] : COLORS.textMuted
40173
- };
40174
- }
40534
+ var [globalTick, setGlobalTick] = createSignal(0);
40535
+ setInterval(() => setGlobalTick((t2) => t2 + 1), 80);
40175
40536
  function StatusBar(props) {
40176
40537
  const targetNames = () => props.stickyTarget ?? props.agents.map((a) => a.name);
40177
40538
  const isTargeted = (name) => targetNames().includes(name);
@@ -40219,65 +40580,59 @@ function StatusBar(props) {
40219
40580
  }), _el$4);
40220
40581
  insertNode(_el$4, createTextNode(`| /info`));
40221
40582
  setProp(_el$6, "flexDirection", "row");
40222
- setProp(_el$6, "gap", 2);
40223
40583
  insert(_el$6, createComponent2(For, {
40224
40584
  get each() {
40225
40585
  return props.agents;
40226
40586
  },
40227
- children: (a) => createComponent2(AgentChip, {
40587
+ children: (a, i) => [createComponent2(AgentChip, {
40228
40588
  get name() {
40229
40589
  return a.name;
40230
40590
  },
40231
- get state() {
40232
- return props.agentStates.get(a.name) ?? "idle";
40233
- }
40234
- })
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)]
40235
40599
  }));
40236
40600
  effect((_$p) => setProp(_el$4, "fg", COLORS.textDim, _$p));
40237
40601
  return _el$;
40238
40602
  })();
40239
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
+ }
40240
40609
  function AgentChip(props) {
40241
- const {
40242
- spinner,
40243
- color
40244
- } = useThinkingAnimation(() => props.state === "thinking");
40245
- return createComponent2(Switch, {
40246
- get fallback() {
40247
- return (() => {
40248
- var _el$16 = createElement("text");
40249
- insert(_el$16, () => props.name);
40250
- effect((_$p) => setProp(_el$16, "fg", COLORS.textMuted, _$p));
40251
- return _el$16;
40252
- })();
40253
- },
40254
- get children() {
40255
- return [createComponent2(Match, {
40256
- get when() {
40257
- return props.state === "error";
40258
- },
40259
- get children() {
40260
- var _el$12 = createElement("text"), _el$13 = createTextNode(` ERR`);
40261
- insertNode(_el$12, _el$13);
40262
- insert(_el$12, () => props.name, _el$13);
40263
- effect((_$p) => setProp(_el$12, "fg", COLORS.error, _$p));
40264
- return _el$12;
40265
- }
40266
- }), createComponent2(Match, {
40267
- get when() {
40268
- return props.state === "thinking";
40269
- },
40270
- get children() {
40271
- var _el$14 = createElement("text"), _el$15 = createTextNode(` `);
40272
- insertNode(_el$14, _el$15);
40273
- insert(_el$14, () => props.name, _el$15);
40274
- insert(_el$14, spinner, null);
40275
- effect((_$p) => setProp(_el$14, "fg", color(), _$p));
40276
- return _el$14;
40277
- }
40278
- })];
40279
- }
40280
- });
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
+ })();
40281
40636
  }
40282
40637
 
40283
40638
  // src/ui/InputLine.tsx
@@ -40314,7 +40669,7 @@ function InputLine(props) {
40314
40669
  useKeyboard((key) => {
40315
40670
  if (props.disabled)
40316
40671
  return;
40317
- if (key.shift && (key.name === "enter" || key.name === "return")) {
40672
+ if ((key.shift || key.option || key.meta) && (key.name === "enter" || key.name === "return")) {
40318
40673
  update(value.slice(0, cursor) + `
40319
40674
  ` + value.slice(cursor), cursor + 1);
40320
40675
  return;
@@ -40529,15 +40884,15 @@ function InputLine(props) {
40529
40884
  }
40530
40885
 
40531
40886
  // src/ui/ConfigWizard.tsx
40532
- import { userInfo as userInfo3 } from "os";
40887
+ import { userInfo as userInfo2 } from "os";
40533
40888
 
40534
40889
  // src/config/detector.ts
40535
- import { spawn as spawn4 } from "child_process";
40536
- var DETECT_TIMEOUT = 5000;
40890
+ import { spawn as spawn3 } from "child_process";
40891
+ var DETECT_TIMEOUT = 1e4;
40537
40892
  function detectBinary(command) {
40538
40893
  return new Promise((resolve4) => {
40539
40894
  const timer = setTimeout(() => resolve4({ available: false }), DETECT_TIMEOUT);
40540
- const proc = spawn4(command, ["--version"], {
40895
+ const proc = spawn3(command, ["--version"], {
40541
40896
  stdio: ["ignore", "pipe", "ignore"],
40542
40897
  shell: true,
40543
40898
  timeout: DETECT_TIMEOUT,
@@ -40562,33 +40917,13 @@ function detectBinary(command) {
40562
40917
  });
40563
40918
  });
40564
40919
  }
40565
- function detectAlias(command) {
40566
- return new Promise((resolve4) => {
40567
- const shell = process.env.SHELL || "/bin/bash";
40568
- const timer = setTimeout(() => resolve4({ available: false }), DETECT_TIMEOUT);
40569
- const proc = spawn4(shell, ["-ic", `type ${command}`], {
40570
- stdio: ["ignore", "pipe", "ignore"],
40571
- timeout: DETECT_TIMEOUT,
40572
- detached: true
40573
- });
40574
- proc.unref();
40575
- proc.on("close", (code) => {
40576
- clearTimeout(timer);
40577
- resolve4({ available: code === 0 });
40578
- });
40579
- proc.on("error", () => {
40580
- clearTimeout(timer);
40581
- resolve4({ available: false });
40582
- });
40583
- });
40584
- }
40585
40920
  async function detectProviders() {
40586
40921
  const results = await Promise.allSettled(PROVIDERS.map(async (provider) => {
40587
- const result = provider.detectType === "alias" ? await detectAlias(provider.detectCommand) : await detectBinary(provider.detectCommand);
40922
+ const result = await detectBinary(provider.detectCommand);
40588
40923
  return {
40589
40924
  id: provider.id,
40590
40925
  available: result.available,
40591
- version: "version" in result ? result.version : undefined
40926
+ version: result.version
40592
40927
  };
40593
40928
  }));
40594
40929
  return results.map((result, i) => {
@@ -40600,57 +40935,8 @@ async function detectProviders() {
40600
40935
 
40601
40936
  // src/config/writer.ts
40602
40937
  import { writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
40603
- import { userInfo as userInfo2 } from "os";
40604
40938
  import path10 from "path";
40605
- async function writeWizardConfig(selectedIds, overrides, existingConfig) {
40606
- const overrideMap = new Map((overrides || []).map((o) => [o.id, o]));
40607
- const existingByProvider = new Map;
40608
- if (existingConfig?.agents) {
40609
- for (const agent of existingConfig.agents) {
40610
- existingByProvider.set(agent.provider, agent);
40611
- }
40612
- }
40613
- const agents = selectedIds.map((id) => {
40614
- const def = PROVIDERS.find((p) => p.id === id);
40615
- if (!def)
40616
- throw new Error(`Unknown provider: ${id}`);
40617
- const override = overrideMap.get(id);
40618
- const existing = existingByProvider.get(id);
40619
- const agent = {
40620
- name: override?.name || def.displayName,
40621
- tag: override?.tag || def.defaultTag,
40622
- provider: def.id,
40623
- model: override?.model || def.defaultModel
40624
- };
40625
- if (existing?.env) {
40626
- agent.env = { ...existing.env };
40627
- } else if (def.env) {
40628
- agent.env = { ...def.env };
40629
- }
40630
- if (existing?.prompts)
40631
- agent.prompts = existing.prompts;
40632
- if (existing?.preloadSkills)
40633
- agent.preloadSkills = existing.preloadSkills;
40634
- if (existing?.executablePath)
40635
- agent.executablePath = existing.executablePath;
40636
- if (existing?.timeout)
40637
- agent.timeout = existing.timeout;
40638
- return agent;
40639
- });
40640
- const config = {
40641
- humanName: existingConfig?.humanName || userInfo2().username || "USER",
40642
- humanTag: existingConfig?.humanTag,
40643
- maxAutoHops: existingConfig?.maxAutoHops ?? 15,
40644
- timeout: existingConfig?.timeout,
40645
- reminderInterval: existingConfig?.reminderInterval,
40646
- agents
40647
- };
40648
- if (!config.humanTag)
40649
- delete config.humanTag;
40650
- if (config.timeout === undefined)
40651
- delete config.timeout;
40652
- if (config.reminderInterval === undefined)
40653
- delete config.reminderInterval;
40939
+ async function writeConfig(config) {
40654
40940
  await mkdir6(LLM_PARTY_HOME, { recursive: true });
40655
40941
  const configPath = path10.join(LLM_PARTY_HOME, "config.json");
40656
40942
  await writeFile6(configPath, JSON.stringify(config, null, 2) + `
@@ -40663,16 +40949,114 @@ function MultiSelect(props) {
40663
40949
  const [focused, setFocused] = createSignal(props.items.findIndex((item) => !item.disabled));
40664
40950
  const [selected, setSelected] = createSignal(new Set(props.initialSelected || []));
40665
40951
  const [error, setError] = createSignal("");
40952
+ const [addingCustom, setAddingCustom] = createSignal(false);
40953
+ const [customName, setCustomName] = createSignal("");
40954
+ const [customCursor, setCustomCursor] = createSignal(0);
40955
+ const tuiRenderer = useRenderer();
40956
+ createEffect(() => {
40957
+ const handlePaste = (event) => {
40958
+ if (!addingCustom())
40959
+ return;
40960
+ const text = new TextDecoder().decode(event.bytes);
40961
+ if (!text)
40962
+ return;
40963
+ const cleaned = text.replace(/\n/g, "");
40964
+ if (!cleaned)
40965
+ return;
40966
+ const c = customCursor();
40967
+ const val = customName();
40968
+ setCustomName(val.slice(0, c) + cleaned + val.slice(c));
40969
+ setCustomCursor(c + cleaned.length);
40970
+ };
40971
+ tuiRenderer.keyInput.on("paste", handlePaste);
40972
+ onCleanup(() => {
40973
+ tuiRenderer.keyInput.off("paste", handlePaste);
40974
+ });
40975
+ });
40976
+ const totalRows = () => props.items.length + (props.addCustom ? 1 : 0);
40977
+ const isAddCustomRow = (idx) => props.addCustom && idx === props.items.length;
40666
40978
  const findNextEnabled = (from, direction) => {
40979
+ const total = totalRows();
40667
40980
  let idx = from;
40668
- for (let i = 0;i < props.items.length; i++) {
40669
- idx = (idx + direction + props.items.length) % props.items.length;
40981
+ for (let i = 0;i < total; i++) {
40982
+ idx = (idx + direction + total) % total;
40983
+ if (isAddCustomRow(idx))
40984
+ return idx;
40670
40985
  if (!props.items[idx].disabled)
40671
40986
  return idx;
40672
40987
  }
40673
40988
  return from;
40674
40989
  };
40675
40990
  useKeyboard((key) => {
40991
+ if (addingCustom()) {
40992
+ if (key.name === "escape") {
40993
+ setAddingCustom(false);
40994
+ setCustomName("");
40995
+ setCustomCursor(0);
40996
+ return;
40997
+ }
40998
+ if (key.name === "enter" || key.name === "return") {
40999
+ const name = customName().trim();
41000
+ if (name.length > 0) {
41001
+ props.addCustom.onAdd(name);
41002
+ setAddingCustom(false);
41003
+ setCustomName("");
41004
+ setCustomCursor(0);
41005
+ }
41006
+ return;
41007
+ }
41008
+ if (key.name === "backspace") {
41009
+ const c = customCursor();
41010
+ if (c > 0) {
41011
+ const val = customName();
41012
+ setCustomName(val.slice(0, c - 1) + val.slice(c));
41013
+ setCustomCursor(c - 1);
41014
+ }
41015
+ return;
41016
+ }
41017
+ if (key.name === "delete") {
41018
+ const c = customCursor();
41019
+ const val = customName();
41020
+ if (c < val.length) {
41021
+ setCustomName(val.slice(0, c) + val.slice(c + 1));
41022
+ }
41023
+ return;
41024
+ }
41025
+ if (key.name === "left") {
41026
+ setCustomCursor((c) => Math.max(0, c - 1));
41027
+ return;
41028
+ }
41029
+ if (key.name === "right") {
41030
+ setCustomCursor((c) => Math.min(customName().length, c + 1));
41031
+ return;
41032
+ }
41033
+ if (key.name === "home" || key.ctrl && key.name === "a") {
41034
+ setCustomCursor(0);
41035
+ return;
41036
+ }
41037
+ if (key.name === "end" || key.ctrl && key.name === "e") {
41038
+ setCustomCursor(customName().length);
41039
+ return;
41040
+ }
41041
+ if (key.ctrl && key.name === "u") {
41042
+ setCustomName("");
41043
+ setCustomCursor(0);
41044
+ return;
41045
+ }
41046
+ if (key.ctrl || key.name === "up" || key.name === "down" || key.name === "pageup" || key.name === "pagedown" || key.name === "tab") {
41047
+ return;
41048
+ }
41049
+ const ch = key.sequence;
41050
+ if (ch && ch.length > 0 && !ch.startsWith("\x1B")) {
41051
+ if (ch === "'" || ch === '"' || ch === "`")
41052
+ return;
41053
+ const c = customCursor();
41054
+ const val = customName();
41055
+ setCustomName(val.slice(0, c) + ch + val.slice(c));
41056
+ setCustomCursor(c + ch.length);
41057
+ }
41058
+ return;
41059
+ }
40676
41060
  if (key.name === "up" || key.name === "k") {
40677
41061
  setFocused((f) => findNextEnabled(f, -1));
40678
41062
  setError("");
@@ -40684,13 +41068,20 @@ function MultiSelect(props) {
40684
41068
  return;
40685
41069
  }
40686
41070
  if (key.name === "space" || key.sequence === " ") {
40687
- if (focused() >= 0 && !props.items[focused()].disabled) {
41071
+ const f = focused();
41072
+ if (isAddCustomRow(f)) {
41073
+ setAddingCustom(true);
41074
+ setCustomName("");
41075
+ setCustomCursor(0);
41076
+ return;
41077
+ }
41078
+ if (f >= 0 && !props.items[f].disabled) {
40688
41079
  setSelected((prev) => {
40689
41080
  const next = new Set(prev);
40690
- if (next.has(focused())) {
40691
- next.delete(focused());
41081
+ if (next.has(f)) {
41082
+ next.delete(f);
40692
41083
  } else {
40693
- next.add(focused());
41084
+ next.add(f);
40694
41085
  }
40695
41086
  return next;
40696
41087
  });
@@ -40699,6 +41090,13 @@ function MultiSelect(props) {
40699
41090
  return;
40700
41091
  }
40701
41092
  if (key.name === "enter" || key.name === "return") {
41093
+ const f = focused();
41094
+ if (isAddCustomRow(f)) {
41095
+ setAddingCustom(true);
41096
+ setCustomName("");
41097
+ setCustomCursor(0);
41098
+ return;
41099
+ }
40702
41100
  if (selected().size === 0) {
40703
41101
  setError("Select at least one agent");
40704
41102
  return;
@@ -40720,7 +41118,7 @@ function MultiSelect(props) {
40720
41118
  },
40721
41119
  children: (item, i) => {
40722
41120
  const isSelected = () => selected().has(i());
40723
- const isFocused = () => i() === focused();
41121
+ const isFocused = () => i() === focused() && !addingCustom();
40724
41122
  const isDisabled = () => !!item.disabled;
40725
41123
  const bullet = () => isSelected() ? "\u25CF" : "\u25CB";
40726
41124
  const bulletColor = () => isDisabled() ? COLORS.textFaint : isSelected() ? COLORS.success : COLORS.textSecondary;
@@ -40728,29 +41126,29 @@ function MultiSelect(props) {
40728
41126
  const descColor = () => isDisabled() ? COLORS.textFaint : COLORS.textMuted;
40729
41127
  const bgColor = () => isFocused() && !isDisabled() ? COLORS.bgFocus : undefined;
40730
41128
  return (() => {
40731
- var _el$4 = createElement("text"), _el$5 = createElement("span"), _el$6 = createTextNode(` `), _el$7 = createTextNode(` `), _el$8 = createElement("span"), _el$9 = createElement("span"), _el$0 = createTextNode(` `);
40732
- insertNode(_el$4, _el$5);
40733
- insertNode(_el$4, _el$8);
40734
- insertNode(_el$4, _el$9);
40735
- setProp(_el$4, "selectable", false);
40736
- insertNode(_el$5, _el$6);
40737
- insertNode(_el$5, _el$7);
40738
- insert(_el$5, bullet, _el$7);
40739
- insert(_el$8, () => item.label);
40740
- insertNode(_el$9, _el$0);
40741
- insert(_el$9, () => item.description, null);
41129
+ var _el$0 = createElement("text"), _el$1 = createElement("span"), _el$10 = createTextNode(` `), _el$11 = createTextNode(` `), _el$12 = createElement("span"), _el$13 = createElement("span"), _el$14 = createTextNode(` `);
41130
+ insertNode(_el$0, _el$1);
41131
+ insertNode(_el$0, _el$12);
41132
+ insertNode(_el$0, _el$13);
41133
+ setProp(_el$0, "selectable", false);
41134
+ insertNode(_el$1, _el$10);
41135
+ insertNode(_el$1, _el$11);
41136
+ insert(_el$1, bullet, _el$11);
41137
+ insert(_el$12, () => item.label);
41138
+ insertNode(_el$13, _el$14);
41139
+ insert(_el$13, () => item.description, null);
40742
41140
  effect((_p$) => {
40743
- var _v$ = bgColor(), _v$2 = {
41141
+ var _v$4 = bgColor(), _v$5 = {
40744
41142
  fg: bulletColor()
40745
- }, _v$3 = {
41143
+ }, _v$6 = {
40746
41144
  fg: labelColor()
40747
- }, _v$4 = {
41145
+ }, _v$7 = {
40748
41146
  fg: descColor()
40749
41147
  };
40750
- _v$ !== _p$.e && (_p$.e = setProp(_el$4, "bg", _v$, _p$.e));
40751
- _v$2 !== _p$.t && (_p$.t = setProp(_el$5, "style", _v$2, _p$.t));
40752
- _v$3 !== _p$.a && (_p$.a = setProp(_el$8, "style", _v$3, _p$.a));
40753
- _v$4 !== _p$.o && (_p$.o = setProp(_el$9, "style", _v$4, _p$.o));
41148
+ _v$4 !== _p$.e && (_p$.e = setProp(_el$0, "bg", _v$4, _p$.e));
41149
+ _v$5 !== _p$.t && (_p$.t = setProp(_el$1, "style", _v$5, _p$.t));
41150
+ _v$6 !== _p$.a && (_p$.a = setProp(_el$12, "style", _v$6, _p$.a));
41151
+ _v$7 !== _p$.o && (_p$.o = setProp(_el$13, "style", _v$7, _p$.o));
40754
41152
  return _p$;
40755
41153
  }, {
40756
41154
  e: undefined,
@@ -40758,21 +41156,96 @@ function MultiSelect(props) {
40758
41156
  a: undefined,
40759
41157
  o: undefined
40760
41158
  });
40761
- return _el$4;
41159
+ return _el$0;
40762
41160
  })();
40763
41161
  }
40764
41162
  }), null);
41163
+ insert(_el$, createComponent2(Show, {
41164
+ get when() {
41165
+ return props.addCustom;
41166
+ },
41167
+ get children() {
41168
+ return createComponent2(Show, {
41169
+ get when() {
41170
+ return addingCustom();
41171
+ },
41172
+ get fallback() {
41173
+ return (() => {
41174
+ var _el$15 = createElement("text"), _el$16 = createElement("span"), _el$18 = createElement("span");
41175
+ insertNode(_el$15, _el$16);
41176
+ insertNode(_el$15, _el$18);
41177
+ setProp(_el$15, "selectable", false);
41178
+ insertNode(_el$16, createTextNode(` + `));
41179
+ insertNode(_el$18, createTextNode(`Add Custom...`));
41180
+ effect((_p$) => {
41181
+ var _v$8 = focused() === props.items.length && !addingCustom() ? COLORS.bgFocus : undefined, _v$9 = {
41182
+ fg: COLORS.primary
41183
+ }, _v$0 = {
41184
+ fg: COLORS.textSecondary
41185
+ };
41186
+ _v$8 !== _p$.e && (_p$.e = setProp(_el$15, "bg", _v$8, _p$.e));
41187
+ _v$9 !== _p$.t && (_p$.t = setProp(_el$16, "style", _v$9, _p$.t));
41188
+ _v$0 !== _p$.a && (_p$.a = setProp(_el$18, "style", _v$0, _p$.a));
41189
+ return _p$;
41190
+ }, {
41191
+ e: undefined,
41192
+ t: undefined,
41193
+ a: undefined
41194
+ });
41195
+ return _el$15;
41196
+ })();
41197
+ },
41198
+ get children() {
41199
+ var _el$2 = createElement("text"), _el$3 = createElement("span"), _el$5 = createElement("span"), _el$7 = createElement("span");
41200
+ insertNode(_el$2, _el$3);
41201
+ insertNode(_el$2, _el$5);
41202
+ insertNode(_el$2, _el$7);
41203
+ setProp(_el$2, "selectable", false);
41204
+ insertNode(_el$3, createTextNode(` + `));
41205
+ insertNode(_el$5, createTextNode(`Name: `));
41206
+ insert(_el$2, () => customName().slice(0, customCursor()), _el$7);
41207
+ insert(_el$7, (() => {
41208
+ var _c$ = memo2(() => customCursor() < customName().length);
41209
+ return () => _c$() ? customName()[customCursor()] : " ";
41210
+ })());
41211
+ insert(_el$2, (() => {
41212
+ var _c$2 = memo2(() => customCursor() < customName().length);
41213
+ return () => _c$2() ? customName().slice(customCursor() + 1) : "";
41214
+ })(), null);
41215
+ effect((_p$) => {
41216
+ var _v$ = {
41217
+ fg: COLORS.primary
41218
+ }, _v$2 = {
41219
+ fg: COLORS.textSecondary
41220
+ }, _v$3 = {
41221
+ bg: COLORS.textPrimary,
41222
+ fg: "#000000"
41223
+ };
41224
+ _v$ !== _p$.e && (_p$.e = setProp(_el$3, "style", _v$, _p$.e));
41225
+ _v$2 !== _p$.t && (_p$.t = setProp(_el$5, "style", _v$2, _p$.t));
41226
+ _v$3 !== _p$.a && (_p$.a = setProp(_el$7, "style", _v$3, _p$.a));
41227
+ return _p$;
41228
+ }, {
41229
+ e: undefined,
41230
+ t: undefined,
41231
+ a: undefined
41232
+ });
41233
+ return _el$2;
41234
+ }
41235
+ });
41236
+ }
41237
+ }), null);
40765
41238
  insert(_el$, createComponent2(Show, {
40766
41239
  get when() {
40767
41240
  return error();
40768
41241
  },
40769
41242
  get children() {
40770
- var _el$2 = createElement("text"), _el$3 = createTextNode(` `);
40771
- insertNode(_el$2, _el$3);
40772
- setProp(_el$2, "marginTop", 1);
40773
- insert(_el$2, error, null);
40774
- effect((_$p) => setProp(_el$2, "fg", COLORS.error, _$p));
40775
- return _el$2;
41243
+ var _el$8 = createElement("text"), _el$9 = createTextNode(` `);
41244
+ insertNode(_el$8, _el$9);
41245
+ setProp(_el$8, "marginTop", 1);
41246
+ insert(_el$8, error, null);
41247
+ effect((_$p) => setProp(_el$8, "fg", COLORS.error, _$p));
41248
+ return _el$8;
40776
41249
  }
40777
41250
  }), null);
40778
41251
  return _el$;
@@ -40793,7 +41266,6 @@ var SWEEP_CHARS = ["\u2591", "\u2592", "\u2593", "\u2588", "\u2593", "\u2592", "
40793
41266
  var BAR_WIDTH = 6;
40794
41267
  function SweepBar(props) {
40795
41268
  const glow = SWEEP_CHARS.length;
40796
- const totalWidth = BAR_WIDTH * 2 + props.title.length + 2;
40797
41269
  const [pos, setPos] = createSignal(0);
40798
41270
  createEffect(() => {
40799
41271
  const interval = setInterval(() => setPos((p) => (p + 1) % (BAR_WIDTH + glow)), 50);
@@ -40869,22 +41341,105 @@ function useDiscoColor() {
40869
41341
  });
40870
41342
  return () => PARTY_COLORS[idx()];
40871
41343
  }
41344
+ function personaToEntry(agent) {
41345
+ const env2 = agent.env ?? {};
41346
+ return {
41347
+ name: agent.name,
41348
+ tag: agent.tag,
41349
+ model: agent.model,
41350
+ provider: agent.provider,
41351
+ active: agent.active !== false,
41352
+ authUrl: env2.AUTH_URL ?? "",
41353
+ authToken: env2.AUTH_TOKEN ?? "",
41354
+ cli: agent.cli ?? "claude",
41355
+ prompts: agent.prompts,
41356
+ preloadSkills: agent.preloadSkills,
41357
+ executablePath: agent.executablePath,
41358
+ timeout: agent.timeout,
41359
+ extraEnv: extractExtraEnv(env2)
41360
+ };
41361
+ }
41362
+ function extractExtraEnv(env2) {
41363
+ const extra = {};
41364
+ for (const [k2, v2] of Object.entries(env2)) {
41365
+ if (k2 !== "AUTH_URL" && k2 !== "AUTH_TOKEN") {
41366
+ extra[k2] = v2;
41367
+ }
41368
+ }
41369
+ return Object.keys(extra).length > 0 ? extra : undefined;
41370
+ }
41371
+ function entryToPersona(entry) {
41372
+ const config = {
41373
+ name: entry.name.trim(),
41374
+ tag: entry.tag.trim(),
41375
+ provider: entry.provider,
41376
+ model: entry.model.trim(),
41377
+ active: entry.active
41378
+ };
41379
+ if (entry.provider === "custom") {
41380
+ config.cli = entry.cli || "claude";
41381
+ const env2 = {};
41382
+ if (entry.authUrl)
41383
+ env2.AUTH_URL = entry.authUrl;
41384
+ if (entry.authToken)
41385
+ env2.AUTH_TOKEN = entry.authToken;
41386
+ if (entry.extraEnv)
41387
+ Object.assign(env2, entry.extraEnv);
41388
+ if (Object.keys(env2).length > 0)
41389
+ config.env = env2;
41390
+ }
41391
+ if (entry.prompts)
41392
+ config.prompts = entry.prompts;
41393
+ if (entry.preloadSkills)
41394
+ config.preloadSkills = entry.preloadSkills;
41395
+ if (entry.executablePath)
41396
+ config.executablePath = entry.executablePath;
41397
+ if (entry.timeout)
41398
+ config.timeout = entry.timeout;
41399
+ return config;
41400
+ }
40872
41401
  function ConfigWizard(props) {
40873
41402
  const [step, setStep] = createSignal("detect");
40874
41403
  const [detection, setDetection] = createSignal([]);
40875
- const [selectedIds, setSelectedIds] = createSignal([]);
40876
- const [, setAgentConfigs] = createSignal([]);
40877
41404
  const [activeTab, setActiveTab] = createSignal(0);
40878
41405
  const [focusedField, setFocusedField] = createSignal(0);
40879
41406
  const [error, setError] = createSignal("");
40880
41407
  const [tick, setTick] = createSignal(0);
40881
- let inputRefsData = [];
40882
- let humanData = {
40883
- name: props.existingConfig?.humanName || userInfo3().username || "USER",
40884
- tag: props.existingConfig?.humanTag || toTag(props.existingConfig?.humanName || userInfo3().username || "USER")
41408
+ const [customItems, setCustomItems] = createSignal([]);
41409
+ let agentEntries = [];
41410
+ let settingsData = {
41411
+ name: props.existingConfig?.humanName || userInfo2().username || "USER",
41412
+ tag: props.existingConfig?.humanTag || toTag(props.existingConfig?.humanName || userInfo2().username || "USER"),
41413
+ maxHops: String(props.existingConfig?.maxAutoHops ?? 15),
41414
+ timeout: String(props.existingConfig?.timeout ?? 600)
40885
41415
  };
40886
41416
  let cursorPos = 0;
40887
41417
  const spinner = useSpinner();
41418
+ const tuiRenderer = useRenderer();
41419
+ createEffect(() => {
41420
+ const handlePaste = (event) => {
41421
+ if (step() !== "configure")
41422
+ return;
41423
+ const text = new TextDecoder().decode(event.bytes);
41424
+ if (!text)
41425
+ return;
41426
+ const cleaned = text.replace(/\n/g, "");
41427
+ if (!cleaned)
41428
+ return;
41429
+ const fieldValue = getCurrentFieldValue();
41430
+ const cursor = cursorPos;
41431
+ updateField(fieldValue.slice(0, cursor) + cleaned + fieldValue.slice(cursor), cursor + cleaned.length);
41432
+ };
41433
+ tuiRenderer.keyInput.on("paste", handlePaste);
41434
+ onCleanup(() => {
41435
+ tuiRenderer.keyInput.off("paste", handlePaste);
41436
+ });
41437
+ });
41438
+ createEffect(() => {
41439
+ const existing = props.existingConfig?.agents ?? [];
41440
+ const customs = existing.filter((a) => a.provider === "custom").map(personaToEntry);
41441
+ setCustomItems(customs);
41442
+ });
40888
41443
  createEffect(() => {
40889
41444
  detectProviders().then((results) => {
40890
41445
  setDetection(results);
@@ -40894,103 +41449,255 @@ function ConfigWizard(props) {
40894
41449
  setStep("providers");
40895
41450
  });
40896
41451
  });
40897
- const existingByProvider = () => new Map((props.existingConfig?.agents || []).map((a) => [a.provider, a]));
40898
- const initialSelected = () => props.existingConfig ? PROVIDERS.map((p, i) => existingByProvider().has(p.id) ? i : -1).filter((i) => i >= 0) : undefined;
40899
- const multiSelectItems = () => PROVIDERS.map((provider) => {
40900
- const result = detection().find((d2) => d2.id === provider.id);
40901
- const available = result?.available ?? false;
40902
- return {
40903
- label: provider.displayName,
40904
- description: available ? provider.description : provider.unavailableHint,
40905
- disabled: !available
40906
- };
40907
- });
40908
- const handleProviderConfirm = (selectedIndices) => {
40909
- const ids = selectedIndices.map((i) => PROVIDERS[i].id);
40910
- setSelectedIds(ids);
40911
- const configs = ids.map((id) => {
40912
- const def = PROVIDERS.find((p) => p.id === id);
40913
- const existing = existingByProvider().get(id);
41452
+ const existingNative = () => {
41453
+ const map = new Map;
41454
+ for (const a of props.existingConfig?.agents ?? []) {
41455
+ if (a.provider !== "custom")
41456
+ map.set(a.provider, a);
41457
+ }
41458
+ return map;
41459
+ };
41460
+ const multiSelectItems = () => {
41461
+ const nativeItems = PROVIDERS.map((provider) => {
41462
+ const result = detection().find((d2) => d2.id === provider.id);
41463
+ const available = result?.available ?? false;
40914
41464
  return {
40915
- id: def.id,
40916
- name: existing?.name || def.displayName,
40917
- tag: existing?.tag || def.defaultTag,
40918
- model: existing?.model || def.defaultModel
41465
+ label: provider.displayName,
41466
+ description: available ? provider.description : provider.unavailableHint,
41467
+ disabled: !available
40919
41468
  };
40920
41469
  });
40921
- setAgentConfigs(configs);
40922
- inputRefsData = configs.map((c) => ({
40923
- ...c
41470
+ const customs = customItems();
41471
+ if (customs.length > 0) {
41472
+ nativeItems.push({
41473
+ label: "\u2500\u2500 Custom \u2500\u2500",
41474
+ description: "",
41475
+ disabled: true
41476
+ });
41477
+ }
41478
+ const customEntries = customs.map((c) => ({
41479
+ label: c.name,
41480
+ description: "custom provider"
40924
41481
  }));
41482
+ return [...nativeItems, ...customEntries];
41483
+ };
41484
+ const initialSelected = () => {
41485
+ if (!props.existingConfig)
41486
+ return;
41487
+ const indices = [];
41488
+ const nativeMap = existingNative();
41489
+ PROVIDERS.forEach((p, i) => {
41490
+ const existing = nativeMap.get(p.id);
41491
+ if (existing && existing.active !== false)
41492
+ indices.push(i);
41493
+ });
41494
+ const customs = customItems();
41495
+ if (customs.length > 0) {
41496
+ const offset = PROVIDERS.length + 1;
41497
+ customs.forEach((c, i) => {
41498
+ if (c.active)
41499
+ indices.push(offset + i);
41500
+ });
41501
+ }
41502
+ return indices.length > 0 ? indices : undefined;
41503
+ };
41504
+ const handleAddCustom = (name) => {
41505
+ const entry = {
41506
+ name,
41507
+ tag: toTag(name),
41508
+ model: "",
41509
+ provider: "custom",
41510
+ active: true,
41511
+ authUrl: "",
41512
+ authToken: "",
41513
+ cli: "claude"
41514
+ };
41515
+ setCustomItems((prev) => [...prev, entry]);
41516
+ };
41517
+ const handleProviderConfirm = (selectedIndices) => {
41518
+ const nativeMap = existingNative();
41519
+ const customs = customItems();
41520
+ const hasCustoms = customs.length > 0;
41521
+ const customOffset = PROVIDERS.length + (hasCustoms ? 1 : 0);
41522
+ const entries = [];
41523
+ for (let i = 0;i < PROVIDERS.length; i++) {
41524
+ const def = PROVIDERS[i];
41525
+ const isSelected = selectedIndices.includes(i);
41526
+ const existing = nativeMap.get(def.id);
41527
+ if (isSelected || existing) {
41528
+ entries.push({
41529
+ name: existing?.name || def.displayName,
41530
+ tag: existing?.tag || def.defaultTag,
41531
+ model: existing?.model || def.defaultModel,
41532
+ provider: def.id,
41533
+ active: isSelected,
41534
+ authUrl: "",
41535
+ authToken: "",
41536
+ cli: "claude",
41537
+ prompts: existing?.prompts,
41538
+ preloadSkills: existing?.preloadSkills,
41539
+ executablePath: existing?.executablePath,
41540
+ timeout: existing?.timeout
41541
+ });
41542
+ }
41543
+ }
41544
+ for (let i = 0;i < customs.length; i++) {
41545
+ const isSelected = selectedIndices.includes(customOffset + i);
41546
+ entries.push({
41547
+ ...customs[i],
41548
+ active: isSelected
41549
+ });
41550
+ }
41551
+ agentEntries = entries;
40925
41552
  setActiveTab(0);
40926
41553
  setFocusedField(0);
40927
- cursorPos = configs[0]?.name.length || 0;
41554
+ cursorPos = settingsData.name.length;
40928
41555
  setStep("configure");
40929
41556
  };
40930
41557
  const handleProviderCancel = () => {
40931
41558
  if (props.onCancel)
40932
41559
  props.onCancel();
40933
41560
  };
40934
- const isYouTab = () => activeTab() === 0;
41561
+ const activeEntries = () => agentEntries.filter((e) => e.active);
41562
+ const isSettingsTab = () => activeTab() === 0;
40935
41563
  const agentTabIndex = () => activeTab() - 1;
40936
- const totalTabs = () => (inputRefsData?.length || 0) + 1;
40937
- const maxFieldIndex = () => isYouTab() ? 1 : 2;
41564
+ const totalTabs = () => activeEntries().length + 1;
41565
+ const isCustomAgent = () => {
41566
+ if (isSettingsTab())
41567
+ return false;
41568
+ const entry = activeEntries()[agentTabIndex()];
41569
+ return entry?.provider === "custom";
41570
+ };
41571
+ const maxFieldIndex = () => {
41572
+ if (isSettingsTab())
41573
+ return 3;
41574
+ return isCustomAgent() ? 4 : 2;
41575
+ };
41576
+ const getFieldValueForEntry = (entry, field) => {
41577
+ if (entry.provider === "custom") {
41578
+ return [entry.name, entry.tag, entry.model, entry.authUrl, entry.authToken][field];
41579
+ }
41580
+ return [entry.name, entry.tag, entry.model][field];
41581
+ };
41582
+ const getSettingsFieldValue = (field) => {
41583
+ return [settingsData.name, settingsData.tag, settingsData.maxHops, settingsData.timeout][field];
41584
+ };
41585
+ const getCurrentFieldValue = () => {
41586
+ if (isSettingsTab())
41587
+ return getSettingsFieldValue(focusedField());
41588
+ const entry = activeEntries()[agentTabIndex()];
41589
+ if (!entry)
41590
+ return "";
41591
+ return getFieldValueForEntry(entry, focusedField());
41592
+ };
41593
+ const updateField = (value, newCursor) => {
41594
+ if (isSettingsTab()) {
41595
+ if (focusedField() === 0)
41596
+ settingsData.name = value;
41597
+ else if (focusedField() === 1)
41598
+ settingsData.tag = value;
41599
+ else if (focusedField() === 2)
41600
+ settingsData.maxHops = value;
41601
+ else
41602
+ settingsData.timeout = value;
41603
+ } else {
41604
+ const entry = activeEntries()[agentTabIndex()];
41605
+ if (!entry)
41606
+ return;
41607
+ if (entry.provider === "custom") {
41608
+ if (focusedField() === 0)
41609
+ entry.name = value;
41610
+ else if (focusedField() === 1)
41611
+ entry.tag = value;
41612
+ else if (focusedField() === 2)
41613
+ entry.model = value;
41614
+ else if (focusedField() === 3)
41615
+ entry.authUrl = value;
41616
+ else
41617
+ entry.authToken = value;
41618
+ } else {
41619
+ if (focusedField() === 0)
41620
+ entry.name = value;
41621
+ else if (focusedField() === 1)
41622
+ entry.tag = value;
41623
+ else
41624
+ entry.model = value;
41625
+ }
41626
+ }
41627
+ cursorPos = newCursor;
41628
+ setError("");
41629
+ setTick((n) => n + 1);
41630
+ };
41631
+ const isTagField = () => focusedField() === 1;
41632
+ const isNumberField = () => isSettingsTab() && (focusedField() === 2 || focusedField() === 3);
40938
41633
  const saveConfig = async () => {
40939
- const configs = inputRefsData;
40940
- const human = humanData;
40941
- if (!human.name.trim()) {
40942
- setError("Your name cannot be empty");
41634
+ if (!settingsData.name.trim()) {
41635
+ setError("Name cannot be empty");
41636
+ return;
41637
+ }
41638
+ if (!settingsData.tag.trim()) {
41639
+ setError("Tag cannot be empty");
40943
41640
  return;
40944
41641
  }
40945
- if (!human.tag.trim()) {
40946
- setError("Your tag cannot be empty");
41642
+ if (!TAG_PATTERN.test(settingsData.tag.trim())) {
41643
+ setError("Tag can only contain letters, numbers, hyphens, underscores");
40947
41644
  return;
40948
41645
  }
40949
- if (!TAG_PATTERN.test(human.tag.trim())) {
40950
- setError("Your tag can only contain letters, numbers, hyphens, underscores");
41646
+ const maxHops = parseInt(settingsData.maxHops, 10);
41647
+ if (isNaN(maxHops) || maxHops < 0) {
41648
+ setError("Max Hops must be 0 or a positive number");
40951
41649
  return;
40952
41650
  }
40953
- for (const c of configs) {
40954
- if (!c.name.trim()) {
40955
- setError(`Name cannot be empty for ${c.id}`);
41651
+ const timeout = parseInt(settingsData.timeout, 10);
41652
+ if (isNaN(timeout) || timeout < 0) {
41653
+ setError("Timeout must be 0 or a positive number");
41654
+ return;
41655
+ }
41656
+ const tags = new Set;
41657
+ tags.add(settingsData.tag.trim().toLowerCase());
41658
+ for (const entry of agentEntries) {
41659
+ if (!entry.name.trim()) {
41660
+ setError(`Name cannot be empty for an agent`);
40956
41661
  return;
40957
41662
  }
40958
- if (!c.tag.trim()) {
40959
- setError(`Tag cannot be empty for ${c.id}`);
41663
+ if (!entry.tag.trim()) {
41664
+ setError(`Tag cannot be empty for ${entry.name}`);
40960
41665
  return;
40961
41666
  }
40962
- if (!TAG_PATTERN.test(c.tag.trim())) {
40963
- setError(`Tag for ${c.name} can only contain letters, numbers, hyphens, underscores`);
41667
+ if (!TAG_PATTERN.test(entry.tag.trim())) {
41668
+ setError(`Tag for ${entry.name} can only contain letters, numbers, hyphens, underscores`);
40964
41669
  return;
40965
41670
  }
40966
- if (!c.model.trim()) {
40967
- setError(`Model cannot be empty for ${c.id}`);
41671
+ if (!entry.model.trim()) {
41672
+ setError(`Model cannot be empty for ${entry.name}`);
40968
41673
  return;
40969
41674
  }
40970
- }
40971
- const names = new Set;
40972
- for (const c of configs) {
40973
- const lower = c.name.trim().toLowerCase();
40974
- if (names.has(lower)) {
40975
- setError(`Duplicate agent name: ${c.name}`);
41675
+ const tagLower = entry.tag.trim().toLowerCase();
41676
+ if (tags.has(tagLower)) {
41677
+ setError(`Duplicate tag: ${entry.tag}`);
41678
+ return;
41679
+ }
41680
+ tags.add(tagLower);
41681
+ if (entry.provider === "custom" && entry.active && !entry.authUrl.trim()) {
41682
+ setError(`URL is required for ${entry.name}`);
40976
41683
  return;
40977
41684
  }
40978
- names.add(lower);
40979
41685
  }
40980
- const overrides = configs.map((c) => ({
40981
- id: c.id,
40982
- name: c.name.trim(),
40983
- tag: c.tag.trim(),
40984
- model: c.model.trim()
40985
- }));
40986
- const mergedExisting = {
40987
- ...props.existingConfig || {},
40988
- humanName: human.name.trim(),
40989
- humanTag: human.tag.trim(),
40990
- agents: props.existingConfig?.agents || []
41686
+ const agents = agentEntries.map(entryToPersona);
41687
+ const config = {
41688
+ humanName: settingsData.name.trim(),
41689
+ humanTag: settingsData.tag.trim(),
41690
+ maxAutoHops: maxHops,
41691
+ timeout: timeout > 0 ? timeout : undefined,
41692
+ reminderInterval: props.existingConfig?.reminderInterval,
41693
+ agents
40991
41694
  };
41695
+ if (config.timeout === undefined)
41696
+ delete config.timeout;
41697
+ if (config.reminderInterval === undefined)
41698
+ delete config.reminderInterval;
40992
41699
  try {
40993
- await writeWizardConfig(selectedIds(), overrides, mergedExisting);
41700
+ await writeConfig(config);
40994
41701
  setStep("done");
40995
41702
  } catch (err) {
40996
41703
  setError(`Failed to save: ${err.message}`);
@@ -41002,9 +41709,8 @@ function ConfigWizard(props) {
41002
41709
  return;
41003
41710
  }
41004
41711
  if (step() !== "configure") {
41005
- if (step() === "detect") {
41712
+ if (step() === "detect")
41006
41713
  return;
41007
- }
41008
41714
  if (step() === "done") {
41009
41715
  if (key.name === "enter" || key.name === "return" || key.name === "space") {
41010
41716
  props.onComplete();
@@ -41013,49 +41719,20 @@ function ConfigWizard(props) {
41013
41719
  }
41014
41720
  return;
41015
41721
  }
41016
- const configs = inputRefsData;
41017
- const human = humanData;
41018
- let fieldValue;
41019
- if (isYouTab()) {
41020
- fieldValue = focusedField() === 0 ? human.name : human.tag;
41021
- } else {
41022
- const current = configs[agentTabIndex()];
41023
- if (!current)
41024
- return;
41025
- fieldValue = [current.name, current.tag, current.model][focusedField()];
41026
- }
41722
+ const fieldValue = getCurrentFieldValue();
41027
41723
  const cursor = cursorPos;
41028
- const updateField = (value, newCursor) => {
41029
- if (isYouTab()) {
41030
- if (focusedField() === 0)
41031
- human.name = value;
41032
- else
41033
- human.tag = value;
41034
- } else {
41035
- const current = configs[agentTabIndex()];
41036
- if (focusedField() === 0)
41037
- current.name = value;
41038
- else if (focusedField() === 1)
41039
- current.tag = value;
41040
- else
41041
- current.model = value;
41042
- }
41043
- cursorPos = newCursor;
41044
- setError("");
41045
- setTick((n) => n + 1);
41046
- };
41047
41724
  if (key.sequence === "[" || key.sequence === "]") {
41048
41725
  const dir = key.sequence === "[" ? -1 : 1;
41049
41726
  const next = (activeTab() + dir + totalTabs()) % totalTabs();
41050
41727
  setActiveTab(next);
41051
- const newMax = next === 0 ? 1 : 2;
41728
+ const newMax = next === 0 ? 3 : activeEntries()[next - 1]?.provider === "custom" ? 4 : 2;
41052
41729
  const newField = Math.min(focusedField(), newMax);
41053
41730
  setFocusedField(newField);
41054
41731
  let newVal;
41055
41732
  if (next === 0) {
41056
- newVal = newField === 0 ? human.name : human.tag;
41733
+ newVal = getSettingsFieldValue(newField);
41057
41734
  } else {
41058
- newVal = getFieldValue(configs[next - 1], newField);
41735
+ newVal = getFieldValueForEntry(activeEntries()[next - 1], newField);
41059
41736
  }
41060
41737
  cursorPos = newVal.length;
41061
41738
  setTick((n) => n + 1);
@@ -41065,12 +41742,7 @@ function ConfigWizard(props) {
41065
41742
  const fieldCount = maxFieldIndex() + 1;
41066
41743
  const nextField = key.shift ? (focusedField() - 1 + fieldCount) % fieldCount : (focusedField() + 1) % fieldCount;
41067
41744
  setFocusedField(nextField);
41068
- let newVal;
41069
- if (isYouTab()) {
41070
- newVal = nextField === 0 ? human.name : human.tag;
41071
- } else {
41072
- newVal = getFieldValue(configs[agentTabIndex()], nextField);
41073
- }
41745
+ const newVal = isSettingsTab() ? getSettingsFieldValue(nextField) : getFieldValueForEntry(activeEntries()[agentTabIndex()], nextField);
41074
41746
  cursorPos = newVal.length;
41075
41747
  setTick((n) => n + 1);
41076
41748
  return;
@@ -41125,7 +41797,7 @@ function ConfigWizard(props) {
41125
41797
  return;
41126
41798
  }
41127
41799
  if (key.name === "space" || key.sequence === " ") {
41128
- if (focusedField() !== 1) {
41800
+ if (!isTagField() && !isNumberField()) {
41129
41801
  updateField(fieldValue.slice(0, cursor) + " " + fieldValue.slice(cursor), cursor + 1);
41130
41802
  }
41131
41803
  return;
@@ -41134,7 +41806,9 @@ function ConfigWizard(props) {
41134
41806
  if (ch && ch.length > 0 && !ch.startsWith("\x1B")) {
41135
41807
  if (ch === "'" || ch === '"' || ch === "`")
41136
41808
  return;
41137
- if (focusedField() === 1 && !TAG_PATTERN.test(ch))
41809
+ if (isTagField() && !TAG_PATTERN.test(ch))
41810
+ return;
41811
+ if (isNumberField() && !/^[0-9]$/.test(ch))
41138
41812
  return;
41139
41813
  updateField(fieldValue.slice(0, cursor) + ch + fieldValue.slice(cursor), cursor + ch.length);
41140
41814
  }
@@ -41152,7 +41826,7 @@ function ConfigWizard(props) {
41152
41826
  })();
41153
41827
  }
41154
41828
  const discoColor = useDiscoColor();
41155
- function renderField(label, value, isFocused) {
41829
+ function renderField(label, value, isFocused, hint) {
41156
41830
  tick();
41157
41831
  const cursor = cursorPos;
41158
41832
  const labelColor = isFocused ? COLORS.primary : COLORS.textDim;
@@ -41175,7 +41849,7 @@ function ConfigWizard(props) {
41175
41849
  setProp(_el$12, "style", {
41176
41850
  fg: valueColor
41177
41851
  });
41178
- insert(_el$12, value);
41852
+ insert(_el$12, value || (hint ? hint : ""));
41179
41853
  effect((_$p) => setProp(_el$0, "style", {
41180
41854
  fg: COLORS.borderStrong
41181
41855
  }, _$p));
@@ -41210,6 +41884,13 @@ function ConfigWizard(props) {
41210
41884
  return _el$13;
41211
41885
  })();
41212
41886
  }
41887
+ function renderTokenField(label, value, isFocused) {
41888
+ if (!isFocused && value.length > 0) {
41889
+ const masked = value.length > 4 ? "*".repeat(value.length - 4) + value.slice(-4) : "*".repeat(value.length);
41890
+ return renderField(label, masked, false);
41891
+ }
41892
+ return renderField(label, value, isFocused);
41893
+ }
41213
41894
  const DetectStep = () => (() => {
41214
41895
  var _el$19 = createElement("box"), _el$20 = createElement("box"), _el$21 = createElement("box"), _el$22 = createElement("text"), _el$23 = createTextNode(` Scanning for installed CLIs...`);
41215
41896
  insertNode(_el$19, _el$20);
@@ -41325,6 +42006,9 @@ function ConfigWizard(props) {
41325
42006
  },
41326
42007
  get initialSelected() {
41327
42008
  return initialSelected();
42009
+ },
42010
+ addCustom: {
42011
+ onAdd: handleAddCustom
41328
42012
  }
41329
42013
  }));
41330
42014
  effect((_p$) => {
@@ -41362,12 +42046,8 @@ function ConfigWizard(props) {
41362
42046
  })();
41363
42047
  const ConfigureStep = () => {
41364
42048
  tick();
41365
- const configs = inputRefsData;
41366
- const human = humanData;
41367
- const tabLabels = ["You", ...selectedIds().map((id) => {
41368
- const def = PROVIDERS.find((p) => p.id === id);
41369
- return def?.displayName || id;
41370
- })];
42049
+ const active = activeEntries();
42050
+ const tabLabels = ["Settings", ...active.map((e) => e.name)];
41371
42051
  return (() => {
41372
42052
  var _el$48 = createElement("box"), _el$49 = createElement("box"), _el$50 = createElement("box"), _el$51 = createElement("box"), _el$52 = createElement("box"), _el$53 = createElement("text"), _el$55 = createElement("box"), _el$56 = createElement("box"), _el$60 = createElement("box"), _el$61 = createElement("text"), _el$62 = createElement("span"), _el$64 = createElement("span"), _el$66 = createElement("span"), _el$68 = createElement("span"), _el$70 = createElement("span"), _el$72 = createElement("span"), _el$74 = createElement("span"), _el$76 = createElement("span"), _el$78 = createElement("span"), _el$80 = createElement("span"), _el$82 = createElement("span"), _el$84 = createElement("span"), _el$86 = createElement("span");
41373
42053
  insertNode(_el$48, _el$49);
@@ -41439,7 +42119,7 @@ function ConfigWizard(props) {
41439
42119
  setProp(_el$56, "flexDirection", "column");
41440
42120
  insert(_el$56, createComponent2(Show, {
41441
42121
  get when() {
41442
- return isYouTab();
42122
+ return isSettingsTab();
41443
42123
  },
41444
42124
  get fallback() {
41445
42125
  return [(() => {
@@ -41448,19 +42128,39 @@ function ConfigWizard(props) {
41448
42128
  setProp(_el$94, "marginBottom", 1);
41449
42129
  insertNode(_el$95, _el$96);
41450
42130
  insert(_el$95, () => tabLabels[activeTab()], _el$96);
42131
+ insert(_el$94, createComponent2(Show, {
42132
+ get when() {
42133
+ return activeEntries()[agentTabIndex()]?.provider === "custom";
42134
+ },
42135
+ get children() {
42136
+ var _el$97 = createElement("span");
42137
+ insertNode(_el$97, createTextNode(` (custom)`));
42138
+ effect((_$p) => setProp(_el$97, "style", {
42139
+ fg: COLORS.textFaint
42140
+ }, _$p));
42141
+ return _el$97;
42142
+ }
42143
+ }), null);
41451
42144
  effect((_$p) => setProp(_el$94, "fg", COLORS.success, _$p));
41452
42145
  return _el$94;
41453
- })(), memo2(() => renderField("Name ", configs[agentTabIndex()].name, focusedField() === 0)), memo2(() => renderField("Tag ", configs[agentTabIndex()].tag, focusedField() === 1)), memo2(() => renderField("Model", configs[agentTabIndex()].model, focusedField() === 2))];
42146
+ })(), memo2(() => renderField("Name ", active[agentTabIndex()].name, focusedField() === 0)), memo2(() => renderField("Tag ", active[agentTabIndex()].tag, focusedField() === 1)), memo2(() => renderField("Model", active[agentTabIndex()].model, focusedField() === 2)), createComponent2(Show, {
42147
+ get when() {
42148
+ return active[agentTabIndex()]?.provider === "custom";
42149
+ },
42150
+ get children() {
42151
+ return [memo2(() => renderField("URL ", active[agentTabIndex()].authUrl, focusedField() === 3)), memo2(() => renderTokenField("Token", active[agentTabIndex()].authToken, focusedField() === 4))];
42152
+ }
42153
+ })];
41454
42154
  },
41455
42155
  get children() {
41456
42156
  return [(() => {
41457
42157
  var _el$57 = createElement("text"), _el$58 = createElement("strong");
41458
42158
  insertNode(_el$57, _el$58);
41459
42159
  setProp(_el$57, "marginBottom", 1);
41460
- insertNode(_el$58, createTextNode(`Your Identity`));
42160
+ insertNode(_el$58, createTextNode(`General Settings`));
41461
42161
  effect((_$p) => setProp(_el$57, "fg", COLORS.agent, _$p));
41462
42162
  return _el$57;
41463
- })(), memo2(() => renderField("Name", human.name, focusedField() === 0)), memo2(() => renderField("Tag ", human.tag, focusedField() === 1))];
42163
+ })(), memo2(() => renderField("Name ", settingsData.name, focusedField() === 0)), memo2(() => renderField("Tag ", settingsData.tag, focusedField() === 1)), memo2(() => renderField("Max Hops", settingsData.maxHops, focusedField() === 2, "0 = unlimited")), memo2(() => renderField("Timeout ", settingsData.timeout, focusedField() === 3, "0 = unlimited"))];
41464
42164
  }
41465
42165
  }));
41466
42166
  insertNode(_el$60, _el$61);
@@ -41520,7 +42220,7 @@ function ConfigWizard(props) {
41520
42220
  }
41521
42221
  }), null);
41522
42222
  effect((_p$) => {
41523
- var _v$13 = discoColor(), _v$14 = COLORS.bgPanel, _v$15 = COLORS.borderStrong, _v$16 = isYouTab() ? COLORS.agent : COLORS.success, _v$17 = COLORS.bgContent, _v$18 = {
42223
+ var _v$13 = discoColor(), _v$14 = COLORS.bgPanel, _v$15 = COLORS.borderStrong, _v$16 = isSettingsTab() ? COLORS.agent : COLORS.success, _v$17 = COLORS.bgContent, _v$18 = {
41524
42224
  fg: COLORS.textFaint
41525
42225
  }, _v$19 = {
41526
42226
  fg: COLORS.success
@@ -41590,57 +42290,57 @@ function ConfigWizard(props) {
41590
42290
  })();
41591
42291
  };
41592
42292
  const DoneStep = () => (() => {
41593
- var _el$97 = createElement("box"), _el$98 = createElement("box"), _el$99 = createElement("box"), _el$100 = createElement("text"), _el$102 = createElement("text"), _el$104 = createElement("text"), _el$106 = createElement("text"), _el$108 = createElement("text"), _el$110 = createElement("text"), _el$111 = createTextNode(`Press `), _el$112 = createElement("span"), _el$113 = createElement("strong"), _el$115 = createTextNode(` to continue`);
41594
- insertNode(_el$97, _el$98);
41595
- setProp(_el$97, "flexDirection", "column");
41596
- setProp(_el$97, "width", "100%");
41597
- setProp(_el$97, "height", "100%");
41598
- setProp(_el$97, "justifyContent", "center");
41599
- setProp(_el$97, "alignItems", "center");
41600
- insertNode(_el$98, _el$99);
41601
- setProp(_el$98, "border", true);
41602
- setProp(_el$98, "borderStyle", "double");
41603
- setProp(_el$98, "paddingX", 4);
41604
- setProp(_el$98, "paddingY", 2);
42293
+ var _el$99 = createElement("box"), _el$100 = createElement("box"), _el$101 = createElement("box"), _el$102 = createElement("text"), _el$104 = createElement("text"), _el$106 = createElement("text"), _el$108 = createElement("text"), _el$110 = createElement("text"), _el$112 = createElement("text"), _el$113 = createTextNode(`Press `), _el$114 = createElement("span"), _el$115 = createElement("strong"), _el$117 = createTextNode(` to continue`);
41605
42294
  insertNode(_el$99, _el$100);
41606
- insertNode(_el$99, _el$102);
41607
- insertNode(_el$99, _el$104);
41608
- insertNode(_el$99, _el$106);
41609
- insertNode(_el$99, _el$108);
41610
- insertNode(_el$99, _el$110);
41611
42295
  setProp(_el$99, "flexDirection", "column");
42296
+ setProp(_el$99, "width", "100%");
42297
+ setProp(_el$99, "height", "100%");
42298
+ setProp(_el$99, "justifyContent", "center");
41612
42299
  setProp(_el$99, "alignItems", "center");
41613
- insert(_el$99, createComponent2(SweepBar, {
42300
+ insertNode(_el$100, _el$101);
42301
+ setProp(_el$100, "border", true);
42302
+ setProp(_el$100, "borderStyle", "double");
42303
+ setProp(_el$100, "paddingX", 4);
42304
+ setProp(_el$100, "paddingY", 2);
42305
+ insertNode(_el$101, _el$102);
42306
+ insertNode(_el$101, _el$104);
42307
+ insertNode(_el$101, _el$106);
42308
+ insertNode(_el$101, _el$108);
42309
+ insertNode(_el$101, _el$110);
42310
+ insertNode(_el$101, _el$112);
42311
+ setProp(_el$101, "flexDirection", "column");
42312
+ setProp(_el$101, "alignItems", "center");
42313
+ insert(_el$101, createComponent2(SweepBar, {
41614
42314
  title: "Config Saved"
41615
- }), _el$100);
41616
- insertNode(_el$100, 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`));
41617
- setProp(_el$100, "marginTop", 1);
41618
- insertNode(_el$102, createTextNode(`Written to ~/.llm-party/config.json`));
42315
+ }), _el$102);
42316
+ insertNode(_el$102, 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`));
41619
42317
  setProp(_el$102, "marginTop", 1);
41620
- insertNode(_el$104, createTextNode(`Edit this file anytime to add prompts,`));
42318
+ insertNode(_el$104, createTextNode(`Written to ~/.llm-party/config.json`));
41621
42319
  setProp(_el$104, "marginTop", 1);
41622
- insertNode(_el$106, createTextNode(`env vars, or tweak settings.`));
41623
- insertNode(_el$108, 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`));
41624
- setProp(_el$108, "marginTop", 1);
41625
- insertNode(_el$110, _el$111);
41626
- insertNode(_el$110, _el$112);
41627
- insertNode(_el$110, _el$115);
42320
+ insertNode(_el$106, createTextNode(`Edit this file anytime to add prompts,`));
42321
+ setProp(_el$106, "marginTop", 1);
42322
+ insertNode(_el$108, createTextNode(`env vars, or tweak settings.`));
42323
+ insertNode(_el$110, 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`));
41628
42324
  setProp(_el$110, "marginTop", 1);
41629
42325
  insertNode(_el$112, _el$113);
41630
- insertNode(_el$113, createTextNode(`Enter`));
42326
+ insertNode(_el$112, _el$114);
42327
+ insertNode(_el$112, _el$117);
42328
+ setProp(_el$112, "marginTop", 1);
42329
+ insertNode(_el$114, _el$115);
42330
+ insertNode(_el$115, createTextNode(`Enter`));
41631
42331
  effect((_p$) => {
41632
42332
  var _v$33 = discoColor(), _v$34 = COLORS.bgPanel, _v$35 = COLORS.textSubtle, _v$36 = COLORS.textMuted, _v$37 = COLORS.textSubtle, _v$38 = COLORS.textSubtle, _v$39 = COLORS.textSubtle, _v$40 = COLORS.primary, _v$41 = {
41633
42333
  fg: COLORS.success
41634
42334
  };
41635
- _v$33 !== _p$.e && (_p$.e = setProp(_el$98, "borderColor", _v$33, _p$.e));
41636
- _v$34 !== _p$.t && (_p$.t = setProp(_el$98, "backgroundColor", _v$34, _p$.t));
41637
- _v$35 !== _p$.a && (_p$.a = setProp(_el$100, "fg", _v$35, _p$.a));
41638
- _v$36 !== _p$.o && (_p$.o = setProp(_el$102, "fg", _v$36, _p$.o));
41639
- _v$37 !== _p$.i && (_p$.i = setProp(_el$104, "fg", _v$37, _p$.i));
41640
- _v$38 !== _p$.n && (_p$.n = setProp(_el$106, "fg", _v$38, _p$.n));
41641
- _v$39 !== _p$.s && (_p$.s = setProp(_el$108, "fg", _v$39, _p$.s));
41642
- _v$40 !== _p$.h && (_p$.h = setProp(_el$110, "fg", _v$40, _p$.h));
41643
- _v$41 !== _p$.r && (_p$.r = setProp(_el$112, "style", _v$41, _p$.r));
42335
+ _v$33 !== _p$.e && (_p$.e = setProp(_el$100, "borderColor", _v$33, _p$.e));
42336
+ _v$34 !== _p$.t && (_p$.t = setProp(_el$100, "backgroundColor", _v$34, _p$.t));
42337
+ _v$35 !== _p$.a && (_p$.a = setProp(_el$102, "fg", _v$35, _p$.a));
42338
+ _v$36 !== _p$.o && (_p$.o = setProp(_el$104, "fg", _v$36, _p$.o));
42339
+ _v$37 !== _p$.i && (_p$.i = setProp(_el$106, "fg", _v$37, _p$.i));
42340
+ _v$38 !== _p$.n && (_p$.n = setProp(_el$108, "fg", _v$38, _p$.n));
42341
+ _v$39 !== _p$.s && (_p$.s = setProp(_el$110, "fg", _v$39, _p$.s));
42342
+ _v$40 !== _p$.h && (_p$.h = setProp(_el$112, "fg", _v$40, _p$.h));
42343
+ _v$41 !== _p$.r && (_p$.r = setProp(_el$114, "style", _v$41, _p$.r));
41644
42344
  return _p$;
41645
42345
  }, {
41646
42346
  e: undefined,
@@ -41653,7 +42353,7 @@ function ConfigWizard(props) {
41653
42353
  h: undefined,
41654
42354
  r: undefined
41655
42355
  });
41656
- return _el$97;
42356
+ return _el$99;
41657
42357
  })();
41658
42358
  return [createComponent2(Show, {
41659
42359
  get when() {
@@ -41685,13 +42385,6 @@ function ConfigWizard(props) {
41685
42385
  }
41686
42386
  })];
41687
42387
  }
41688
- function getFieldValue(config, field) {
41689
- if (field === 0)
41690
- return config.name;
41691
- if (field === 1)
41692
- return config.tag;
41693
- return config.model;
41694
- }
41695
42388
 
41696
42389
  // src/ui/AgentsPanel.tsx
41697
42390
  function AgentsPanel(props) {
@@ -41996,7 +42689,7 @@ function InfoPanel(props) {
41996
42689
 
41997
42690
  // src/ui/App.tsx
41998
42691
  function copyToClipboard(text) {
41999
- const proc = spawn5("pbcopy", [], {
42692
+ const proc = spawn4("pbcopy", [], {
42000
42693
  stdio: ["pipe", "ignore", "ignore"]
42001
42694
  });
42002
42695
  proc.stdin?.write(text);
@@ -42018,29 +42711,64 @@ function App(props) {
42018
42711
  const {
42019
42712
  messages,
42020
42713
  agentStates,
42714
+ queueCounts,
42021
42715
  stickyTarget,
42022
42716
  dispatching,
42023
42717
  dispatch,
42024
42718
  addSystemMessage,
42025
- clearMessages
42026
- } = useOrchestrator(props.orchestrator, props.maxAutoHops);
42719
+ addDisplayMessage,
42720
+ clearMessages,
42721
+ refreshStickyTarget
42722
+ } = useOrchestrator(props.orchestrator);
42027
42723
  const humanName = props.orchestrator.getHumanName();
42028
42724
  const agents = props.orchestrator.listAgents();
42029
42725
  let scrollRef = null;
42030
42726
  const [screen, setScreen] = createSignal("chat");
42727
+ const [freshConfig, setFreshConfig] = createSignal(props.config);
42031
42728
  const [showAgents, setShowAgents] = createSignal(false);
42032
42729
  const [showInfo, setShowInfo] = createSignal(false);
42033
- process.on("SIGINT", () => renderer.destroy());
42034
- process.on("SIGTERM", () => renderer.destroy());
42035
- 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());
42036
42760
  process.on("SIGTSTP", () => {
42037
42761
  process.once("SIGCONT", () => renderer.resume());
42038
42762
  renderer.suspend();
42039
42763
  });
42040
- const gracefulExit = () => {
42764
+ const gracefulExit = async () => {
42765
+ await props.orchestrator.abortAll();
42041
42766
  renderer.destroy();
42042
42767
  const adapters = props.orchestrator.getAdapters();
42043
- 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);
42044
42772
  };
42045
42773
  useKeyboard((key) => {
42046
42774
  if (key.ctrl && key.name === "p") {
@@ -42101,6 +42829,10 @@ Transcript: ${props.orchestrator.getTranscriptPath()}`);
42101
42829
  return;
42102
42830
  }
42103
42831
  if (line === "/config") {
42832
+ try {
42833
+ const reloaded = await loadConfig2(props.configPath);
42834
+ setFreshConfig(reloaded);
42835
+ } catch {}
42104
42836
  setScreen("config");
42105
42837
  return;
42106
42838
  }
@@ -42115,6 +42847,19 @@ ${files.map((f) => ` ${f}`).join(`
42115
42847
  setShowInfo(true);
42116
42848
  return;
42117
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
+ }
42118
42863
  if (line.startsWith("/flood")) {
42119
42864
  const args = line.split(/\s+/);
42120
42865
  const count = parseInt(args[1], 10) || 50;
@@ -42141,7 +42886,7 @@ ${lines.join(`
42141
42886
  return createComponent2(ConfigWizard, {
42142
42887
  isFirstRun: false,
42143
42888
  get existingConfig() {
42144
- return props.config;
42889
+ return freshConfig();
42145
42890
  },
42146
42891
  onComplete: () => {
42147
42892
  addSystemMessage("Config saved. Restart llm-party to apply changes.");
@@ -42179,13 +42924,16 @@ ${lines.join(`
42179
42924
  },
42180
42925
  get stickyTarget() {
42181
42926
  return stickyTarget();
42927
+ },
42928
+ get queueCounts() {
42929
+ return queueCounts();
42182
42930
  }
42183
42931
  }), null);
42184
42932
  insert(_el$, createComponent2(InputLine, {
42185
42933
  humanName,
42186
42934
  onSubmit: handleSubmit,
42187
42935
  get disabled() {
42188
- return dispatching() || showAgents() || showInfo();
42936
+ return showAgents() || showInfo();
42189
42937
  },
42190
42938
  get disabledMessage() {
42191
42939
  return showAgents() ? "" : undefined;
@@ -42221,6 +42969,8 @@ ${lines.join(`
42221
42969
  async function main2() {
42222
42970
  const appRoot = path11.resolve(path11.dirname(fileURLToPath3(import.meta.url)), "..");
42223
42971
  await initLlmPartyHome(appRoot);
42972
+ const resumeIndex = process.argv.indexOf("--resume");
42973
+ const resumeSessionId = resumeIndex !== -1 ? process.argv[resumeIndex + 1] : undefined;
42224
42974
  const rendererConfig = {
42225
42975
  exitOnCtrlC: false,
42226
42976
  useMouse: true,
@@ -42231,14 +42981,14 @@ async function main2() {
42231
42981
  await render(() => ConfigWizard({
42232
42982
  isFirstRun: true,
42233
42983
  onComplete: async () => {
42234
- await bootApp(appRoot, rendererConfig);
42984
+ await bootApp(appRoot, rendererConfig, resumeSessionId);
42235
42985
  }
42236
42986
  }), rendererConfig);
42237
42987
  } else {
42238
- await bootApp(appRoot, rendererConfig);
42988
+ await bootApp(appRoot, rendererConfig, resumeSessionId);
42239
42989
  }
42240
42990
  }
42241
- async function bootApp(appRoot, rendererConfig) {
42991
+ async function bootApp(appRoot, rendererConfig, resumeSessionId) {
42242
42992
  const configPath = await resolveConfigPath(appRoot);
42243
42993
  const config = await loadConfig2(configPath);
42244
42994
  const humanName = config.humanName?.trim() || "USER";
@@ -42258,7 +43008,8 @@ async function bootApp(appRoot, rendererConfig) {
42258
43008
  ` + obsidianPrompt;
42259
43009
  const availableSkills = await discoverSkills();
42260
43010
  const agentVerifiedSkills = new Map;
42261
- for (const agent of config.agents) {
43011
+ const activeAgents = config.agents.filter((a) => a.active !== false);
43012
+ for (const agent of activeAgents) {
42262
43013
  if (agent.preloadSkills && agent.preloadSkills.length > 0) {
42263
43014
  const verified = [];
42264
43015
  const report = [];
@@ -42279,11 +43030,11 @@ async function bootApp(appRoot, rendererConfig) {
42279
43030
  const resolveFromConfig = (value) => {
42280
43031
  return path11.isAbsolute(value) ? value : path11.resolve(configDir, value);
42281
43032
  };
42282
- const adapters = await Promise.all(config.agents.map(async (agent, _index, allAgents) => {
43033
+ const adapters = await Promise.all(activeAgents.map(async (agent, _index, allAgents) => {
42283
43034
  const promptParts = [mergedBase];
42284
43035
  if (agent.prompts && agent.prompts.length > 0) {
42285
43036
  const extraPaths = agent.prompts.map((p) => resolveFromConfig(p));
42286
- const extraParts = await Promise.all(extraPaths.map((p) => readFile5(p, "utf8")));
43037
+ const extraParts = await Promise.all(extraPaths.map((p) => readFile6(p, "utf8")));
42287
43038
  promptParts.push(...extraParts);
42288
43039
  }
42289
43040
  const promptTemplate = promptParts.join(`
@@ -42318,7 +43069,7 @@ ${mySkills.map((s) => `- ${s}`).join(`
42318
43069
  agentCount: String(allAgents.length),
42319
43070
  preloadedSkills
42320
43071
  });
42321
- const adapter = agent.provider === "claude" ? new ClaudeAdapter(agent.name, agent.model) : agent.provider === "codex" ? new CodexAdapter(agent.name, agent.model) : agent.provider === "copilot" ? new CopilotAdapter(agent.name, agent.model) : agent.provider === "glm" ? new GlmAdapter(agent.name, agent.model) : null;
43072
+ const adapter = agent.provider === "claude" ? new ClaudeAdapter(agent.name, agent.model, humanName) : agent.provider === "codex" ? new CodexAdapter(agent.name, agent.model, humanName) : agent.provider === "copilot" ? new CopilotAdapter(agent.name, agent.model, humanName) : agent.provider === "custom" ? new CustomAdapter(agent.name, agent.model, humanName) : null;
42322
43073
  if (!adapter) {
42323
43074
  throw new Error(`Unsupported provider: ${agent.provider}`);
42324
43075
  }
@@ -42326,12 +43077,12 @@ ${mySkills.map((s) => `- ${s}`).join(`
42326
43077
  return adapter;
42327
43078
  }));
42328
43079
  const defaultTimeout = typeof config.timeout === "number" && config.timeout > 0 ? config.timeout * 1000 : 600000;
42329
- const agentTimeouts = Object.fromEntries(config.agents.filter((agent) => typeof agent.timeout === "number" && agent.timeout > 0).map((agent) => [agent.name, agent.timeout * 1000]));
42330
- const orchestrator = new Orchestrator(adapters, humanName, Object.fromEntries(config.agents.map((agent) => [agent.name, agent.tag?.trim() || toTag(agent.name)])), humanTag, defaultTimeout, agentTimeouts, { reminderInterval: config.reminderInterval });
42331
- await render(() => App({ orchestrator, maxAutoHops, config }), rendererConfig);
43080
+ const agentTimeouts = Object.fromEntries(activeAgents.filter((agent) => typeof agent.timeout === "number" && agent.timeout > 0).map((agent) => [agent.name, agent.timeout * 1000]));
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);
42332
43083
  }
42333
43084
  function resolveMaxAutoHops(value) {
42334
- if (value === "unlimited") {
43085
+ if (typeof value === "number" && value === 0) {
42335
43086
  return Number.POSITIVE_INFINITY;
42336
43087
  }
42337
43088
  if (typeof value === "number" && Number.isFinite(value) && value >= 1) {