@cjhyy/code-shell-web 0.9.18 → 0.9.20

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.
@@ -19,6 +19,8 @@ export interface PendingApproval {
19
19
  options?: string[];
20
20
  /** When true, the user must pick an option (no free text). */
21
21
  optionsOnly?: boolean;
22
+ /** A nonblocking question remains answerable after the run ends. */
23
+ asynchronous?: boolean;
22
24
  /** True for path-scoped tools (Read/Edit/Write/…) → show file/dir scope. */
23
25
  pathScoped: boolean;
24
26
  }
@@ -72,6 +72,7 @@ export function useRemoteApp(options = {}) {
72
72
  const [approvals, setApprovals] = useState([]);
73
73
  const approvalsRef = useRef(approvals);
74
74
  approvalsRef.current = approvals;
75
+ const submittingAsyncApprovalsRef = useRef(new Set());
75
76
  // The SELECTED project (one-true-source for "what am I looking at"), distinct
76
77
  // from activeSessionCwd which is derived from the bound session. Drives session
77
78
  // list filtering AND ccRoom.listSessions.
@@ -219,6 +220,7 @@ export function useRemoteApp(options = {}) {
219
220
  setLoading((prev) => (prev[key] === value ? prev : { ...prev, [key]: value }));
220
221
  }, []);
221
222
  const clearApproval = useCallback((requestId) => {
223
+ submittingAsyncApprovalsRef.current.delete(requestId);
222
224
  approvalsRef.current = removeResolvedApproval(approvalsRef.current, requestId);
223
225
  setApprovals((prev) => removeResolvedApproval(prev, requestId));
224
226
  }, []);
@@ -240,8 +242,10 @@ export function useRemoteApp(options = {}) {
240
242
  const type = event?.type;
241
243
  return type === "turn_complete" || type === "error";
242
244
  })) {
243
- approvalsRef.current = [];
244
- setApprovals([]);
245
+ // Asynchronous questions outlive the current run. Only a resolution
246
+ // notification (or a new worker epoch) makes their answer stale.
247
+ approvalsRef.current = approvalsRef.current.filter((approval) => approval.asynchronous);
248
+ setApprovals((prev) => prev.filter((approval) => approval.asynchronous));
245
249
  }
246
250
  }, []);
247
251
  const appendSessionEvents = useCallback((sessionId, events) => {
@@ -558,6 +562,9 @@ export function useRemoteApp(options = {}) {
558
562
  setNotice(event.message || t("mobile.notice.roomError"));
559
563
  break;
560
564
  case "error":
565
+ if (event.approvalId) {
566
+ submittingAsyncApprovalsRef.current.delete(event.approvalId);
567
+ }
561
568
  if (event.clientMessageId) {
562
569
  settleMessageAck(event.clientMessageId, new Error(event.message));
563
570
  }
@@ -773,6 +780,7 @@ export function useRemoteApp(options = {}) {
773
780
  risk,
774
781
  options: askOptions?.options,
775
782
  optionsOnly: askOptions?.optionsOnly,
783
+ asynchronous: rq.toolName === "__ask_user__" && rq.args?.asynchronous === true,
776
784
  pathScoped: PATH_SCOPED.has(rq.toolName ?? ""),
777
785
  });
778
786
  }
@@ -811,18 +819,9 @@ export function useRemoteApp(options = {}) {
811
819
  return;
812
820
  }
813
821
  dispatchChat({ kind: "raw", raw });
814
- // A turn ending (or erroring) resolves any pending approval for this
815
- // session — clear stale cards so a request the user already handled (or
816
- // that the desktop answered) doesn't linger ("点了还存在").
817
- const ev = params.event?.type;
818
- if (ev === "turn_complete" || ev === "error") {
819
- // We only ever hold bound-session approvals, so the turn ending clears
820
- // them all (the request can no longer be answered).
821
- approvalsRef.current = [];
822
- setApprovals([]);
823
- }
822
+ clearApprovalsIfTerminal([params.event]);
824
823
  }
825
- }, [addApproval, clearApproval, sessionCwdById]);
824
+ }, [addApproval, clearApproval, clearApprovalsIfTerminal, sessionCwdById]);
826
825
  const requestActiveResync = useCallback(() => {
827
826
  const roomId = activeRoomIdRef.current;
828
827
  if (roomId) {
@@ -877,6 +876,7 @@ export function useRemoteApp(options = {}) {
877
876
  useEffect(() => {
878
877
  if (socket.status === "online")
879
878
  return;
879
+ submittingAsyncApprovalsRef.current.clear();
880
880
  const error = new Error("Remote connection was lost");
881
881
  for (const waiter of uploadWaitersRef.current.values()) {
882
882
  clearTimeout(waiter.timer);
@@ -1115,7 +1115,9 @@ export function useRemoteApp(options = {}) {
1115
1115
  // card unmounts). Sending again would violate the approve-once invariant.
1116
1116
  if (!a)
1117
1117
  return;
1118
- socket.send({
1118
+ if (a.asynchronous && submittingAsyncApprovalsRef.current.has(requestId))
1119
+ return;
1120
+ const sent = socket.send({
1119
1121
  type: "approval.respond",
1120
1122
  approvalId: requestId,
1121
1123
  decision,
@@ -1125,6 +1127,14 @@ export function useRemoteApp(options = {}) {
1125
1127
  scope: opts?.scope,
1126
1128
  pathScope: opts?.pathScope,
1127
1129
  });
1130
+ if (!sent)
1131
+ return;
1132
+ if (a.asynchronous) {
1133
+ // Keep the card until the worker accepts this late answer. A rejected
1134
+ // delivery unlocks it for retry without losing the user's question.
1135
+ submittingAsyncApprovalsRef.current.add(requestId);
1136
+ return;
1137
+ }
1128
1138
  // Update the ref synchronously too so a second tap in the same tick (before
1129
1139
  // the async setApprovals commits) sees it gone and no-ops above.
1130
1140
  approvalsRef.current = approvalsRef.current.filter((p) => p.requestId !== requestId);
@@ -53,6 +53,7 @@ function runStateForReason(reason) {
53
53
  case "prompt_too_long":
54
54
  case "model_error":
55
55
  case "image_error":
56
+ case "replay_incomplete":
56
57
  return "error";
57
58
  // "completed", "max_turns", "goal_budget_exhausted", "stop_hook_prevented",
58
59
  // "hook_stopped", and any future/unknown terminal reason: a normal stop.