@tea-agent/loop-agent 0.43.0-next.17 → 0.43.0-next.3
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/CHANGELOG.md +9 -140
- package/dist/application/task-lifecycle/advance.js +4 -17
- package/dist/application/task-lifecycle/observe.js +29 -43
- package/dist/application/task-lifecycle/plan-transitions.js +4 -5
- package/dist/build-stamp.json +3 -3
- package/dist/commands/client-recovery.js +55 -20
- package/dist/commands/new-task.js +5 -0
- package/dist/commands/status.js +57 -0
- package/dist/commands/task-contract.js +269 -0
- package/dist/executors/dag-pi-executor.js +230 -269
- package/dist/executors/pi-executor.js +0 -2
- package/dist/executors/pi-sdk-executor.js +39 -77
- package/dist/executors/shell-executor.js +2 -3
- package/dist/executors/shell-write-guard.js +32 -27
- package/dist/shared/pi-context-pressure/extension.js +16 -26
- package/dist/shared/pi-retry-settings.js +0 -40
- package/dist/shared/prompts.js +26 -0
- package/dist/task/config.js +2 -0
- package/dist/task/contract/validate-draft.js +106 -0
- package/dist/task/goal.js +3 -0
- package/dist/task/index.js +12 -0
- package/dist/task/lifecycle.js +1 -0
- package/dist/task/operator/capabilities.js +6 -0
- package/dist/task/operator/envelope.js +2 -0
- package/dist/task/operator/index.js +5 -0
- package/dist/task/operator/registry.js +2 -0
- package/dist/task/operator/types.js +1 -0
- package/dist/task/paths.js +1 -0
- package/dist/task/source-prepare/source-fidelity-pi.js +2 -2
- package/dist/task/source-references.js +23 -48
- package/dist/task/source-state.js +1 -0
- package/dist/task/subagent-guidance.js +1 -0
- package/dist/worker/console/chat/assistant-content.js +2 -23
- package/dist/worker/console/chat/explore-tools.js +0 -13
- package/dist/worker/console/chat/pi-runtime.js +1182 -221
- package/dist/worker/console/chat/routes.js +71 -213
- package/dist/worker/console/chat/sdd-data-alignment.js +9 -65
- package/dist/worker/console/chat/session-catalog.js +0 -32
- package/dist/worker/console/chat/session-store.js +6 -22
- package/dist/worker/console/chat/shortcuts.js +0 -6
- package/dist/worker/console/chat/sift-bridge.js +1 -0
- package/dist/worker/console/chat/tool-preview.js +0 -11
- package/dist/worker/console/chat/tools.js +0 -6
- package/dist/worker/console/console-update-and-init.js +19 -1
- package/dist/worker/console/console-update-runtime.js +20 -5
- package/dist/worker/console/index.js +20 -0
- package/dist/worker/console/operator-actions.js +1 -13
- package/dist/worker/console/routes.js +0 -8
- package/dist/worker/console/server.js +29 -11
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4NXyedy.js → abnfDiagram-N423BO3Z-hUmM_GRf.js} +1 -1
- package/dist/worker/console/static/assets/{arc-NZ3qXvBI.js → arc-Bt-xuieW.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-COZSRvml.js → architectureDiagram-T3A2C74G-DTqkL4DJ.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Ck0Gxi9C.js → blockDiagram-VBNYF7ZC-Cy0MNnzV.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BX5HQ4Ox.js → c4Diagram-5PPSVZJV-DDdlT0Pd.js} +1 -1
- package/dist/worker/console/static/assets/channel-BpzRnZmU.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CoviemSL.js → chunk-2GRJ4B5K-3JawwNZ6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DbOdOkbZ.js → chunk-2Q5K7J3B-DoTp7Cri.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BsG2pmdQ.js → chunk-5RXB4S5H-CcirhZaS.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DGdvrNm8.js → chunk-5VM5RSS4-VrG21gnP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BAy7Yd6y.js → chunk-6Q2QTUOP-DDOgIS70.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D9UA7hUb.js → chunk-GF5L2VYU-CBjmgvJ_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DPz7SZ8V.js → chunk-JWPE2WC7-CCQSht8M.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BmuEOIh7.js → chunk-KBJHAD2P-BpGMkUUV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CIhBWnd8.js → chunk-RYQCIY6F-DNDT41QL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CScR6mDD.js → chunk-XXDRQBXY-euXnDFbn.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BhlMVqCc.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BhlMVqCc.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BjpfWaUr.js → cose-bilkent-JH36ORCC-BJ6NAeql.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BFQohJgT.js → cynefin-VYW2F7L2-F5JKIvIq.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-D3gzRJnS.js → cynefinDiagram-MW4NZA55-Cwmkl_Gi.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-5eojkhoA.js → dagre-VZM6K2ZE-BmCZ8815.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CpD8LWu_.js → diagram-7IWD3JNH-zWA3hUnX.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B8_4qJ2a.js → diagram-B4RE2ZJO-BzA-888F.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-HOsXf0eu.js → diagram-LBJQPF4R-tPNheoch.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BqwRwiWP.js → diagram-Q27KOJAE-Cdi7Fyqp.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BRVCNZe1.js → diagram-UB23O5K3-DFK9VBf1.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Cvb1TgGh.js → ebnfDiagram-BXEA7PRR-xCUr1kpg.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DS0O0qG9.js → erDiagram-JOGREHBK-BANQHY_A.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BHIRvRr3.js → flowDiagram-UKHOOZJN-VHJYEMkP.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DcCJ1JWx.js → ganttDiagram-PKOTCBZU-DiyZQ0C6.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-B9ia5ony.js → gitGraphDiagram-DS77QQ5N-CZtIeBBa.js} +1 -1
- package/dist/worker/console/static/assets/index-BlN6ytLr.css +1 -0
- package/dist/worker/console/static/assets/index-CA-jfWlE.js +449 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-3ogpgegn.js → infoDiagram-6WML65LV-BuSspC0X.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-4tgpQ5SL.js → ishikawaDiagram-WSZJBQD7-DRJZyPFy.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DNfgHqFx.js → journeyDiagram-NVQOT4AX-DIfXIVuE.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Dz7ngC8z.js → kanban-definition-27J2QSJJ-CT10_7Lt.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DI7Jv62E.js → linear-ufemj74H.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CQA4Nzmo.js → mermaid.core-DEQe8-_m.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CtsMc8Mf.js → mindmap-definition-FAOFIHXS-BRLUrbrC.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-ByppE9yH.js → pegDiagram-VL7TDLO6-BgibKtiF.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B5PtgFgt.js → pieDiagram-7S7Q4E2Y-D3eyfdV9.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Cq4p0k2K.js → quadrantDiagram-CIZ2JOQS-DXeTk5Px.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DjfkyljN.js → railroadDiagram-AXF67PYL-BgXdFEe7.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DzynNRSl.js → requirementDiagram-LRYGKXZP-CseC2A7R.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cj7TCpTJ.js → sankeyDiagram-W5VNT64P-D80PFzfJ.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-C73C6ykA.js → sequenceDiagram-SI44F4Z6-BWzHW7LS.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BiNLgN2x.js → sizeCapture-X5ZJPWSS-xWQYTLAn.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-UwvjS64-.js → stateDiagram-OKZ733FA-CLxtpZeL.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CiVoq4G-.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CC7DH98r.js → swimlanes-SLNWSIFB-DwugtmWV.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BkdgdpfR.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-LSBXTR70.js → timeline-definition-Z64GVDOM-DHEOwHIe.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BAuxWwGP.js → vennDiagram-T6HMQDX7-pmbZKlBK.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y--E5_gjZx.js → wardleyDiagram-T6FBY63Y-CivHoM5q.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-dJWNmyzF.js → xychartDiagram-ELKLHX3M-BfHIE7o6.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +10 -122
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +0 -7
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +15 -35
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +62 -136
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +68 -159
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +56 -75
- package/dist/worker/console/static-src/operator-chat/useComposer.js +78 -206
- package/dist/worker/console/workspace-context.js +1 -21
- package/dist/worker/observe/static/router.js +1 -1
- package/dist/worker/observe/static/styles.css +4 -7
- package/dist/worker/observe/static/views/dag-inspector.js +19 -12
- package/dist/worker/observe/static/views/dag.js +2 -2
- package/dist/worker/scheduler/index.js +25 -0
- package/dist/workflows/dag/facts.js +4 -0
- package/dist/workflows/dag/frontend-closeout.js +1 -1
- package/dist/workflows/dag/frontend-design-policy.js +1 -2
- package/dist/workflows/dag/frontend-implementation-contract.js +4 -8
- package/dist/workflows/dag/frontend-prewrite-gate.js +3 -3
- package/dist/workflows/dag/frontend-repair.js +76 -55
- package/dist/workflows/dag/frontend-review-context.js +1 -2
- package/dist/workflows/dag/frontend-review-scopes.js +12 -34
- package/dist/workflows/dag/frontend-shape-facts.js +2 -12
- package/dist/workflows/dag/frontend-verification-trace.js +42 -8
- package/dist/workflows/dag/index.js +6 -0
- package/dist/workflows/dag/init-hybrid.js +160 -97
- package/dist/workflows/dynamic/index.js +7 -0
- package/docs/architecture/runtime-boundaries.md +3 -3
- package/docs/templates/frontend-implementation-dag.json +8 -9
- package/package.json +3 -7
- package/skills/frontend-bounded-implement/SKILL.md +3 -4
- package/skills/frontend-bounded-implement/references/code-standards.md +3 -3
- package/skills/frontend-contract/references/contract-protocol.md +1 -3
- package/skills/frontend-design-review/SKILL.md +7 -12
- package/skills/frontend-design-review/references/review-checklist.md +7 -7
- package/skills/frontend-plan/SKILL.md +0 -19
- package/skills/frontend-plan/references/decision-contract.md +19 -26
- package/skills/frontend-review/SKILL.md +18 -12
- package/skills/frontend-review/references/review-findings.md +43 -24
- package/dist/shared/pi-provider-recovery.js +0 -182
- package/dist/worker/console/chat/browser-automation.js +0 -363
- package/dist/worker/console/chat/browser-policy.js +0 -113
- package/dist/worker/console/chat/browser-routes.js +0 -162
- package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +0 -44
- package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +0 -89
- package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +0 -202
- package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +0 -79
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +0 -86
- package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +0 -38
- package/dist/worker/console/chat/pi-runtime/inspection-types.js +0 -28
- package/dist/worker/console/chat/pi-runtime/inspection.js +0 -439
- package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +0 -153
- package/dist/worker/console/chat/pi-runtime/sdk-session.js +0 -5
- package/dist/worker/console/chat/scm-routes.js +0 -217
- package/dist/worker/console/chat/scm-service.js +0 -283
- package/dist/worker/console/chat/scm-tools.js +0 -111
- package/dist/worker/console/chat/session-mode-view.js +0 -49
- package/dist/worker/console/chat/session-mode.js +0 -220
- package/dist/worker/console/chat/terminal-routes.js +0 -216
- package/dist/worker/console/chat/terminal-sessions.js +0 -348
- package/dist/worker/console/chat/terminal-tools.js +0 -211
- package/dist/worker/console/static/assets/channel-BxdTlb_5.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-_ByA1Nf8.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-_ByA1Nf8.js +0 -1
- package/dist/worker/console/static/assets/index-B9goN3S5.css +0 -1
- package/dist/worker/console/static/assets/index-wXlQohNs.js +0 -469
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-nLgMZ0D3.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BcYqTady.js +0 -8
- package/dist/worker/console/static-src/operator-chat/active-turn-tail.js +0 -10
- package/dist/worker/console/static-src/operator-chat/chat-link.js +0 -94
- package/dist/worker/console/static-src/operator-chat/chat-scroll-position.js +0 -24
- package/dist/worker/console/static-src/operator-chat/composer-session-ledger.js +0 -192
- package/dist/worker/console/static-src/operator-chat/process-label.js +0 -41
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +0 -13
- package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +0 -10
- package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +0 -51
- package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +0 -21
- package/dist/worker/console/workspace-initialization.js +0 -209
- package/dist/workflows/dag/frontend-capacity.js +0 -9
- package/dist/workflows/dag/frontend-plan-completeness.js +0 -186
- package/dist/workflows/dag/frontend-plan-recovery-policy.js +0 -18
|
@@ -53,18 +53,14 @@ function clearEmptyAssistant(setMessages, assistantId) {
|
|
|
53
53
|
return;
|
|
54
54
|
setMessages((messages) => messages.filter((message) => !(message.id === assistantId &&
|
|
55
55
|
message.role === "assistant" &&
|
|
56
|
-
!message.text.trim()
|
|
57
|
-
// A row carrying only thinking/tool blocks is real content. Deleting
|
|
58
|
-
// it here dropped the turn's process nodes while the settled path in
|
|
59
|
-
// chat-sse-events deliberately keeps them (2026-09-08).
|
|
60
|
-
!(message.content && message.content.length > 0))));
|
|
56
|
+
!message.text.trim())));
|
|
61
57
|
}
|
|
62
58
|
/** One GET /events stream owns all Chat events. Sending a prompt creates a
|
|
63
59
|
* detached Turn resource only; it never opens a second inline SSE consumer. */
|
|
64
60
|
export function useChatStream(params) {
|
|
65
61
|
const { origin, refs, session, thread, composer, setError, setAutoScroll, setNotice, recoveryVersion = 0, watchSessionTitle, } = params;
|
|
66
62
|
const { applySseBlock, setMessages, setStreaming, setStreamingAssistantId, streaming, setTurnPhase, setDeferredTurn, setDeferredTurns, } = thread;
|
|
67
|
-
const {
|
|
63
|
+
const { setInput, setPendingImages, recordHistory } = composer;
|
|
68
64
|
const showNotice = useCallback((message) => {
|
|
69
65
|
if (!setNotice)
|
|
70
66
|
return;
|
|
@@ -144,10 +140,7 @@ export function useChatStream(params) {
|
|
|
144
140
|
current.generation === generation)
|
|
145
141
|
current.ready = true;
|
|
146
142
|
replay = false;
|
|
147
|
-
|
|
148
|
-
// returns immediately for this event. `continue` here silently
|
|
149
|
-
// dropped that flush, so a reconnect that landed after the
|
|
150
|
-
// turn settled never restored the answer (2026-09-08).
|
|
143
|
+
continue;
|
|
151
144
|
}
|
|
152
145
|
const assistantId = refs.assistantIdRef.current ?? fallbackAssistantId;
|
|
153
146
|
const eventId = applySseBlock(block, assistantId, {
|
|
@@ -223,22 +216,15 @@ export function useChatStream(params) {
|
|
|
223
216
|
}, [origin]);
|
|
224
217
|
const postCreateTurn = useCallback(async (input) => {
|
|
225
218
|
try {
|
|
226
|
-
// Resolve against the configured Console origin and fail closed if a
|
|
227
|
-
// future URL helper ever escapes that trusted same-origin boundary.
|
|
228
|
-
const consoleOrigin = new URL(origin, window.location.href);
|
|
229
|
-
const turnsUrl = new URL(operatorChatTurnsUrl(origin, input.sessionId), consoleOrigin);
|
|
230
|
-
if (turnsUrl.origin !== consoleOrigin.origin)
|
|
231
|
-
throw new Error("create-turn URL escaped the Console origin");
|
|
232
219
|
// Real create-turn path remains `${origin}/api/operator/v1/chat/sessions/:id/turns`
|
|
233
220
|
// (via operatorChatTurnsUrl); source contract anchors on /turns`.
|
|
234
|
-
const res = await fetch(
|
|
221
|
+
const res = await fetch(operatorChatTurnsUrl(origin, input.sessionId), {
|
|
235
222
|
method: "POST",
|
|
236
223
|
headers: {
|
|
237
224
|
"Content-Type": "application/json",
|
|
238
225
|
"x-loop-console-confirmation": confirmationToken(),
|
|
239
226
|
},
|
|
240
227
|
credentials: "include",
|
|
241
|
-
signal: input.signal,
|
|
242
228
|
body: JSON.stringify({
|
|
243
229
|
clientRequestId: input.clientRequestId,
|
|
244
230
|
text: input.text.trim() || "请查看附件图片。",
|
|
@@ -357,6 +343,9 @@ export function useChatStream(params) {
|
|
|
357
343
|
bag.sessionId = refs.sessionRef.current?.sessionId ?? null;
|
|
358
344
|
bag.sessionGeneration = refs.sessionGenerationRef.current;
|
|
359
345
|
bag.submitGeneration = refs.submitGenerationRef.current;
|
|
346
|
+
const setPendingImagesUi = (images) => {
|
|
347
|
+
setPendingImages(images);
|
|
348
|
+
};
|
|
360
349
|
settleCreateTurnOutcome(bag, outcome, {
|
|
361
350
|
assistantId: ctx.assistantId,
|
|
362
351
|
text: ctx.text,
|
|
@@ -367,13 +356,13 @@ export function useChatStream(params) {
|
|
|
367
356
|
acceptedUi: {
|
|
368
357
|
setStreaming,
|
|
369
358
|
setStreamingAssistantId,
|
|
370
|
-
setPendingImages:
|
|
359
|
+
setPendingImages: setPendingImagesUi,
|
|
371
360
|
watchSessionTitle,
|
|
372
361
|
},
|
|
373
362
|
adoptUi: {
|
|
374
363
|
clearEmptyAssistant: (assistantId) => clearEmptyAssistant(setMessages, assistantId),
|
|
375
|
-
setInput
|
|
376
|
-
setPendingImages:
|
|
364
|
+
setInput,
|
|
365
|
+
setPendingImages: setPendingImagesUi,
|
|
377
366
|
setStreaming,
|
|
378
367
|
setStreamingAssistantId,
|
|
379
368
|
removePendingMessage: (requestId) => setMessages((messages) => removePendingSubmission(messages, requestId)),
|
|
@@ -383,8 +372,8 @@ export function useChatStream(params) {
|
|
|
383
372
|
setStreaming,
|
|
384
373
|
setStreamingAssistantId,
|
|
385
374
|
clearEmptyAssistant: (assistantId) => clearEmptyAssistant(setMessages, assistantId),
|
|
386
|
-
setInput
|
|
387
|
-
setPendingImages:
|
|
375
|
+
setInput,
|
|
376
|
+
setPendingImages: setPendingImagesUi,
|
|
388
377
|
formatError: formatOperatorUserError,
|
|
389
378
|
removePendingMessage: (requestId) => setMessages((messages) => removePendingSubmission(messages, requestId)),
|
|
390
379
|
},
|
|
@@ -393,14 +382,6 @@ export function useChatStream(params) {
|
|
|
393
382
|
void postAbortForTurn(ctx.sessionId, ctx.generation, turnId);
|
|
394
383
|
},
|
|
395
384
|
});
|
|
396
|
-
if (outcome.kind === "accepted") {
|
|
397
|
-
settleSubmission(ctx.attempt, "accepted");
|
|
398
|
-
}
|
|
399
|
-
else if (outcome.kind === "adopt-active" ||
|
|
400
|
-
outcome.kind === "rejected" ||
|
|
401
|
-
outcome.kind === "stale") {
|
|
402
|
-
settleSubmission(ctx.attempt, "rejected");
|
|
403
|
-
}
|
|
404
385
|
applyOwnershipBagToRefs(refs, bag);
|
|
405
386
|
const ownershipPhase = turnPhaseFromOwnership(bag);
|
|
406
387
|
setTurnPhase(visualTurnPhaseFromOwnership(bag));
|
|
@@ -415,84 +396,68 @@ export function useChatStream(params) {
|
|
|
415
396
|
setStreaming,
|
|
416
397
|
setTurnPhase,
|
|
417
398
|
setStreamingAssistantId,
|
|
418
|
-
|
|
399
|
+
setPendingImages,
|
|
419
400
|
watchSessionTitle,
|
|
420
401
|
setMessages,
|
|
402
|
+
setInput,
|
|
421
403
|
setError,
|
|
422
404
|
postAbortForTurn,
|
|
423
405
|
]);
|
|
424
406
|
const queueDeferredTurn = useCallback(async (input) => {
|
|
425
|
-
const { attempt } = input;
|
|
426
407
|
const activeSession = refs.sessionRef.current;
|
|
427
|
-
|
|
428
|
-
refs.sessionGenerationRef.current === attempt.sessionGeneration;
|
|
429
|
-
if (!activeSession ||
|
|
430
|
-
activeSession.sessionId !== attempt.identity.sessionId ||
|
|
431
|
-
!isCurrent()) {
|
|
432
|
-
settleSubmission(attempt, "rejected");
|
|
408
|
+
if (!activeSession || (!input.text.trim() && input.images.length === 0))
|
|
433
409
|
return { action: "handled" };
|
|
434
|
-
}
|
|
435
|
-
const showError = (message) => {
|
|
436
|
-
if (isCurrent())
|
|
437
|
-
setError(message);
|
|
438
|
-
};
|
|
439
410
|
const acceptDeferred = (deferredTurn, deferredTurns) => {
|
|
440
|
-
settleSubmission(attempt, "accepted");
|
|
441
|
-
if (!isCurrent())
|
|
442
|
-
return;
|
|
443
411
|
setDeferredTurn(deferredTurns?.[0] ?? deferredTurn);
|
|
444
412
|
setDeferredTurns(deferredTurns ?? [deferredTurn]);
|
|
413
|
+
setInput("");
|
|
414
|
+
setPendingImages([]);
|
|
445
415
|
setError(null);
|
|
446
416
|
};
|
|
447
417
|
const acceptDispatched = () => {
|
|
448
|
-
settleSubmission(attempt, "accepted");
|
|
449
|
-
if (!isCurrent())
|
|
450
|
-
return;
|
|
451
418
|
setDeferredTurn(null);
|
|
452
419
|
setDeferredTurns([]);
|
|
420
|
+
setInput("");
|
|
421
|
+
setPendingImages([]);
|
|
453
422
|
setError(null);
|
|
454
423
|
};
|
|
455
424
|
const putOnce = async () => {
|
|
456
|
-
const response = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(
|
|
425
|
+
const response = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(activeSession.sessionId)}/deferred-turn`, {
|
|
457
426
|
method: "PUT",
|
|
458
427
|
headers: {
|
|
459
428
|
"Content-Type": "application/json",
|
|
460
429
|
"x-loop-console-confirmation": confirmationToken(),
|
|
461
430
|
},
|
|
462
431
|
credentials: "include",
|
|
463
|
-
signal: attempt.signal,
|
|
464
432
|
body: JSON.stringify({
|
|
465
|
-
clientRequestId:
|
|
433
|
+
clientRequestId: input.clientRequestId,
|
|
466
434
|
blockedByTurnId: input.blockedByTurnId,
|
|
467
|
-
text:
|
|
468
|
-
images:
|
|
435
|
+
text: input.text,
|
|
436
|
+
images: input.images,
|
|
469
437
|
}),
|
|
470
438
|
});
|
|
471
439
|
const body = (await response.json().catch(() => ({})));
|
|
472
440
|
return { response, body };
|
|
473
441
|
};
|
|
474
442
|
try {
|
|
475
|
-
for (let
|
|
443
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
476
444
|
const { response, body } = await putOnce();
|
|
477
445
|
if (response.ok && body.accepted && body.deferredTurn) {
|
|
478
446
|
acceptDeferred(body.deferredTurn, body.deferredTurns);
|
|
479
447
|
return { action: "handled" };
|
|
480
448
|
}
|
|
449
|
+
if (body.deferredTurns)
|
|
450
|
+
setDeferredTurns(body.deferredTurns);
|
|
451
|
+
if (body.deferredTurn)
|
|
452
|
+
setDeferredTurn(body.deferredTurn);
|
|
481
453
|
if (body.error?.code !== "TURN_NOT_ACTIVE") {
|
|
482
|
-
|
|
483
|
-
if (body.deferredTurns)
|
|
484
|
-
setDeferredTurns(body.deferredTurns);
|
|
485
|
-
if (body.deferredTurn)
|
|
486
|
-
setDeferredTurn(body.deferredTurn);
|
|
487
|
-
}
|
|
488
|
-
settleSubmission(attempt, "rejected");
|
|
489
|
-
showError(body.error?.message ??
|
|
454
|
+
setError(body.error?.message ??
|
|
490
455
|
`待发送消息未被服务端接受(HTTP ${response.status})`);
|
|
491
456
|
return { action: "handled" };
|
|
492
457
|
}
|
|
493
|
-
const canonical = await fetchSessionState(
|
|
458
|
+
const canonical = await fetchSessionState(activeSession.sessionId);
|
|
494
459
|
const decision = reconcileDeferredSubmissionFromState({
|
|
495
|
-
clientRequestId:
|
|
460
|
+
clientRequestId: input.clientRequestId,
|
|
496
461
|
blockedByTurnId: input.blockedByTurnId,
|
|
497
462
|
state: canonical ?? undefined,
|
|
498
463
|
});
|
|
@@ -504,29 +469,20 @@ export function useChatStream(params) {
|
|
|
504
469
|
acceptDispatched();
|
|
505
470
|
return { action: "handled" };
|
|
506
471
|
}
|
|
507
|
-
if (decision.action === "retry-deferred" &&
|
|
472
|
+
if (decision.action === "retry-deferred" && attempt === 0)
|
|
508
473
|
continue;
|
|
509
|
-
if (decision.action === "send-now")
|
|
474
|
+
if (decision.action === "send-now") {
|
|
510
475
|
return { action: "send-now" };
|
|
511
|
-
if (isCurrent()) {
|
|
512
|
-
if (body.deferredTurns)
|
|
513
|
-
setDeferredTurns(body.deferredTurns);
|
|
514
|
-
if (body.deferredTurn)
|
|
515
|
-
setDeferredTurn(body.deferredTurn);
|
|
516
476
|
}
|
|
517
|
-
|
|
518
|
-
showError(body.error?.message ?? "停止状态已变化,当前消息仍保留在输入框");
|
|
477
|
+
setError(body.error?.message ?? "停止状态已变化,当前消息仍保留在输入框");
|
|
519
478
|
return { action: "handled" };
|
|
520
479
|
}
|
|
521
|
-
settleSubmission(attempt, "rejected");
|
|
522
480
|
return { action: "handled" };
|
|
523
481
|
}
|
|
524
482
|
catch (error) {
|
|
525
|
-
|
|
526
|
-
return { action: "handled" };
|
|
527
|
-
const canonical = await fetchSessionState(attempt.identity.sessionId);
|
|
483
|
+
const canonical = await fetchSessionState(activeSession.sessionId);
|
|
528
484
|
const decision = reconcileDeferredSubmissionFromState({
|
|
529
|
-
clientRequestId:
|
|
485
|
+
clientRequestId: input.clientRequestId,
|
|
530
486
|
blockedByTurnId: input.blockedByTurnId,
|
|
531
487
|
state: canonical ?? undefined,
|
|
532
488
|
});
|
|
@@ -538,10 +494,10 @@ export function useChatStream(params) {
|
|
|
538
494
|
acceptDispatched();
|
|
539
495
|
return { action: "handled" };
|
|
540
496
|
}
|
|
541
|
-
if (decision.action === "send-now")
|
|
497
|
+
if (decision.action === "send-now") {
|
|
542
498
|
return { action: "send-now" };
|
|
543
|
-
|
|
544
|
-
|
|
499
|
+
}
|
|
500
|
+
setError(error instanceof Error
|
|
545
501
|
? `无法确认消息是否已排队:${error.message}`
|
|
546
502
|
: "无法确认消息是否已排队,请刷新后查看服务端状态");
|
|
547
503
|
return { action: "handled" };
|
|
@@ -552,7 +508,8 @@ export function useChatStream(params) {
|
|
|
552
508
|
thread.deferredTurn,
|
|
553
509
|
setDeferredTurn,
|
|
554
510
|
setDeferredTurns,
|
|
555
|
-
|
|
511
|
+
setInput,
|
|
512
|
+
setPendingImages,
|
|
556
513
|
setError,
|
|
557
514
|
fetchSessionState,
|
|
558
515
|
]);
|
|
@@ -565,10 +522,6 @@ export function useChatStream(params) {
|
|
|
565
522
|
: thread.deferredTurns[0] ?? thread.deferredTurn;
|
|
566
523
|
if (!activeSession || !deferred)
|
|
567
524
|
return;
|
|
568
|
-
const identity = { ...activeSession };
|
|
569
|
-
const generation = refs.sessionGenerationRef.current;
|
|
570
|
-
const isCurrent = () => refs.sessionRef.current?.sessionId === identity.sessionId &&
|
|
571
|
-
refs.sessionGenerationRef.current === generation;
|
|
572
525
|
try {
|
|
573
526
|
const response = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(activeSession.sessionId)}/deferred-turn`, {
|
|
574
527
|
method: "DELETE",
|
|
@@ -584,25 +537,20 @@ export function useChatStream(params) {
|
|
|
584
537
|
});
|
|
585
538
|
const body = (await response.json().catch(() => ({})));
|
|
586
539
|
if (!response.ok || !body.cancelled) {
|
|
587
|
-
|
|
588
|
-
setError(body.error?.message ?? "待发送消息撤回失败");
|
|
540
|
+
setError(body.error?.message ?? "待发送消息撤回失败");
|
|
589
541
|
return;
|
|
590
542
|
}
|
|
591
|
-
if (mode === "restore") {
|
|
592
|
-
replaceSessionDraft(identity, deferred.text ?? body.deferredTurn?.text ?? "", deferred.images ??
|
|
593
|
-
body.deferredTurn?.images ??
|
|
594
|
-
[]);
|
|
595
|
-
}
|
|
596
|
-
if (!isCurrent())
|
|
597
|
-
return;
|
|
598
543
|
const remaining = thread.deferredTurns.filter((item) => item.clientRequestId !== deferred.clientRequestId);
|
|
599
544
|
setDeferredTurns(remaining);
|
|
600
545
|
setDeferredTurn(remaining[0] ?? null);
|
|
546
|
+
if (mode === "restore") {
|
|
547
|
+
setInput(deferred.text ?? body.deferredTurn?.text ?? "");
|
|
548
|
+
setPendingImages(deferred.images ?? body.deferredTurn?.images ?? []);
|
|
549
|
+
}
|
|
601
550
|
setError(null);
|
|
602
551
|
}
|
|
603
552
|
catch (error) {
|
|
604
|
-
|
|
605
|
-
setError(error instanceof Error ? error.message : String(error));
|
|
553
|
+
setError(error instanceof Error ? error.message : String(error));
|
|
606
554
|
}
|
|
607
555
|
}, [
|
|
608
556
|
origin,
|
|
@@ -611,7 +559,8 @@ export function useChatStream(params) {
|
|
|
611
559
|
thread.deferredTurns,
|
|
612
560
|
setDeferredTurn,
|
|
613
561
|
setDeferredTurns,
|
|
614
|
-
|
|
562
|
+
setInput,
|
|
563
|
+
setPendingImages,
|
|
615
564
|
setError,
|
|
616
565
|
]);
|
|
617
566
|
const cancelDeferredTurn = useCallback(() => requestDeferredTurnDelete("restore"), [requestDeferredTurnDelete]);
|
|
@@ -620,9 +569,6 @@ export function useChatStream(params) {
|
|
|
620
569
|
const activeSession = refs.sessionRef.current;
|
|
621
570
|
if (!activeSession)
|
|
622
571
|
return;
|
|
623
|
-
const generation = refs.sessionGenerationRef.current;
|
|
624
|
-
const isCurrent = () => refs.sessionRef.current?.sessionId === activeSession.sessionId &&
|
|
625
|
-
refs.sessionGenerationRef.current === generation;
|
|
626
572
|
const item = thread.deferredTurns.find((entry) => entry.clientRequestId === target.clientRequestId);
|
|
627
573
|
if (!item)
|
|
628
574
|
return;
|
|
@@ -634,8 +580,6 @@ export function useChatStream(params) {
|
|
|
634
580
|
body: JSON.stringify({ clientRequestId: target.clientRequestId, blockedByTurnId: item.blockedByTurnId, text, images: item.images ?? [], expectedRevision: target.revision }),
|
|
635
581
|
});
|
|
636
582
|
const body = (await response.json().catch(() => ({})));
|
|
637
|
-
if (!isCurrent())
|
|
638
|
-
return;
|
|
639
583
|
if (!response.ok)
|
|
640
584
|
throw new Error(body.error?.message ?? "编辑排队消息失败");
|
|
641
585
|
if (body.deferredTurns)
|
|
@@ -645,17 +589,13 @@ export function useChatStream(params) {
|
|
|
645
589
|
setError(null);
|
|
646
590
|
}
|
|
647
591
|
catch (error) {
|
|
648
|
-
|
|
649
|
-
setError(error instanceof Error ? error.message : "编辑排队消息失败");
|
|
592
|
+
setError(error instanceof Error ? error.message : "编辑排队消息失败");
|
|
650
593
|
}
|
|
651
594
|
}, [origin, refs, thread.deferredTurns, setDeferredTurn, setDeferredTurns, setError]);
|
|
652
595
|
const steerDeferredTurn = useCallback(async (target) => {
|
|
653
596
|
const activeSession = refs.sessionRef.current;
|
|
654
597
|
if (!activeSession)
|
|
655
598
|
return;
|
|
656
|
-
const generation = refs.sessionGenerationRef.current;
|
|
657
|
-
const isCurrent = () => refs.sessionRef.current?.sessionId === activeSession.sessionId &&
|
|
658
|
-
refs.sessionGenerationRef.current === generation;
|
|
659
599
|
try {
|
|
660
600
|
const response = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(activeSession.sessionId)}/deferred-turn/${encodeURIComponent(target.clientRequestId)}/steer`, {
|
|
661
601
|
method: "POST",
|
|
@@ -664,8 +604,6 @@ export function useChatStream(params) {
|
|
|
664
604
|
body: JSON.stringify({ expectedRevision: target.revision }),
|
|
665
605
|
});
|
|
666
606
|
const body = (await response.json().catch(() => ({})));
|
|
667
|
-
if (!isCurrent())
|
|
668
|
-
return;
|
|
669
607
|
if (!response.ok)
|
|
670
608
|
throw new Error(body.error?.message ?? "插话发送失败");
|
|
671
609
|
const remaining = body.deferredTurns ?? thread.deferredTurns.filter((item) => item.clientRequestId !== target.clientRequestId);
|
|
@@ -674,39 +612,33 @@ export function useChatStream(params) {
|
|
|
674
612
|
setError(null);
|
|
675
613
|
}
|
|
676
614
|
catch (error) {
|
|
677
|
-
|
|
678
|
-
setError(error instanceof Error ? error.message : "插话发送失败");
|
|
615
|
+
setError(error instanceof Error ? error.message : "插话发送失败");
|
|
679
616
|
}
|
|
680
617
|
}, [origin, refs, thread.deferredTurns, setDeferredTurn, setDeferredTurns, setError]);
|
|
681
|
-
const sendPrompt = useCallback(async (
|
|
682
|
-
const text = attempt.draftSnapshot;
|
|
683
|
-
const images = attempt.imageSnapshot;
|
|
618
|
+
const sendPrompt = useCallback(async (text, images) => {
|
|
684
619
|
const initialSession = refs.sessionRef.current;
|
|
685
|
-
if (!initialSession ||
|
|
686
|
-
initialSession.sessionId !== attempt.identity.sessionId ||
|
|
687
|
-
refs.sessionGenerationRef.current !== attempt.sessionGeneration ||
|
|
688
|
-
(!text.trim() && images.length === 0)) {
|
|
689
|
-
settleSubmission(attempt, "rejected");
|
|
620
|
+
if (!initialSession || (!text.trim() && images.length === 0))
|
|
690
621
|
return;
|
|
691
|
-
}
|
|
692
622
|
const ownership = ownershipBagFromRefs(refs);
|
|
693
623
|
const route = decideSubmissionRoute({
|
|
694
624
|
turnPhase: turnPhaseFromOwnership(ownership),
|
|
695
625
|
blockedByTurnId: ownership.stoppingTurnId ?? ownership.activeTurnId,
|
|
696
626
|
});
|
|
697
|
-
const clientRequestId =
|
|
627
|
+
const clientRequestId = requestId("chat-submit");
|
|
698
628
|
if (route.action === "hold") {
|
|
699
629
|
const phase = turnPhaseFromOwnership(ownership);
|
|
700
630
|
if (phase === "stopping")
|
|
701
631
|
showNotice("正在确认上一轮停止状态,当前消息已保留");
|
|
702
632
|
else if (phase === "running")
|
|
703
633
|
showNotice("正在确认当前回合状态,当前消息已保留");
|
|
704
|
-
settleSubmission(attempt, "rejected");
|
|
705
634
|
return;
|
|
706
635
|
}
|
|
636
|
+
recordHistory?.(text);
|
|
707
637
|
if (route.action === "queue-deferred") {
|
|
708
638
|
const deferred = await queueDeferredTurn({
|
|
709
|
-
|
|
639
|
+
text,
|
|
640
|
+
images,
|
|
641
|
+
clientRequestId,
|
|
710
642
|
blockedByTurnId: route.blockedByTurnId,
|
|
711
643
|
});
|
|
712
644
|
if (deferred.action !== "send-now")
|
|
@@ -716,22 +648,19 @@ export function useChatStream(params) {
|
|
|
716
648
|
const generation = refs.sessionGenerationRef.current;
|
|
717
649
|
if (!activeSession ||
|
|
718
650
|
activeSession.sessionId !== initialSession.sessionId ||
|
|
719
|
-
generation !== attempt.sessionGeneration ||
|
|
720
651
|
(!text.trim() && images.length === 0) ||
|
|
721
652
|
refs.streamingRef.current ||
|
|
722
653
|
refs.submitInFlightRef.current ||
|
|
723
654
|
// AC-R5-002: ownership lease blocks send until terminal release.
|
|
724
|
-
refs.pendingSubmissionRef.current)
|
|
725
|
-
settleSubmission(attempt, "rejected");
|
|
655
|
+
refs.pendingSubmissionRef.current)
|
|
726
656
|
return;
|
|
727
|
-
}
|
|
728
657
|
// AC-001: acquire synchronous submit ownership before the first await.
|
|
729
658
|
refs.submitInFlightRef.current = true;
|
|
730
659
|
const submitGeneration = ++refs.submitGenerationRef.current;
|
|
731
660
|
const userMessageId = requestId("u");
|
|
732
661
|
const assistantId = requestId("a");
|
|
733
662
|
const draftText = text.trim();
|
|
734
|
-
const draftImages =
|
|
663
|
+
const draftImages = images.slice();
|
|
735
664
|
const pending = createPendingSubmission({
|
|
736
665
|
clientRequestId,
|
|
737
666
|
submitGeneration,
|
|
@@ -763,6 +692,7 @@ export function useChatStream(params) {
|
|
|
763
692
|
user: userMsg,
|
|
764
693
|
assistant: assistantMsg,
|
|
765
694
|
}));
|
|
695
|
+
setInput("");
|
|
766
696
|
setAutoScroll(true);
|
|
767
697
|
setError(null);
|
|
768
698
|
try {
|
|
@@ -776,19 +706,13 @@ export function useChatStream(params) {
|
|
|
776
706
|
releaseSubmitOwnership(clientRequestId);
|
|
777
707
|
setMessages((messages) => removePendingSubmission(messages, clientRequestId));
|
|
778
708
|
clearEmptyAssistant(setMessages, assistantId);
|
|
779
|
-
|
|
709
|
+
setInput(draftText);
|
|
710
|
+
setPendingImages(draftImages);
|
|
780
711
|
return;
|
|
781
712
|
}
|
|
782
713
|
if (refs.sessionRef.current?.sessionId !== activeSession.sessionId ||
|
|
783
714
|
refs.sessionGenerationRef.current !== generation) {
|
|
784
715
|
releaseSubmitOwnership(clientRequestId);
|
|
785
|
-
if (refs.sessionRef.current?.sessionId === activeSession.sessionId) {
|
|
786
|
-
// Same session, newer generation: the activation snapshot replaced
|
|
787
|
-
// the thread, so remove its obsolete optimistic projection.
|
|
788
|
-
setMessages((messages) => removePendingSubmission(messages, clientRequestId));
|
|
789
|
-
clearEmptyAssistant(setMessages, assistantId);
|
|
790
|
-
}
|
|
791
|
-
settleSubmission(attempt, "rejected");
|
|
792
716
|
return;
|
|
793
717
|
}
|
|
794
718
|
// Attachments stay until accepted (AC/UIS-accepted).
|
|
@@ -804,7 +728,6 @@ export function useChatStream(params) {
|
|
|
804
728
|
assistantId,
|
|
805
729
|
text: draftText,
|
|
806
730
|
images: draftImages,
|
|
807
|
-
attempt,
|
|
808
731
|
pending,
|
|
809
732
|
};
|
|
810
733
|
const first = await postCreateTurn({
|
|
@@ -812,7 +735,6 @@ export function useChatStream(params) {
|
|
|
812
735
|
clientRequestId,
|
|
813
736
|
text: draftText,
|
|
814
737
|
images: draftImages,
|
|
815
|
-
signal: attempt.signal,
|
|
816
738
|
});
|
|
817
739
|
if (!isSubmissionCurrent({
|
|
818
740
|
pending,
|
|
@@ -822,7 +744,6 @@ export function useChatStream(params) {
|
|
|
822
744
|
})) {
|
|
823
745
|
pending.resolveOutcome({ kind: "stale" });
|
|
824
746
|
releaseSubmitOwnership(clientRequestId);
|
|
825
|
-
settleSubmission(attempt, first.httpOk && first.body.accepted ? "accepted" : "rejected");
|
|
826
747
|
return;
|
|
827
748
|
}
|
|
828
749
|
let outcome = first.networkError
|
|
@@ -849,7 +770,6 @@ export function useChatStream(params) {
|
|
|
849
770
|
clientRequestId,
|
|
850
771
|
text: draftText,
|
|
851
772
|
images: draftImages,
|
|
852
|
-
signal: attempt.signal,
|
|
853
773
|
});
|
|
854
774
|
if (!isSubmissionCurrent({
|
|
855
775
|
pending,
|
|
@@ -859,7 +779,6 @@ export function useChatStream(params) {
|
|
|
859
779
|
})) {
|
|
860
780
|
pending.resolveOutcome({ kind: "stale" });
|
|
861
781
|
releaseSubmitOwnership(clientRequestId);
|
|
862
|
-
settleSubmission(attempt, retry.httpOk && retry.body.accepted ? "accepted" : "rejected");
|
|
863
782
|
return;
|
|
864
783
|
}
|
|
865
784
|
outcome = retry.networkError
|
|
@@ -890,13 +809,14 @@ export function useChatStream(params) {
|
|
|
890
809
|
releaseSubmitOwnership,
|
|
891
810
|
showNotice,
|
|
892
811
|
setMessages,
|
|
893
|
-
|
|
812
|
+
setInput,
|
|
894
813
|
setAutoScroll,
|
|
895
814
|
setError,
|
|
896
815
|
setStreaming,
|
|
897
816
|
setTurnPhase,
|
|
898
817
|
setStreamingAssistantId,
|
|
899
818
|
queueDeferredTurn,
|
|
819
|
+
recordHistory,
|
|
900
820
|
]);
|
|
901
821
|
const handleStop = useCallback(async () => {
|
|
902
822
|
// AC-R5-001: durable stopRequested is set synchronously before any await so
|
|
@@ -952,9 +872,8 @@ export function useChatStream(params) {
|
|
|
952
872
|
// Intent remains; /state bind-active will fire exactly one abort.
|
|
953
873
|
return;
|
|
954
874
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
// Rejected/stale settlement released this submission's stop intent.
|
|
875
|
+
else {
|
|
876
|
+
// rejected / stale — clear durable intent (settle already released).
|
|
958
877
|
refs.stopRequestedRef.current = false;
|
|
959
878
|
return;
|
|
960
879
|
}
|
|
@@ -998,7 +917,6 @@ export function useChatStream(params) {
|
|
|
998
917
|
if (!session || (!streaming && !hasOwnershipWork))
|
|
999
918
|
return;
|
|
1000
919
|
const sessionId = session.sessionId;
|
|
1001
|
-
const sessionIdentity = { ...session };
|
|
1002
920
|
const generation = refs.sessionGenerationRef.current;
|
|
1003
921
|
let cancelled = false;
|
|
1004
922
|
let syncInFlight = null;
|
|
@@ -1041,15 +959,11 @@ export function useChatStream(params) {
|
|
|
1041
959
|
if (state && "deferredTurn" in state)
|
|
1042
960
|
setDeferredTurn(state.deferredTurn ?? null);
|
|
1043
961
|
if (result === "bound" && ownershipPhase !== "stopping") {
|
|
1044
|
-
settleSubmissionByRequest(sessionIdentity, pending.clientRequestId, "accepted");
|
|
1045
962
|
// US-BUSY-ACTIVE-BOUND: keep streaming/busy with same id.
|
|
1046
963
|
setStreaming(true);
|
|
1047
964
|
return;
|
|
1048
965
|
}
|
|
1049
966
|
if (result === "released") {
|
|
1050
|
-
settleSubmissionByRequest(sessionIdentity, pending.clientRequestId, decision.action === "release-not-accepted"
|
|
1051
|
-
? "rejected"
|
|
1052
|
-
: "accepted");
|
|
1053
967
|
return;
|
|
1054
968
|
}
|
|
1055
969
|
// hold without acceptedTurnId: keep pending + busy.
|
|
@@ -1072,7 +986,6 @@ export function useChatStream(params) {
|
|
|
1072
986
|
stillPending.clientRequestId &&
|
|
1073
987
|
isTerminalTurnState(state?.activeTurn?.state);
|
|
1074
988
|
if (latestMatches || activeMatches) {
|
|
1075
|
-
settleSubmissionByRequest(sessionIdentity, stillPending.clientRequestId, "accepted");
|
|
1076
989
|
const termTurnId = activeMatches
|
|
1077
990
|
? (state?.activeTurn?.turnId ?? null)
|
|
1078
991
|
: (state?.latestTurn?.turnId ??
|
|
@@ -1144,10 +1057,7 @@ export function useChatStream(params) {
|
|
|
1144
1057
|
stopRetryIndex >= STOP_RECONCILE_DELAYS_MS.length ||
|
|
1145
1058
|
(!refs.stopRequestedRef.current && !refs.stoppingTurnIdRef.current))
|
|
1146
1059
|
return;
|
|
1147
|
-
const delay = STOP_RECONCILE_DELAYS_MS[stopRetryIndex];
|
|
1148
|
-
stopRetryIndex += 1;
|
|
1149
|
-
if (delay === undefined)
|
|
1150
|
-
return;
|
|
1060
|
+
const delay = STOP_RECONCILE_DELAYS_MS[stopRetryIndex++];
|
|
1151
1061
|
stopRetryTimer = window.setTimeout(() => {
|
|
1152
1062
|
void sync().finally(scheduleStopRetry);
|
|
1153
1063
|
}, delay);
|
|
@@ -1169,7 +1079,6 @@ export function useChatStream(params) {
|
|
|
1169
1079
|
refs,
|
|
1170
1080
|
session,
|
|
1171
1081
|
streaming,
|
|
1172
|
-
settleSubmissionByRequest,
|
|
1173
1082
|
setTurnPhase,
|
|
1174
1083
|
markIdleIfCurrent,
|
|
1175
1084
|
releaseSubmitOwnership,
|