@cargo-ai/cli 1.0.86 → 1.0.87

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.
@@ -25,12 +25,26 @@ export type LocalToolApproval = {
25
25
  /** A tool starting or finishing, so the caller can show what is happening. */
26
26
  export type LocalToolActivity = {
27
27
  kind: "start";
28
+ toolCallId: string;
28
29
  toolName: string;
29
30
  summary: string;
30
31
  } | {
31
32
  kind: "end";
33
+ toolCallId: string;
32
34
  toolName: string;
33
35
  isError: boolean;
36
+ }
37
+ /**
38
+ * Announced, then never run — a tool whose input stopped mid-stream, one
39
+ * that was refused, or a sibling whose approval request the harness lost.
40
+ * None of them report a result, so `end` never comes: a caller tracking
41
+ * what is live has to be told, or it shows that tool for the rest of the
42
+ * turn. `retractAbandonedCalls` decides which calls qualify and when.
43
+ */
44
+ | {
45
+ kind: "dropped";
46
+ toolCallId: string;
47
+ toolName: string;
34
48
  };
35
49
  export type LocalStreamHandlers = {
36
50
  onTextDelta?: (delta: string) => void;
@@ -49,6 +63,12 @@ export type LocalStreamHandlers = {
49
63
  export type LocalStreamOptions = LocalStreamHandlers & {
50
64
  /** Aborts the turn; the harness interrupts Claude Code gracefully. */
51
65
  abortSignal?: AbortSignal;
66
+ /**
67
+ * Fired when the stall watchdog gives up, before the turn is rejected.
68
+ * Should abort `abortSignal` so Claude Code stops rather than being torn
69
+ * down out from under a still-open bridge.
70
+ */
71
+ onStall?: () => void;
52
72
  };
53
73
  /**
54
74
  * What one turn spent upstream, summed over the streams a suspended turn is
@@ -197,6 +217,11 @@ export declare function runPromptTurn(input: {
197
217
  unanswered?: LocalToolApproval[];
198
218
  /** Overridden by tests; `Infinity` waits forever. */
199
219
  stallTimeoutMs?: number;
220
+ /**
221
+ * Called when the stall watchdog fires, before the turn is rejected — abort
222
+ * the harness stream so Claude Code does not keep running after we give up.
223
+ */
224
+ onStall?: () => void;
200
225
  }): Promise<string>;
201
226
  /**
202
227
  * The previous turn could not be wound down, so this session cannot take
@@ -209,6 +234,20 @@ export declare function runPromptTurn(input: {
209
234
  export declare class HarnessTurnStuck extends Error {
210
235
  constructor();
211
236
  }
237
+ /**
238
+ * The turn produced nothing for `TURN_STALL_TIMEOUT_MS` while no tool was in
239
+ * flight. Common causes: a step that asked to run several confirmable tools at
240
+ * once (the harness only surfaces one approval), or the Claude Code bridge
241
+ * going silent. Recoverable — replace the session and ask again.
242
+ */
243
+ export declare class HarnessTurnStalled extends Error {
244
+ constructor(timeoutMs: number);
245
+ }
246
+ /**
247
+ * The Claude Code bridge dropped mid-turn. Same recovery as a stall: the
248
+ * process behind the session is gone, so the next question needs a new one.
249
+ */
250
+ export declare function isBridgeClosedError(error: unknown): boolean;
212
251
  /**
213
252
  * Wait for `work`, but give up after `timeoutMs` rather than blocking a
214
253
  * teardown forever. Resolves either way; the caller has already decided that
@@ -1 +1 @@
1
- {"version":3,"file":"localHarness.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/localHarness.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"localHarness.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/localHarness.ts"],"names":[],"mappings":"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"}
@@ -395,6 +395,11 @@ export async function createLocalHarness(input) {
395
395
  handlers: options,
396
396
  accumulator,
397
397
  unanswered,
398
+ // Interrupt Claude Code through the turn's abort signal so the
399
+ // bridge stops cleanly. Without this the host walks away from the
400
+ // stream and teardown looks like "bridge closed before the turn
401
+ // finished" rather than an intentional stop.
402
+ onStall: options === undefined ? undefined : options.onStall,
398
403
  });
399
404
  }
400
405
  catch (error) {
@@ -524,7 +529,7 @@ export async function runPromptTurn(input) {
524
529
  const toolCalls = new Map();
525
530
  const turnState = {
526
531
  toolRanSinceText: false,
527
- inFlightToolCallIds: new Set(),
532
+ announced: [],
528
533
  };
529
534
  let stream = await input.driver.start();
530
535
  let round = 0;
@@ -537,6 +542,7 @@ export async function runPromptTurn(input) {
537
542
  usage: accumulator.usage,
538
543
  turnState,
539
544
  stallTimeoutMs,
545
+ onStall: input.onStall,
540
546
  });
541
547
  if (approvals.length === 0) {
542
548
  break;
@@ -549,7 +555,6 @@ export async function runPromptTurn(input) {
549
555
  throw new Error(`Local Claude Code asked for tool approval ${String(MAX_APPROVAL_ROUNDS)} times in one turn without finishing. Stopping the turn.`);
550
556
  }
551
557
  const continuations = [];
552
- const approvedToolCallIds = [];
553
558
  for (const approval of approvals) {
554
559
  // Refused before the user is asked: not their call to make, and a
555
560
  // prompt for it could not say what it was granting.
@@ -561,8 +566,13 @@ export async function runPromptTurn(input) {
561
566
  : onApproval === undefined
562
567
  ? false
563
568
  : await onApproval(approval);
564
- if (approved === true) {
565
- approvedToolCallIds.push(approval.toolCallId);
569
+ // Recorded against the call so the retraction pass below knows it is
570
+ // settled: an approved one is running and must be left alone, a refused
571
+ // one is certainly not and needs no reprieve to find out. Absent when
572
+ // its result already arrived, which takes it off the list.
573
+ const announced = turnState.announced.find((call) => call.toolCallId === approval.toolCallId);
574
+ if (announced !== undefined) {
575
+ announced.status = approved === true ? "approved" : "refused";
566
576
  }
567
577
  // Answered, so the drain on a later turn must not deny it again.
568
578
  if (input.unanswered !== undefined) {
@@ -588,15 +598,7 @@ export async function runPromptTurn(input) {
588
598
  },
589
599
  });
590
600
  }
591
- // Only the tools just approved are actually going to run. The harness
592
- // surfaces one approval per stream, so a sibling the model announced in
593
- // the same stream was never asked about and will never report a result —
594
- // left in the set it would tell the stall watchdog a tool is in flight
595
- // for the rest of the turn, turning a silent stream into a hang.
596
- turnState.inFlightToolCallIds.clear();
597
- for (const toolCallId of approvedToolCallIds) {
598
- turnState.inFlightToolCallIds.add(toolCallId);
599
- }
601
+ retractAbandonedCalls(turnState.announced, handlers.onActivity);
600
602
  stream = await input.driver.continueWithApprovals(continuations);
601
603
  }
602
604
  const streamed = assistantChunks.join("");
@@ -612,6 +614,53 @@ export async function runPromptTurn(input) {
612
614
  }
613
615
  return fallback;
614
616
  }
617
+ /**
618
+ * Retract the calls the caller was told about and will never hear the end of,
619
+ * at the boundary where the answers to this round's approvals are known.
620
+ *
621
+ * Left on screen instead, an abandoned call outlives the work: no result is
622
+ * coming, so no `end` event is either, and the caller keeps showing a tool
623
+ * that stopped existing.
624
+ *
625
+ * `"approved"` is kept, and `"running"` is the one case that cannot be judged
626
+ * yet — the reason this is not a plain sweep. It is either the sibling whose
627
+ * approval request the harness lost (it surfaces one per stream) or a tool
628
+ * Claude Code ran without asking, whose result has simply not arrived. Both
629
+ * look identical here, so it gets one more stream to prove itself: a live one
630
+ * reports its result and leaves this list on its own.
631
+ *
632
+ * Everything else is certain and retracted — `"streaming"` never finished
633
+ * arriving, `"refused"` was told no, and `"abandoning"` already had its
634
+ * stream and stayed silent. Refusals matter especially: a tool
635
+ * `isAlwaysDenied` refuses never reaches `onApproval`, so this is the caller's
636
+ * only notice.
637
+ *
638
+ * The reprieve is for the caller's benefit only — `"abandoning"` is not an
639
+ * executing status, so the stall watchdog is armed throughout it. Extending
640
+ * the reprieve to the watchdog would deadlock the very hang it exists to
641
+ * prevent: a dropped sibling would hold the timer off, the stream would stay
642
+ * silent, and the next boundary, the thing that resolves the ambiguity, would
643
+ * never arrive.
644
+ */
645
+ function retractAbandonedCalls(announced, onActivity) {
646
+ for (const call of [...announced]) {
647
+ if (call.status === "approved") {
648
+ continue;
649
+ }
650
+ if (call.status === "running") {
651
+ call.status = "abandoning";
652
+ continue;
653
+ }
654
+ announced.splice(announced.indexOf(call), 1);
655
+ if (onActivity !== undefined) {
656
+ onActivity({
657
+ kind: "dropped",
658
+ toolCallId: call.toolCallId,
659
+ toolName: call.toolName,
660
+ });
661
+ }
662
+ }
663
+ }
615
664
  /**
616
665
  * How long a turn may produce nothing at all while no tool is running.
617
666
  *
@@ -644,6 +693,27 @@ export class HarnessTurnStuck extends Error {
644
693
  this.name = "HarnessTurnStuck";
645
694
  }
646
695
  }
696
+ /**
697
+ * The turn produced nothing for `TURN_STALL_TIMEOUT_MS` while no tool was in
698
+ * flight. Common causes: a step that asked to run several confirmable tools at
699
+ * once (the harness only surfaces one approval), or the Claude Code bridge
700
+ * going silent. Recoverable — replace the session and ask again.
701
+ */
702
+ export class HarnessTurnStalled extends Error {
703
+ constructor(timeoutMs) {
704
+ const seconds = String(Math.round(timeoutMs / 1000));
705
+ super(`Local Claude Code went quiet for ${seconds}s with no tool running. Often this is a step that asked to run several confirmable tools at once (only the first can be answered), or the coding session itself stalled.`);
706
+ this.name = "HarnessTurnStalled";
707
+ }
708
+ }
709
+ /**
710
+ * The Claude Code bridge dropped mid-turn. Same recovery as a stall: the
711
+ * process behind the session is gone, so the next question needs a new one.
712
+ */
713
+ export function isBridgeClosedError(error) {
714
+ return (error instanceof Error &&
715
+ error.message.includes("bridge closed before the turn finished") === true);
716
+ }
647
717
  /**
648
718
  * Fail a turn that has gone silent with no tool running, instead of hanging.
649
719
  *
@@ -657,7 +727,7 @@ export class HarnessTurnStuck extends Error {
657
727
  * The timer is armed only while no tool is in flight — a ten-minute test run
658
728
  * is silent too, and must not be cut off.
659
729
  */
660
- async function* withStallTimeout(stream, timeoutMs, isToolInFlight) {
730
+ async function* withStallTimeout(stream, timeoutMs, isToolInFlight, onStall) {
661
731
  if (Number.isFinite(timeoutMs) === false) {
662
732
  for await (const part of stream) {
663
733
  yield part;
@@ -669,7 +739,7 @@ async function* withStallTimeout(stream, timeoutMs, isToolInFlight) {
669
739
  const next = iterator.next();
670
740
  const result = await (isToolInFlight() === true
671
741
  ? next
672
- : rejectIfIdle(next, timeoutMs));
742
+ : rejectIfIdle(next, timeoutMs, onStall));
673
743
  if (result.done === true) {
674
744
  return;
675
745
  }
@@ -699,14 +769,22 @@ export async function settleWithin(work, timeoutMs) {
699
769
  }
700
770
  }
701
771
  }
702
- async function rejectIfIdle(next, timeoutMs) {
772
+ async function rejectIfIdle(next, timeoutMs, onStall) {
703
773
  let timer;
704
774
  try {
705
775
  return await Promise.race([
706
776
  next,
707
777
  new Promise((_resolve, reject) => {
708
778
  timer = setTimeout(() => {
709
- reject(new Error(`Local Claude Code went quiet for ${String(Math.round(timeoutMs / 1000))}s with no tool running. A single step that asks to run several tools needing confirmation ends up here: only the first can be answered, and the rest leave the turn waiting. Ask again, or in smaller steps.`));
779
+ // Reject first so Promise.race settles on HarnessTurnStalled, not
780
+ // on an AbortError the interrupt below may race onto `next`.
781
+ reject(new HarnessTurnStalled(timeoutMs));
782
+ // Then stop the bridge so teardown is an intentional abort, not a
783
+ // mysterious "bridge closed before the turn finished" after we
784
+ // have already walked away from the stream.
785
+ if (onStall !== undefined) {
786
+ onStall();
787
+ }
710
788
  }, timeoutMs);
711
789
  }),
712
790
  ]);
@@ -794,7 +872,7 @@ async function consumeTurn(input) {
794
872
  const approvals = [];
795
873
  const { handlers } = input;
796
874
  const pendingFiles = new Map();
797
- const parts = withStallTimeout(input.stream.fullStream, input.stallTimeoutMs, () => input.turnState.inFlightToolCallIds.size > 0);
875
+ const parts = withStallTimeout(input.stream.fullStream, input.stallTimeoutMs, () => input.turnState.announced.some((call) => call.status === "running" || call.status === "approved"), input.onStall);
798
876
  for await (const rawPart of parts) {
799
877
  if (typeof rawPart !== "object" || rawPart === null) {
800
878
  continue;
@@ -808,6 +886,43 @@ async function consumeTurn(input) {
808
886
  ? error
809
887
  : "Local Claude Code turn failed.");
810
888
  }
889
+ // Claude Code streams the tool name before the full call is assembled.
890
+ // Surface that early so the spinner is not stuck on "Asking…" while the
891
+ // input is still arriving — and so a later `tool-call` can fill in the
892
+ // summary without looking like a second tool.
893
+ if (part["type"] === "tool-input-start") {
894
+ const toolCallId = part["id"];
895
+ const toolName = part["toolName"];
896
+ if (typeof toolCallId === "string" && typeof toolName === "string") {
897
+ if (input.toolCalls.has(toolCallId) === false) {
898
+ input.toolCalls.set(toolCallId, {
899
+ toolName,
900
+ input: undefined,
901
+ });
902
+ input.turnState.toolRanSinceText = true;
903
+ // Announced, because the spinner is about to show this name and is
904
+ // owed a retraction if the call never happens — but `"streaming"`,
905
+ // not executing. The tool is not running yet, its input is still
906
+ // being generated, and the deltas that carry it are output the
907
+ // watchdog is entitled to expect; standing it down here would turn a
908
+ // bridge that dies mid-input into a hang.
909
+ input.turnState.announced.push({
910
+ toolCallId,
911
+ toolName,
912
+ status: "streaming",
913
+ });
914
+ if (handlers.onActivity !== undefined) {
915
+ handlers.onActivity({
916
+ kind: "start",
917
+ toolCallId,
918
+ toolName,
919
+ summary: "",
920
+ });
921
+ }
922
+ }
923
+ }
924
+ continue;
925
+ }
811
926
  if (part["type"] === "tool-call") {
812
927
  const toolCallId = part["toolCallId"];
813
928
  const toolName = part["toolName"];
@@ -817,10 +932,26 @@ async function consumeTurn(input) {
817
932
  input: part["input"],
818
933
  });
819
934
  input.turnState.toolRanSinceText = true;
820
- input.turnState.inFlightToolCallIds.add(toolCallId);
935
+ // The full call is assembled, so something is executing it — that is
936
+ // what stands the stall watchdog down, and it deliberately waits until
937
+ // here rather than firing at `tool-input-start`.
938
+ const announced = input.turnState.announced.find((call) => call.toolCallId === toolCallId);
939
+ if (announced === undefined) {
940
+ input.turnState.announced.push({
941
+ toolCallId,
942
+ toolName,
943
+ status: "running",
944
+ });
945
+ }
946
+ else {
947
+ announced.status = "running";
948
+ }
949
+ // Re-emitted when `tool-input-start` already announced the name, so
950
+ // the spinner can pick up the input summary once it is known.
821
951
  if (handlers.onActivity !== undefined) {
822
952
  handlers.onActivity({
823
953
  kind: "start",
954
+ toolCallId,
824
955
  toolName,
825
956
  summary: summarizeToolInput(part["input"]),
826
957
  });
@@ -842,18 +973,28 @@ async function consumeTurn(input) {
842
973
  ? input.toolCalls.get(toolCallId)
843
974
  : undefined;
844
975
  const isError = part["type"] === "tool-error";
845
- if (handlers.onActivity !== undefined) {
976
+ // A preliminary result is progress from a tool that is still running,
977
+ // so it must not clear the spinner or tell the stall watchdog the tool
978
+ // is done.
979
+ const preliminary = part["preliminary"] === true;
980
+ if (preliminary === false &&
981
+ typeof toolCallId === "string" &&
982
+ handlers.onActivity !== undefined) {
846
983
  handlers.onActivity({
847
984
  kind: "end",
985
+ toolCallId,
848
986
  toolName: recorded === undefined ? "tool" : recorded.toolName,
849
987
  isError,
850
988
  });
851
989
  }
852
- if (typeof toolCallId === "string") {
853
- // A preliminary result is progress from a tool that is still running,
854
- // so it must not tell the stall watchdog the tool is done.
855
- if (part["preliminary"] !== true) {
856
- input.turnState.inFlightToolCallIds.delete(toolCallId);
990
+ if (typeof toolCallId === "string" && preliminary === false) {
991
+ // Off the list on the way out. The `end` above is the caller's
992
+ // retraction, so a later approval round must not announce a second
993
+ // one — and leaving under its own steam is how a tool that ran unasked
994
+ // proves it was alive rather than abandoned.
995
+ const at = input.turnState.announced.findIndex((call) => call.toolCallId === toolCallId);
996
+ if (at !== -1) {
997
+ input.turnState.announced.splice(at, 1);
857
998
  }
858
999
  const touched = pendingFiles.get(toolCallId);
859
1000
  pendingFiles.delete(toolCallId);
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/session.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,eAAe,CAAC;AAIxC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AA0BxC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,GAAG,CAAC;IACT,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,KAAK,WAAW,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAoB1D;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAKT;AAiBD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,SAAS,GAAG,WAAW,CAErE;AA8ED;;;;;;;;;GASG;AACH;;;;GAIG;AACH,KAAK,aAAa,GACd;IAAE,OAAO,EAAE,SAAS,CAAA;CAAE,GACtB;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GACnB;IAAE,OAAO,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/C;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,CAAC,CAkBxB;AAyCD,wBAAsB,aAAa,CACjC,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC,CAqBlB;AAsSD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ7D;AAkGD;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,SAAS,GAAG,MAAM,CAAC,CA0b7B;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE;IACvC,GAAG,EAAE,GAAG,CAAC;IACT,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,OAAO,CAAC,IAAI,CAAC,CA8HhB"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/session.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,eAAe,CAAC;AAIxC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AA4BxC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,GAAG,CAAC;IACT,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,KAAK,WAAW,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAoB1D;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAKT;AAiBD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,SAAS,GAAG,WAAW,CAErE;AA8ED;;;;;;;;;GASG;AACH;;;;GAIG;AACH,KAAK,aAAa,GACd;IAAE,OAAO,EAAE,SAAS,CAAA;CAAE,GACtB;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GACnB;IAAE,OAAO,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/C;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,CAAC,CAkBxB;AAyCD,wBAAsB,aAAa,CACjC,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC,CAqBlB;AA2SD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ7D;AAkGD;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,SAAS,GAAG,MAAM,CAAC,CAygB7B;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE;IACvC,GAAG,EAAE,GAAG,CAAC;IACT,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,OAAO,CAAC,IAAI,CAAC,CA8HhB"}
@@ -6,7 +6,7 @@ import { getConfig } from "../../config.js";
6
6
  import { openPromptInput } from "../../utils/prompt.js";
7
7
  import { colors, ExitCodes, failWith, handleApiCall, outputJson, pollUntilFinished, startSpinner, } from "../runHandler.js";
8
8
  import { loadChatHistoryPreamble } from "./history.js";
9
- import { createLocalHarness, HarnessTurnStuck, } from "./localHarness.js";
9
+ import { createLocalHarness, HarnessTurnStalled, HarnessTurnStuck, isBridgeClosedError, } from "./localHarness.js";
10
10
  import { streamMessageTextDeltas } from "./messageStream.js";
11
11
  import { saveAskState } from "./persist.js";
12
12
  import { readAskLine } from "./prompt.js";
@@ -374,6 +374,9 @@ function beginCancellableTurn(onCancel) {
374
374
  return {
375
375
  signal: controller.signal,
376
376
  wasCancelled: () => state.cancelled,
377
+ abort: () => {
378
+ controller.abort();
379
+ },
377
380
  release: () => {
378
381
  process.removeListener("SIGINT", handleInterrupt);
379
382
  },
@@ -665,6 +668,9 @@ export async function runAskSession(options) {
665
668
  let saidSomething = false;
666
669
  const touchedFiles = new Set();
667
670
  const notices = [];
671
+ // Reads and Cargo MCP tools are batched; the first one to finish must
672
+ // not wipe the spinner back to "Asking…" while siblings still run.
673
+ const activeTools = [];
668
674
  const stopSpinner = () => {
669
675
  if (spinner !== undefined) {
670
676
  spinner.stop();
@@ -678,6 +684,36 @@ export async function runAskSession(options) {
678
684
  }
679
685
  spinner.update(label);
680
686
  };
687
+ // The most recent tool labels the spinner. Nothing running means the
688
+ // turn is thinking rather than working.
689
+ const showActiveToolSpinner = () => {
690
+ const current = activeTools[activeTools.length - 1];
691
+ if (current === undefined) {
692
+ showSpinner("Asking…");
693
+ return;
694
+ }
695
+ showSpinner(describeToolActivity(current.toolName, current.summary));
696
+ };
697
+ // A tool is announced twice — once when its name arrives, again with a
698
+ // summary once its input is known — so the second has to overwrite the
699
+ // first rather than stack a duplicate label.
700
+ const rememberTool = (tool) => {
701
+ const at = activeTools.findIndex((active) => active.toolCallId === tool.toolCallId);
702
+ if (at === -1) {
703
+ activeTools.push(tool);
704
+ }
705
+ else {
706
+ activeTools[at] = tool;
707
+ }
708
+ showActiveToolSpinner();
709
+ };
710
+ const forgetTool = (toolCallId) => {
711
+ const at = activeTools.findIndex((active) => active.toolCallId === toolCallId);
712
+ if (at !== -1) {
713
+ activeTools.splice(at, 1);
714
+ }
715
+ showActiveToolSpinner();
716
+ };
681
717
  // Registered before the spinner so our SIGINT handler runs first and
682
718
  // clack's spinner is already stopped when its own handler fires —
683
719
  // otherwise Ctrl-C prints its cancel line under ours.
@@ -686,6 +722,9 @@ export async function runAskSession(options) {
686
722
  try {
687
723
  await local.streamPrompt(question, {
688
724
  abortSignal: cancellation.signal,
725
+ onStall: () => {
726
+ cancellation.abort();
727
+ },
689
728
  onTextDelta: (delta) => {
690
729
  if (live === undefined) {
691
730
  stopSpinner();
@@ -699,15 +738,23 @@ export async function runAskSession(options) {
699
738
  // not an unexplained pause. Claude Code narrates before it acts,
700
739
  // so a tool usually starts with live text on screen: close that
701
740
  // block first, and the next delta opens a new one.
702
- if (activity.kind === "end") {
703
- showSpinner("Asking…");
741
+ //
742
+ // "dropped" is a tool the harness announced and then abandoned,
743
+ // so it has no result coming. Same handling as a finish: forget
744
+ // it, or the label outlives the work.
745
+ if (activity.kind === "end" || activity.kind === "dropped") {
746
+ forgetTool(activity.toolCallId);
704
747
  return;
705
748
  }
706
749
  if (live !== undefined) {
707
750
  endAssistantTurn(live);
708
751
  live = undefined;
709
752
  }
710
- showSpinner(describeToolActivity(activity.toolName, activity.summary));
753
+ rememberTool({
754
+ toolCallId: activity.toolCallId,
755
+ toolName: activity.toolName,
756
+ summary: activity.summary,
757
+ });
711
758
  },
712
759
  onFileTouched: (path) => {
713
760
  touchedFiles.add(path);
@@ -728,8 +775,18 @@ export async function runAskSession(options) {
728
775
  if (decision === "always") {
729
776
  approveEveryTool = true;
730
777
  }
731
- showSpinner("Asking…");
732
- return decision !== "no";
778
+ if (decision === "no") {
779
+ forgetTool(approval.toolCallId);
780
+ return false;
781
+ }
782
+ // The call was announced before the prompt; keep its label up
783
+ // while it runs instead of falling back to a blank "Asking…".
784
+ rememberTool({
785
+ toolCallId: approval.toolCallId,
786
+ toolName: approval.toolName,
787
+ summary: approval.summary,
788
+ });
789
+ return true;
733
790
  },
734
791
  });
735
792
  stopSpinner();
@@ -758,14 +815,18 @@ export async function runAskSession(options) {
758
815
  continue;
759
816
  }
760
817
  showNotices(notices);
761
- // The runtime could not be wound down, so this session will refuse
762
- // every question from here. Replace it rather than leaving the user
763
- // to guess that /new is the way out — the chat, its history and the
818
+ // The runtime could not be wound down, went silent, or the bridge
819
+ // dropped — this session will refuse every question from here.
820
+ // Replace it rather than exiting Ask: the chat, its history and the
764
821
  // working copy all survive; only the Claude Code process is new.
765
- if (error instanceof HarnessTurnStuck) {
822
+ if (error instanceof HarnessTurnStuck ||
823
+ error instanceof HarnessTurnStalled ||
824
+ isBridgeClosedError(error) === true) {
766
825
  await local.close();
767
826
  local = undefined;
768
- showCard("That coding session stopped responding, so it has been replaced. Ask again — the chat is unchanged.");
827
+ showCard(error instanceof HarnessTurnStalled
828
+ ? `${error.message} The coding session has been replaced — ask again, or try smaller steps.`
829
+ : "That coding session stopped responding, so it has been replaced. Ask again — the chat is unchanged.");
769
830
  continue;
770
831
  }
771
832
  throw error;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cargo-ai/cli",
3
- "version": "1.0.86",
3
+ "version": "1.0.87",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://getcargo.ai",
@@ -68,7 +68,7 @@
68
68
  "@ai-sdk/harness-claude-code": "^1.0.99",
69
69
  "@cargo-ai/api": "^1.0.76",
70
70
  "@cargo-ai/app-sdk": "^1.0.10",
71
- "@cargo-ai/cdk": "^1.0.70",
71
+ "@cargo-ai/cdk": "^1.0.71",
72
72
  "@cargo-ai/types": "^1.0.73",
73
73
  "@cargo-ai/utils": "^1.0.28",
74
74
  "@cargo-ai/worker-sdk": "^1.0.17",