@usabledev/usable-chat 1.153.0 → 1.154.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/cli.js +394 -201
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -77609,11 +77609,11 @@ var require_randomUUID = __commonJS({
77609
77609
  var require_dist_cjs31 = __commonJS({
77610
77610
  "node_modules/.pnpm/@smithy+uuid@1.1.2/node_modules/@smithy/uuid/dist-cjs/index.js"(exports) {
77611
77611
  "use strict";
77612
- var randomUUID7 = require_randomUUID();
77612
+ var randomUUID8 = require_randomUUID();
77613
77613
  var decimalToHex = Array.from({ length: 256 }, (_16, i18) => i18.toString(16).padStart(2, "0"));
77614
77614
  var v42 = () => {
77615
- if (randomUUID7.randomUUID) {
77616
- return randomUUID7.randomUUID();
77615
+ if (randomUUID8.randomUUID) {
77616
+ return randomUUID8.randomUUID();
77617
77617
  }
77618
77618
  const rnds = new Uint8Array(16);
77619
77619
  crypto.getRandomValues(rnds);
@@ -131517,7 +131517,7 @@ var require_gaxios = __commonJS({
131517
131517
  var retry_js_1 = require_retry();
131518
131518
  var stream_1 = __require("stream");
131519
131519
  var interceptor_js_1 = require_interceptor();
131520
- var randomUUID7 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
131520
+ var randomUUID8 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
131521
131521
  var HTTP_STATUS_NO_CONTENT = 204;
131522
131522
  var Gaxios = class {
131523
131523
  agentCache = /* @__PURE__ */ new Map();
@@ -131790,7 +131790,7 @@ var require_gaxios = __commonJS({
131790
131790
  */
131791
131791
  ["Blob", "File", "FormData"].includes(opts.data?.constructor?.name || "");
131792
131792
  if (opts.multipart?.length) {
131793
- const boundary = await randomUUID7();
131793
+ const boundary = await randomUUID8();
131794
131794
  preparedHeaders.set("content-type", `multipart/related; boundary=${boundary}`);
131795
131795
  opts.body = stream_1.Readable.from(this.getMultipartRequest(opts.multipart, boundary));
131796
131796
  } else if (shouldDirectlyPassData) {
@@ -162589,6 +162589,7 @@ async function adaptOpenRouterStream(stream, options2) {
162589
162589
  let finishReason = null;
162590
162590
  const toolCalls = [];
162591
162591
  const completedToolCalls = [];
162592
+ const toolExecutionPromises = [];
162592
162593
  const toolResults = [];
162593
162594
  let capturedUsage;
162594
162595
  let capturedModel;
@@ -162598,6 +162599,188 @@ async function adaptOpenRouterStream(stream, options2) {
162598
162599
  let lastEmittedReasoning = "";
162599
162600
  const accumulatedReasoningDetails = [];
162600
162601
  const pendingToolCalls = /* @__PURE__ */ new Map();
162602
+ const executeParsedToolCall = async (id, name18, args) => {
162603
+ if (context.abortSignal?.aborted) {
162604
+ streamLogger.info("Abort signal detected before tool execution, returning cancelled result", {
162605
+ toolName: name18
162606
+ });
162607
+ const errorResult = { error: "Request cancelled by user", success: false };
162608
+ toolCalls.push({ toolName: name18, success: false });
162609
+ toolResults.push({ id, name: name18, result: errorResult, isError: true });
162610
+ const errorEvent = emitter.emit("tool-result", {
162611
+ id,
162612
+ toolName: name18,
162613
+ input: args,
162614
+ result: errorResult,
162615
+ success: false,
162616
+ error: errorResult.error
162617
+ });
162618
+ multiplexer.send(errorEvent);
162619
+ return;
162620
+ }
162621
+ streamLogger.debug("Executing tool", { toolName: name18, args });
162622
+ try {
162623
+ const toolResult = await executeTool3(name18, args, {
162624
+ ...context,
162625
+ toolCallId: id
162626
+ });
162627
+ const success2 = !toolResult?.error;
162628
+ toolCalls.push({ toolName: name18, success: success2 });
162629
+ toolResults.push({ id, name: name18, result: toolResult, isError: !success2 });
162630
+ if (context.session) {
162631
+ if (!context.session.toolHistory) {
162632
+ context.session.toolHistory = [];
162633
+ }
162634
+ const toolHistory = context.session.toolHistory;
162635
+ toolHistory.push({
162636
+ toolName: name18,
162637
+ args,
162638
+ result: toolResult,
162639
+ success: success2,
162640
+ timestamp: Date.now()
162641
+ });
162642
+ if (toolHistory.length > 50) {
162643
+ toolHistory.shift();
162644
+ }
162645
+ }
162646
+ const toolResultEvent = emitter.emit("tool-result", {
162647
+ id,
162648
+ toolName: name18,
162649
+ input: args,
162650
+ result: toolResult,
162651
+ success: success2,
162652
+ error: toolResult?.error ? String(toolResult.error) : void 0
162653
+ });
162654
+ multiplexer.send(toolResultEvent);
162655
+ if (name18 === "check-context-window" && success2 && toolResult?.data) {
162656
+ const data2 = toolResult.data;
162657
+ const breakdown = data2.breakdown.systemPrompt !== void 0 ? {
162658
+ systemPrompts: data2.breakdown.systemPrompt + (data2.breakdown.systemTools || 0),
162659
+ contextTokens: data2.breakdown.memoryFiles || 0,
162660
+ toolCallsAndResponses: data2.breakdown.toolUseAndResults || 0,
162661
+ userQuestions: 0,
162662
+ assistantMessages: data2.breakdown.messages || 0
162663
+ } : data2.breakdown;
162664
+ const cardEvent = emitter.emit("card", {
162665
+ component: "context-window",
162666
+ data: {
162667
+ totalTokens: data2.totalTokens,
162668
+ maxTokens: data2.maxTokens,
162669
+ percentage: data2.percentage,
162670
+ breakdown,
162671
+ freeSpace: data2.freeSpace,
162672
+ contextItems: data2.contextItems || {
162673
+ workspaces: 0,
162674
+ fragments: 0,
162675
+ toolResults: 0
162676
+ }
162677
+ }
162678
+ });
162679
+ multiplexer.send(cardEvent);
162680
+ }
162681
+ if (isAskQuestionPause(toolResult)) {
162682
+ pauseDetected = true;
162683
+ if (onPauseDetected) {
162684
+ await onPauseDetected({
162685
+ resumeToken: toolResult.__pause.resumeToken,
162686
+ questions: toolResult.__pause.questions,
162687
+ context: toolResult.__pause.context
162688
+ });
162689
+ }
162690
+ return;
162691
+ }
162692
+ if (toolResult?.__parentToolCall === true) {
162693
+ const parentToolName = toolResult.toolName;
162694
+ const parentToolArgs = toolResult.args;
162695
+ const requestId = `parent_${id}_${Date.now()}`;
162696
+ streamLogger.info("Parent tool call detected - waiting for parent response", {
162697
+ toolName: name18,
162698
+ parentToolName,
162699
+ requestId
162700
+ });
162701
+ const parentToolEvent = emitter.emit("parent-tool-call", {
162702
+ requestId,
162703
+ toolName: parentToolName,
162704
+ args: parentToolArgs
162705
+ });
162706
+ multiplexer.send(parentToolEvent);
162707
+ try {
162708
+ const parentResponse = await waitForParentToolResponse(
162709
+ requestId,
162710
+ parentToolName,
162711
+ parentToolArgs,
162712
+ 3e4
162713
+ );
162714
+ streamLogger.info("Parent tool response received", {
162715
+ requestId,
162716
+ parentToolName,
162717
+ hasResult: parentResponse !== void 0
162718
+ });
162719
+ const resultIndex = toolResults.findIndex((r17) => r17.id === id);
162720
+ if (resultIndex !== -1) {
162721
+ toolResults[resultIndex].result = parentResponse;
162722
+ }
162723
+ const updatedResultEvent = emitter.emit("tool-result", {
162724
+ id,
162725
+ toolName: name18,
162726
+ input: args,
162727
+ result: parentResponse,
162728
+ success: true
162729
+ });
162730
+ multiplexer.send(updatedResultEvent);
162731
+ } catch (parentError) {
162732
+ streamLogger.error("Parent tool call failed", {
162733
+ requestId,
162734
+ parentToolName,
162735
+ error: parentError instanceof Error ? parentError.message : String(parentError)
162736
+ });
162737
+ const errorResult = {
162738
+ error: parentError instanceof Error ? parentError.message : String(parentError),
162739
+ success: false
162740
+ };
162741
+ const resultIndex = toolResults.findIndex((r17) => r17.id === id);
162742
+ if (resultIndex !== -1) {
162743
+ toolResults[resultIndex].result = errorResult;
162744
+ toolResults[resultIndex].isError = true;
162745
+ }
162746
+ const errorEvent = emitter.emit("tool-result", {
162747
+ id,
162748
+ toolName: name18,
162749
+ input: args,
162750
+ result: errorResult,
162751
+ success: false,
162752
+ error: parentError instanceof Error ? parentError.message : String(parentError)
162753
+ });
162754
+ multiplexer.send(errorEvent);
162755
+ }
162756
+ }
162757
+ if (emitObservations && success2) {
162758
+ const observation = generateObservation2(name18, toolResult);
162759
+ const observationEvent = emitter.emit("observation", {
162760
+ observation,
162761
+ toolCallIds: [id]
162762
+ });
162763
+ multiplexer.send(observationEvent);
162764
+ }
162765
+ } catch (toolError) {
162766
+ console.error(`\u274C Tool execution error for ${name18}:`, toolError);
162767
+ toolCalls.push({ toolName: name18, success: false });
162768
+ const errorResult = {
162769
+ error: toolError instanceof Error ? toolError.message : String(toolError),
162770
+ success: false
162771
+ };
162772
+ toolResults.push({ id, name: name18, result: errorResult, isError: true });
162773
+ const errorEvent = emitter.emit("tool-result", {
162774
+ id,
162775
+ toolName: name18,
162776
+ input: args,
162777
+ result: null,
162778
+ success: false,
162779
+ error: toolError instanceof Error ? toolError.message : String(toolError)
162780
+ });
162781
+ multiplexer.send(errorEvent);
162782
+ }
162783
+ };
162601
162784
  try {
162602
162785
  while (true) {
162603
162786
  if (context.abortSignal?.aborted) {
@@ -162855,187 +163038,9 @@ async function adaptOpenRouterStream(stream, options2) {
162855
163038
  });
162856
163039
  multiplexer.send(retrievalEvent);
162857
163040
  }
162858
- if (context.abortSignal?.aborted) {
162859
- streamLogger.info("Abort signal detected before tool execution, skipping", {
162860
- toolName: pending.name
162861
- });
162862
- break;
162863
- }
162864
- streamLogger.debug("Executing tool", {
162865
- toolName: pending.name,
162866
- args
162867
- });
162868
- try {
162869
- const toolResult = await executeTool3(pending.name, args, { ...context, toolCallId: pending.id });
162870
- const success2 = !toolResult?.error;
162871
- toolCalls.push({ toolName: pending.name, success: success2 });
162872
- toolResults.push({
162873
- id: pending.id,
162874
- name: pending.name,
162875
- result: toolResult,
162876
- isError: !success2
162877
- });
162878
- if (context.session) {
162879
- if (!context.session.toolHistory) {
162880
- context.session.toolHistory = [];
162881
- }
162882
- const toolHistory = context.session.toolHistory;
162883
- toolHistory.push({
162884
- toolName: pending.name,
162885
- args,
162886
- result: toolResult,
162887
- success: success2,
162888
- timestamp: Date.now()
162889
- });
162890
- if (toolHistory.length > 50) {
162891
- toolHistory.shift();
162892
- }
162893
- }
162894
- const toolResultEvent = emitter.emit("tool-result", {
162895
- id: pending.id,
162896
- toolName: pending.name,
162897
- input: args,
162898
- // Include input arguments
162899
- result: toolResult,
162900
- success: success2,
162901
- error: toolResult?.error ? String(toolResult.error) : void 0
162902
- });
162903
- multiplexer.send(toolResultEvent);
162904
- if (pending.name === "check-context-window" && success2 && toolResult?.data) {
162905
- const data2 = toolResult.data;
162906
- const breakdown = data2.breakdown.systemPrompt !== void 0 ? {
162907
- // New format - map to old format
162908
- systemPrompts: data2.breakdown.systemPrompt + (data2.breakdown.systemTools || 0),
162909
- contextTokens: data2.breakdown.memoryFiles || 0,
162910
- toolCallsAndResponses: data2.breakdown.toolUseAndResults || 0,
162911
- userQuestions: 0,
162912
- // Will be calculated from messages
162913
- assistantMessages: data2.breakdown.messages || 0
162914
- } : data2.breakdown;
162915
- const cardEvent = emitter.emit("card", {
162916
- component: "context-window",
162917
- data: {
162918
- totalTokens: data2.totalTokens,
162919
- maxTokens: data2.maxTokens,
162920
- percentage: data2.percentage,
162921
- breakdown,
162922
- freeSpace: data2.freeSpace,
162923
- contextItems: data2.contextItems || {
162924
- workspaces: 0,
162925
- fragments: 0,
162926
- toolResults: 0
162927
- }
162928
- }
162929
- });
162930
- multiplexer.send(cardEvent);
162931
- }
162932
- if (isAskQuestionPause(toolResult)) {
162933
- pauseDetected = true;
162934
- if (onPauseDetected) {
162935
- await onPauseDetected({
162936
- resumeToken: toolResult.__pause.resumeToken,
162937
- questions: toolResult.__pause.questions,
162938
- context: toolResult.__pause.context
162939
- });
162940
- }
162941
- break;
162942
- }
162943
- if (toolResult?.__parentToolCall === true) {
162944
- const parentToolName = toolResult.toolName;
162945
- const parentToolArgs = toolResult.args;
162946
- const requestId = `parent_${pending.id}_${Date.now()}`;
162947
- streamLogger.info("Parent tool call detected - waiting for parent response", {
162948
- toolName: pending.name,
162949
- parentToolName,
162950
- requestId
162951
- });
162952
- const parentToolEvent = emitter.emit("parent-tool-call", {
162953
- requestId,
162954
- toolName: parentToolName,
162955
- args: parentToolArgs
162956
- });
162957
- multiplexer.send(parentToolEvent);
162958
- try {
162959
- const parentResponse = await waitForParentToolResponse(
162960
- requestId,
162961
- parentToolName,
162962
- parentToolArgs,
162963
- 3e4
162964
- // 30 second timeout
162965
- );
162966
- streamLogger.info("Parent tool response received", {
162967
- requestId,
162968
- parentToolName,
162969
- hasResult: parentResponse !== void 0
162970
- });
162971
- const resultIndex = toolResults.findIndex((r17) => r17.id === pending.id);
162972
- if (resultIndex !== -1) {
162973
- toolResults[resultIndex].result = parentResponse;
162974
- }
162975
- const updatedResultEvent = emitter.emit("tool-result", {
162976
- id: pending.id,
162977
- toolName: pending.name,
162978
- input: args,
162979
- result: parentResponse,
162980
- success: true
162981
- });
162982
- multiplexer.send(updatedResultEvent);
162983
- } catch (parentError) {
162984
- streamLogger.error("Parent tool call failed", {
162985
- requestId,
162986
- parentToolName,
162987
- error: parentError instanceof Error ? parentError.message : String(parentError)
162988
- });
162989
- const errorResult = {
162990
- error: parentError instanceof Error ? parentError.message : String(parentError),
162991
- success: false
162992
- };
162993
- const resultIndex = toolResults.findIndex((r17) => r17.id === pending.id);
162994
- if (resultIndex !== -1) {
162995
- toolResults[resultIndex].result = errorResult;
162996
- toolResults[resultIndex].isError = true;
162997
- }
162998
- const errorEvent = emitter.emit("tool-result", {
162999
- id: pending.id,
163000
- toolName: pending.name,
163001
- input: args,
163002
- result: errorResult,
163003
- success: false,
163004
- error: parentError instanceof Error ? parentError.message : String(parentError)
163005
- });
163006
- multiplexer.send(errorEvent);
163007
- }
163008
- }
163009
- if (emitObservations && success2) {
163010
- const observation = generateObservation2(pending.name, toolResult);
163011
- const observationEvent = emitter.emit("observation", {
163012
- observation,
163013
- toolCallIds: [pending.id]
163014
- });
163015
- multiplexer.send(observationEvent);
163016
- }
163017
- } catch (toolError) {
163018
- console.error(`\u274C Tool execution error for ${pending.name}:`, toolError);
163019
- toolCalls.push({ toolName: pending.name, success: false });
163020
- const errorResult = {
163021
- error: toolError instanceof Error ? toolError.message : String(toolError),
163022
- success: false
163023
- };
163024
- toolResults.push({
163025
- id: pending.id,
163026
- name: pending.name,
163027
- result: errorResult,
163028
- isError: true
163029
- });
163030
- const errorEvent = emitter.emit("tool-result", {
163031
- id: pending.id,
163032
- toolName: pending.name,
163033
- result: null,
163034
- success: false,
163035
- error: toolError instanceof Error ? toolError.message : String(toolError)
163036
- });
163037
- multiplexer.send(errorEvent);
163038
- }
163041
+ toolExecutionPromises.push(
163042
+ executeParsedToolCall(pending.id, pending.name, args)
163043
+ );
163039
163044
  pendingToolCalls.delete(index2);
163040
163045
  } catch {
163041
163046
  }
@@ -163048,6 +163053,13 @@ async function adaptOpenRouterStream(stream, options2) {
163048
163053
  finishReason: finish_reason,
163049
163054
  hasUsage: !!capturedUsage
163050
163055
  });
163056
+ if (finish_reason === "tool_calls" && toolExecutionPromises.length > 0) {
163057
+ streamLogger.debug("Waiting for OpenRouter tool executions", {
163058
+ toolCount: toolExecutionPromises.length
163059
+ });
163060
+ await Promise.allSettled(toolExecutionPromises);
163061
+ toolExecutionPromises.length = 0;
163062
+ }
163051
163063
  if (finish_reason !== "tool_calls" && !summaryEmitted) {
163052
163064
  if (capturedUsage && (capturedUsage.completion_tokens ?? 0) > 0) {
163053
163065
  const usageData = {
@@ -163082,6 +163094,13 @@ async function adaptOpenRouterStream(stream, options2) {
163082
163094
  }
163083
163095
  }
163084
163096
  }
163097
+ if (toolExecutionPromises.length > 0) {
163098
+ streamLogger.debug("Waiting for pending OpenRouter tool executions after stream end", {
163099
+ toolCount: toolExecutionPromises.length
163100
+ });
163101
+ await Promise.allSettled(toolExecutionPromises);
163102
+ toolExecutionPromises.length = 0;
163103
+ }
163085
163104
  if (finishReason && !summaryEmitted && finishReason !== "tool_calls") {
163086
163105
  streamLogger.warn("Stream ended without usage chunk - emitting summary without usage", {
163087
163106
  finishReason
@@ -254228,6 +254247,7 @@ var init_skills3 = __esm({
254228
254247
  // src/adapters/cli/subagent.ts
254229
254248
  import { spawn as spawn5 } from "node:child_process";
254230
254249
  import { basename as basename8 } from "node:path";
254250
+ import { randomUUID as randomUUID6 } from "node:crypto";
254231
254251
  function currentSubagentDepth() {
254232
254252
  const d21 = Number(process.env.USABLE_SUBAGENT_DEPTH ?? 0);
254233
254253
  return Number.isFinite(d21) && d21 > 0 ? Math.floor(d21) : 0;
@@ -254312,11 +254332,73 @@ async function runCliSubagent(input) {
254312
254332
  }
254313
254333
  return { ok: true, text: res.out.trim() };
254314
254334
  }
254335
+ function publicTask(task) {
254336
+ return {
254337
+ taskId: task.taskId,
254338
+ status: task.status,
254339
+ prompt: task.prompt,
254340
+ model: task.model,
254341
+ cwd: task.cwd,
254342
+ startedAt: task.startedAt,
254343
+ completedAt: task.completedAt,
254344
+ ...task.text ? { text: task.text } : {},
254345
+ ...task.error ? { error: task.error } : {}
254346
+ };
254347
+ }
254348
+ function startCliSubagentTask(input) {
254349
+ const taskId = input.taskId ?? randomUUID6();
254350
+ const abortController = new AbortController();
254351
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
254352
+ const run2 = input._run ?? runCliSubagent;
254353
+ const onParentAbort = () => abortController.abort();
254354
+ input.parentAbortSignal?.addEventListener("abort", onParentAbort, { once: true });
254355
+ const task = {
254356
+ taskId,
254357
+ prompt: input.prompt,
254358
+ model: input.model,
254359
+ cwd: input.cwd,
254360
+ status: "running",
254361
+ startedAt,
254362
+ abortController,
254363
+ promise: Promise.resolve(void 0)
254364
+ };
254365
+ task.promise = (async () => {
254366
+ try {
254367
+ const res = await run2({
254368
+ prompt: input.prompt,
254369
+ model: input.model,
254370
+ cwd: input.cwd,
254371
+ abortSignal: abortController.signal,
254372
+ timeoutMs: input.timeoutMs
254373
+ });
254374
+ task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
254375
+ if (abortController.signal.aborted) {
254376
+ task.status = "cancelled";
254377
+ task.error = "subagent cancelled";
254378
+ } else if (res.ok) {
254379
+ task.status = "completed";
254380
+ task.text = res.text;
254381
+ } else {
254382
+ task.status = "failed";
254383
+ task.text = res.text;
254384
+ task.error = res.error ?? "subagent failed";
254385
+ }
254386
+ } catch (err) {
254387
+ task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
254388
+ task.status = abortController.signal.aborted ? "cancelled" : "failed";
254389
+ task.error = err instanceof Error ? err.message : String(err);
254390
+ } finally {
254391
+ input.parentAbortSignal?.removeEventListener("abort", onParentAbort);
254392
+ }
254393
+ return task;
254394
+ })();
254395
+ cliSubagentTasks.set(taskId, task);
254396
+ return task;
254397
+ }
254315
254398
  function buildCliSubagentTool(opts) {
254316
- const run2 = opts._run ?? runCliSubagent;
254317
254399
  return {
254318
254400
  spawn_subagent: {
254319
- description: SPAWN_DESCRIPTION,
254401
+ description: ASYNC_SPAWN_DESCRIPTION,
254320
254402
  parameters: {
254321
254403
  type: "object",
254322
254404
  properties: {
@@ -254335,31 +254417,89 @@ function buildCliSubagentTool(opts) {
254335
254417
  const prompt = String(args.prompt ?? "").trim();
254336
254418
  if (!prompt) return { error: "prompt is required" };
254337
254419
  const model = typeof args.model === "string" && args.model.trim() ? args.model.trim() : opts.model;
254338
- const res = await run2({
254420
+ const task = startCliSubagentTask({
254421
+ taskId: typeof args.taskId === "string" && args.taskId.trim() ? args.taskId.trim() : void 0,
254339
254422
  prompt,
254340
254423
  model,
254341
254424
  cwd: process.cwd(),
254342
- abortSignal: opts.abortSignal
254425
+ parentAbortSignal: opts.abortSignal,
254426
+ _run: opts._run
254343
254427
  });
254344
- if (!res.ok) {
254345
- return { error: res.error ?? "subagent failed", ...res.text ? { partial: res.text } : {} };
254428
+ return publicTask(task);
254429
+ }
254430
+ },
254431
+ list_subagents: {
254432
+ description: "List background CLI subagents in this process. Use this before awaiting when you need task ids or status.",
254433
+ parameters: {
254434
+ type: "object",
254435
+ properties: {
254436
+ includeFinished: {
254437
+ type: "boolean",
254438
+ description: "Include completed, failed, and cancelled tasks. Defaults to true."
254439
+ }
254440
+ }
254441
+ },
254442
+ execute: async (args) => {
254443
+ const includeFinished = args.includeFinished !== false;
254444
+ const tasks = [...cliSubagentTasks.values()].filter(
254445
+ (task) => includeFinished || task.status === "running"
254446
+ );
254447
+ return { tasks: tasks.map(publicTask) };
254448
+ }
254449
+ },
254450
+ await_subagents: {
254451
+ description: "Wait for one or more background CLI subagents and collect their results. Omit taskIds to wait for all running tasks.",
254452
+ parameters: {
254453
+ type: "object",
254454
+ properties: {
254455
+ taskIds: {
254456
+ type: "array",
254457
+ items: { type: "string" },
254458
+ description: "Specific task ids to wait for. Defaults to all running tasks."
254459
+ },
254460
+ timeout: {
254461
+ type: "number",
254462
+ description: "Maximum seconds to wait. Defaults to 300."
254463
+ }
254346
254464
  }
254347
- return { result: res.text };
254465
+ },
254466
+ execute: async (args) => {
254467
+ const ids = Array.isArray(args.taskIds) ? args.taskIds.filter((id) => typeof id === "string" && id.length > 0) : [...cliSubagentTasks.values()].filter((task) => task.status === "running").map((task) => task.taskId);
254468
+ const tasks = ids.map((id) => cliSubagentTasks.get(id));
254469
+ const missing = ids.filter((id, index2) => !tasks[index2]);
254470
+ const existing = tasks.filter((task) => !!task);
254471
+ if (missing.length > 0) return { error: `Unknown subagent task id(s): ${missing.join(", ")}` };
254472
+ if (existing.length === 0) return { results: [] };
254473
+ const timeoutS = typeof args.timeout === "number" && Number.isFinite(args.timeout) ? Math.max(1, args.timeout) : 300;
254474
+ const timeout = new Promise(
254475
+ (resolve8) => setTimeout(() => resolve8("timeout"), timeoutS * 1e3)
254476
+ );
254477
+ const settled = await Promise.race([
254478
+ Promise.all(existing.map((task) => task.promise)),
254479
+ timeout
254480
+ ]);
254481
+ if (settled === "timeout") {
254482
+ return {
254483
+ error: `Timed out waiting for subagents after ${timeoutS}s`,
254484
+ pending: existing.filter((task) => task.status === "running").map(publicTask),
254485
+ results: existing.filter((task) => task.status !== "running").map(publicTask)
254486
+ };
254487
+ }
254488
+ return { results: settled.map(publicTask) };
254348
254489
  }
254349
254490
  }
254350
254491
  };
254351
254492
  }
254352
- var SUBAGENT_TIMEOUT_MS, SUBAGENT_MAX_DEPTH, SPAWN_DESCRIPTION;
254493
+ var SUBAGENT_TIMEOUT_MS, SUBAGENT_MAX_DEPTH, cliSubagentTasks, ASYNC_SPAWN_DESCRIPTION;
254353
254494
  var init_subagent = __esm({
254354
254495
  "src/adapters/cli/subagent.ts"() {
254355
254496
  "use strict";
254356
254497
  SUBAGENT_TIMEOUT_MS = 5 * 60 * 1e3;
254357
254498
  SUBAGENT_MAX_DEPTH = 2;
254358
- SPAWN_DESCRIPTION = `Spawn a subagent to handle a self-contained task and return its findings directly.
254499
+ cliSubagentTasks = /* @__PURE__ */ new Map();
254500
+ ASYNC_SPAWN_DESCRIPTION = `Start a background subagent for a self-contained task and return immediately.
254359
254501
 
254360
- The subagent is a fresh \`usable-chat\` agent running in YOUR current working directory, with the SAME model/provider and the same real filesystem + shell + MCP tools + skills. Use it to fan out isolated work \u2014 explore a folder, search, summarize a large file, draft something \u2014 without cluttering your own context. It returns its answer inline (no separate await step).
254361
-
254362
- Give it a complete, standalone \`prompt\` (it does not see your conversation). Keep tasks focused; it can read/run things on the user's machine.`;
254502
+ The task runs in this CLI process. Use list_subagents to inspect status and await_subagents to collect results. The subagent is a fresh \`usable-chat\` agent running in the current working directory, with the same provider/config and local tools.`;
254363
254503
  }
254364
254504
  });
254365
254505
 
@@ -269627,7 +269767,7 @@ var init_tui_markdown = __esm({
269627
269767
  // src/cli/warp-agent.ts
269628
269768
  import { openSync, writeSync } from "node:fs";
269629
269769
  import { basename as basename9 } from "node:path";
269630
- import { randomUUID as randomUUID6 } from "node:crypto";
269770
+ import { randomUUID as randomUUID7 } from "node:crypto";
269631
269771
  function isWarpAgentHost() {
269632
269772
  return !!process.env.WARP_CLI_AGENT_PROTOCOL_VERSION && process.platform !== "win32";
269633
269773
  }
@@ -269680,7 +269820,7 @@ var init_warp_agent = __esm({
269680
269820
  "use strict";
269681
269821
  WARP_AGENT_SLUG = "usable-chat";
269682
269822
  APP_NAME = "usable-chat";
269683
- sessionId = randomUUID6();
269823
+ sessionId = randomUUID7();
269684
269824
  }
269685
269825
  });
269686
269826
 
@@ -269793,6 +269933,7 @@ async function launchTui(options2) {
269793
269933
  const ui2 = new TUI(terminal);
269794
269934
  const editor = new Editor(ui2, editorTheme, { paddingX: 1 });
269795
269935
  editor.focused = true;
269936
+ const bangTools = createLocalTools();
269796
269937
  let items = [...initialHistory];
269797
269938
  let compactedPrefix = [];
269798
269939
  let busy = false;
@@ -269803,6 +269944,7 @@ async function launchTui(options2) {
269803
269944
  let exitArmed = false;
269804
269945
  let toolsExpanded = false;
269805
269946
  let abort = null;
269947
+ let lastBangCommand = null;
269806
269948
  const extStatus = /* @__PURE__ */ new Map();
269807
269949
  let picker = null;
269808
269950
  const rebuildPicker = () => {
@@ -270056,6 +270198,56 @@ Edit it, then /verify-extension ${arg.trim()} to check it loads, /trust (project
270056
270198
  async function submit(raw) {
270057
270199
  const value = raw.trim();
270058
270200
  if (!value || busy) return;
270201
+ if (value === "!!" || value.startsWith("!") && !value.startsWith("/")) {
270202
+ const command = value === "!!" ? lastBangCommand : value.slice(1).trim();
270203
+ if (!command) {
270204
+ sys(value === "!!" ? "No previous shell command." : "Usage: ! <command>", "error");
270205
+ return;
270206
+ }
270207
+ lastBangCommand = command;
270208
+ busy = true;
270209
+ push({ kind: "user", text: `! ${command}` });
270210
+ const toolId = `bang-${Date.now()}`;
270211
+ push({
270212
+ kind: "tool",
270213
+ id: toolId,
270214
+ name: "bash",
270215
+ args: short(JSON.stringify({ command })),
270216
+ argsFull: prettyJson({ command }),
270217
+ status: "running"
270218
+ });
270219
+ ui2.requestRender();
270220
+ try {
270221
+ const result = await bangTools.bash.execute({ command });
270222
+ const error41 = result && typeof result === "object" && "error" in result ? String(result.error) : void 0;
270223
+ items = items.map(
270224
+ (it7) => it7.kind === "tool" && it7.id === toolId ? {
270225
+ ...it7,
270226
+ status: error41 ? "error" : "done",
270227
+ result: error41 ? void 0 : previewValue(result),
270228
+ error: error41
270229
+ } : it7
270230
+ );
270231
+ } catch (err) {
270232
+ items = items.map(
270233
+ (it7) => it7.kind === "tool" && it7.id === toolId ? {
270234
+ ...it7,
270235
+ status: "error",
270236
+ error: err instanceof Error ? err.message : String(err)
270237
+ } : it7
270238
+ );
270239
+ } finally {
270240
+ busy = false;
270241
+ ui2.requestRender();
270242
+ if (autoSubmit) {
270243
+ setTimeout(() => {
270244
+ teardown();
270245
+ exit(0);
270246
+ }, 25);
270247
+ }
270248
+ }
270249
+ return;
270250
+ }
270059
270251
  if (value.startsWith("/")) {
270060
270252
  const [cmd, ...rest] = value.slice(1).split(/\s+/);
270061
270253
  await runSlash(cmd.toLowerCase(), rest.join(" "));
@@ -270333,6 +270525,7 @@ var init_tui2 = __esm({
270333
270525
  "use strict";
270334
270526
  init_dist8();
270335
270527
  init_tui_select();
270528
+ init_tools();
270336
270529
  init_tui_markdown();
270337
270530
  init_warp_agent();
270338
270531
  BANNER_LINES = [
@@ -270983,7 +271176,7 @@ init_tui_select();
270983
271176
  init_model_registry();
270984
271177
 
270985
271178
  // package.json
270986
- var version2 = "1.153.0";
271179
+ var version2 = "1.154.0";
270987
271180
 
270988
271181
  // src/adapters/cli/model-catalog.ts
270989
271182
  init_codex_auth();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usabledev/usable-chat",
3
- "version": "1.153.0",
3
+ "version": "1.154.0",
4
4
  "description": "usable-chat — terminal harness for usable-chat (headless + TUI)",
5
5
  "type": "module",
6
6
  "bin": {