@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
|
-
|
|
244
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|