@tea-agent/loop-agent 0.43.0-next.10 → 0.43.0-next.12
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 +18 -0
- package/dist/build-stamp.json +3 -3
- package/dist/commands/client-recovery.js +20 -55
- package/dist/executors/dag-pi-executor.js +27 -6
- package/dist/executors/shell-executor.js +3 -2
- package/dist/shared/pi-context-pressure/extension.js +26 -16
- package/dist/shared/pi-provider-recovery.js +182 -0
- package/dist/shared/pi-retry-settings.js +40 -0
- package/dist/worker/console/chat/browser-policy.js +6 -36
- package/dist/worker/console/chat/browser-routes.js +3 -11
- package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +2 -1
- package/dist/worker/console/chat/pi-runtime.js +11 -12
- package/dist/worker/console/chat/routes.js +1 -5
- package/dist/worker/console/chat/session-mode-view.js +5 -4
- package/dist/worker/console/chat/session-mode.js +11 -10
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-D_fNbWc_.js → abnfDiagram-N423BO3Z-BEg5712w.js} +1 -1
- package/dist/worker/console/static/assets/{arc-_HuqLW6m.js → arc-CkQvaBGZ.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Djy2qY1m.js → architectureDiagram-T3A2C74G-BrdwFNJ7.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-bB-1TSyY.js → blockDiagram-VBNYF7ZC-M_LjP2kx.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-C5Hmghrz.js → c4Diagram-5PPSVZJV-Bwr5Dtjc.js} +1 -1
- package/dist/worker/console/static/assets/channel-CKmyKgY-.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Duyl39oy.js → chunk-2GRJ4B5K-CDc44stW.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-x6mdnSyf.js → chunk-2Q5K7J3B-DZELwsTt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-WlBuFj7W.js → chunk-5RXB4S5H-D7YI6SWX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CTF429HX.js → chunk-5VM5RSS4-Bc46bEyj.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Dwd6r-SF.js → chunk-6Q2QTUOP-Bj4vc-Dp.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-C-Br82D1.js → chunk-GF5L2VYU-DRi-0nDX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CuGp0DeE.js → chunk-JWPE2WC7-Dr-wFY60.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CJhFX6Lh.js → chunk-KBJHAD2P-CqIHE_D7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DpGOa_jT.js → chunk-RYQCIY6F-C-Dl8wzk.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DXJaTynd.js → chunk-XXDRQBXY-CV4Mydz3.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D8Wc3jJP.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D8Wc3jJP.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CRBZWjE4.js → cose-bilkent-JH36ORCC-C3MamSMJ.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BdXcxtlb.js → cynefin-VYW2F7L2-zo_Jy2sf.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-07UKsOts.js → cynefinDiagram-MW4NZA55-BB2PN1AG.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-D38p9__S.js → dagre-VZM6K2ZE-BimYueoi.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-nSiBNNjH.js → diagram-7IWD3JNH-Di1ObJRS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BFOTot5D.js → diagram-B4RE2ZJO-C83l-IJx.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DypKaZ6k.js → diagram-LBJQPF4R-B3S3fOpm.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-B6yoOQeW.js → diagram-Q27KOJAE-urW_Zhe7.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BHYS6viX.js → diagram-UB23O5K3-nvIbCwF6.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-D2VD5Snj.js → ebnfDiagram-BXEA7PRR-DG6O3KpP.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-E98mWxN1.js → erDiagram-JOGREHBK-DpXdtdsj.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CT1zxhqP.js → flowDiagram-UKHOOZJN-DvXWHrND.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-6GcngNcy.js → ganttDiagram-PKOTCBZU-6F7SbvUS.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DCL6NvKe.js → gitGraphDiagram-DS77QQ5N-D1tZnFec.js} +1 -1
- package/dist/worker/console/static/assets/{index-24jp3aBh.js → index-CWarwRqc.js} +94 -94
- package/dist/worker/console/static/assets/{index-CY6y6I8M.css → index-SAANIK8g.css} +1 -1
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-C0627e-9.js → infoDiagram-6WML65LV-HN4qwJ2M.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C_u3P6Ik.js → ishikawaDiagram-WSZJBQD7-OJ4qwDRo.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DPDPv9HL.js → journeyDiagram-NVQOT4AX-CJZ6giQk.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-D3OHdWiO.js → kanban-definition-27J2QSJJ-co60m19S.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DI88zD7n.js → linear-D1KeFqZY.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-p2l8IrWY.js → mermaid.core-i7dDGqov.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-B1bAX-oS.js → mindmap-definition-FAOFIHXS-1SkQxBh1.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DwOnWxdD.js → pegDiagram-VL7TDLO6-B10dhI-Y.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-xRkTXnJC.js → pieDiagram-7S7Q4E2Y-CiUKUo6j.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-_9UC5Zio.js → quadrantDiagram-CIZ2JOQS-C5vH4LXZ.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CgEUirWJ.js → railroadDiagram-AXF67PYL-5nlWOCEj.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-Czbpwiyz.js → requirementDiagram-LRYGKXZP-CILd6QS3.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-_j7_KiqY.js → sankeyDiagram-W5VNT64P-Dfp18ZO2.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BNJa6Hyi.js → sequenceDiagram-SI44F4Z6-B9pkswBi.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DedpKtOc.js → sizeCapture-X5ZJPWSS-C1-eSgx4.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BBvthr7c.js → stateDiagram-OKZ733FA-COJsOoAF.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Bpfb0q5s.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Ds7umgEM.js → swimlanes-SLNWSIFB-Dv5NXJUM.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BEwGGabW.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKa-p1nF.js → timeline-definition-Z64GVDOM-DRdrZ7eR.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-LoUNR56o.js → vennDiagram-T6HMQDX7-0-2TlwCj.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Bqbl6ym7.js → wardleyDiagram-T6FBY63Y-3-o3q92P.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-B8dUg4lf.js → xychartDiagram-ELKLHX3M-DSgOgdUi.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/composer-session-ledger.js +192 -0
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +9 -1
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +78 -35
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +145 -70
- package/dist/worker/console/static-src/operator-chat/useComposer.js +182 -69
- package/dist/workflows/dag/frontend-closeout.js +1 -1
- package/dist/workflows/dag/frontend-prewrite-gate.js +3 -3
- package/dist/workflows/dag/frontend-review-context.js +2 -1
- package/dist/workflows/dag/frontend-review-scopes.js +2 -2
- package/dist/workflows/dag/frontend-verification-trace.js +1 -1
- package/dist/workflows/dag/init-hybrid.js +51 -159
- package/docs/architecture/runtime-boundaries.md +3 -3
- package/docs/templates/frontend-implementation-dag.json +9 -8
- package/package.json +1 -1
- package/skills/frontend-bounded-implement/SKILL.md +3 -3
- package/skills/frontend-bounded-implement/references/code-standards.md +3 -3
- package/skills/frontend-contract/references/contract-protocol.md +3 -1
- package/skills/frontend-design-review/SKILL.md +12 -7
- package/skills/frontend-design-review/references/review-checklist.md +7 -7
- package/skills/frontend-plan/SKILL.md +12 -0
- package/skills/frontend-plan/references/decision-contract.md +25 -19
- package/skills/frontend-review/SKILL.md +12 -18
- package/skills/frontend-review/references/review-findings.md +24 -43
- package/dist/worker/console/static/assets/channel-C0A6UEIa.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DzguNppy.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DzguNppy.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BABVQnfM.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D0qsLaxR.js +0 -8
|
@@ -1,31 +1,70 @@
|
|
|
1
|
-
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState, } from "react";
|
|
2
2
|
import { filterSlashCommands, mergeSlashCommands, } from "../../chat/shortcuts.js";
|
|
3
|
-
import {
|
|
3
|
+
import { ComposerSessionLedger, composerSessionKey, } from "./composer-session-ledger.js";
|
|
4
|
+
import { confirmationToken, requestId } from "./format.js";
|
|
4
5
|
import { clampShortcutIndex } from "./slash-palette-nav.js";
|
|
5
|
-
import { createInputHistoryState, navigateInputHistory, recordInputHistoryEntry, resetInputHistoryBrowsing, } from "./input-history.js";
|
|
6
6
|
import { attachRuntimeSnapshotController } from "./useRuntimeSnapshot.js";
|
|
7
|
+
function sameComposerSession(left, right) {
|
|
8
|
+
if (!left || !right)
|
|
9
|
+
return left === right;
|
|
10
|
+
return composerSessionKey(left) === composerSessionKey(right);
|
|
11
|
+
}
|
|
12
|
+
function readPendingImage(file) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const reader = new FileReader();
|
|
15
|
+
reader.onerror = () => reject(reader.error ?? new Error("图片读取失败"));
|
|
16
|
+
reader.onload = () => {
|
|
17
|
+
const value = String(reader.result ?? "");
|
|
18
|
+
resolve({
|
|
19
|
+
name: file.name,
|
|
20
|
+
type: "image",
|
|
21
|
+
mimeType: file.type,
|
|
22
|
+
data: value.slice(value.indexOf(",") + 1),
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
reader.readAsDataURL(file);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
7
28
|
/** Composer state: input text, @file palette, image attachments, /shortcuts,
|
|
8
29
|
* IME composition guards, auto-grow and server-side draft sync. */
|
|
9
30
|
export function useComposer(params) {
|
|
10
|
-
const { origin,
|
|
11
|
-
const
|
|
31
|
+
const { origin, sessionIdentity, sessionMode, sessionReady = true, refs, setError, onUnrecoverableSession, } = params;
|
|
32
|
+
const sessionId = sessionIdentity?.sessionId;
|
|
33
|
+
const ledgerRef = useRef(null);
|
|
34
|
+
if (!ledgerRef.current)
|
|
35
|
+
ledgerRef.current = new ComposerSessionLedger();
|
|
36
|
+
const ledger = ledgerRef.current;
|
|
37
|
+
const activeIdentityRef = useRef(sessionIdentity);
|
|
38
|
+
activeIdentityRef.current = sessionIdentity;
|
|
39
|
+
const [, forceRender] = useReducer((version) => version + 1, 0);
|
|
40
|
+
const activeSnapshot = sessionIdentity
|
|
41
|
+
? ledger.snapshot(sessionIdentity)
|
|
42
|
+
: { draft: "", images: [] };
|
|
43
|
+
const input = activeSnapshot.draft;
|
|
44
|
+
const pendingImages = activeSnapshot.images;
|
|
12
45
|
const [fileOptions, setFileOptions] = useState([]);
|
|
13
|
-
const [pendingImages, setPendingImages] = useState([]);
|
|
14
46
|
const [shortcutOpen, setShortcutOpen] = useState(false);
|
|
15
47
|
/** Any composer text that starts with `/` should reveal the palette,
|
|
16
48
|
* including draft restore. Escape can still close it without clearing. */
|
|
17
49
|
const setInput = useCallback((value) => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const next = value(prev);
|
|
21
|
-
setShortcutOpen(next.startsWith("/"));
|
|
22
|
-
return next;
|
|
23
|
-
});
|
|
50
|
+
const identity = activeIdentityRef.current;
|
|
51
|
+
if (!identity)
|
|
24
52
|
return;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
53
|
+
const previous = ledger.snapshot(identity).draft;
|
|
54
|
+
const next = typeof value === "function" ? value(previous) : value;
|
|
55
|
+
if (ledger.setDraft(identity, next))
|
|
56
|
+
forceRender();
|
|
57
|
+
setShortcutOpen(next.startsWith("/"));
|
|
58
|
+
}, [ledger]);
|
|
59
|
+
const setPendingImages = useCallback((value) => {
|
|
60
|
+
const identity = activeIdentityRef.current;
|
|
61
|
+
if (!identity)
|
|
62
|
+
return;
|
|
63
|
+
const previous = ledger.snapshot(identity).images;
|
|
64
|
+
const next = typeof value === "function" ? value(previous) : value;
|
|
65
|
+
ledger.setImages(identity, next);
|
|
66
|
+
forceRender();
|
|
67
|
+
}, [ledger]);
|
|
29
68
|
const [shortcutIndex, setShortcutIndex] = useState(0);
|
|
30
69
|
const [dynamicCommands, setDynamicCommands] = useState([]);
|
|
31
70
|
const [commandCapabilities, setCommandCapabilities] = useState({
|
|
@@ -36,30 +75,29 @@ export function useComposer(params) {
|
|
|
36
75
|
const [dynamicCommandsError, setDynamicCommandsError] = useState(null);
|
|
37
76
|
const [commandsEpoch, setCommandsEpoch] = useState(0);
|
|
38
77
|
const textareaRef = useRef(null);
|
|
39
|
-
/** Arrow-key input history; unsent drafts survive history browsing. */
|
|
40
|
-
const historyRef = useRef(createInputHistoryState());
|
|
41
78
|
/** Active session generation for /commands; bumped immediately on switch (AC-2). */
|
|
42
79
|
const commandsSessionRef = useRef(sessionId);
|
|
43
80
|
const commandsGenerationRef = useRef(0);
|
|
44
81
|
const handleInputChange = useCallback((value) => {
|
|
45
82
|
setInput(value);
|
|
46
|
-
setShortcutOpen(value.startsWith("/"));
|
|
47
83
|
setShortcutIndex(0);
|
|
48
|
-
|
|
49
|
-
historyRef.current = resetInputHistoryBrowsing(historyRef.current);
|
|
50
|
-
}, []);
|
|
84
|
+
}, [setInput]);
|
|
51
85
|
/** Recall an earlier input via ↑/↓; returns true when the key was consumed. */
|
|
52
86
|
const navigateHistory = useCallback((direction) => {
|
|
87
|
+
const identity = activeIdentityRef.current;
|
|
88
|
+
if (!identity)
|
|
89
|
+
return false;
|
|
90
|
+
const current = ledger.snapshot(identity).draft;
|
|
53
91
|
const textarea = textareaRef.current;
|
|
54
|
-
const decision =
|
|
55
|
-
text:
|
|
56
|
-
caretStart: textarea?.selectionStart ??
|
|
57
|
-
caretEnd: textarea?.selectionEnd ??
|
|
92
|
+
const decision = ledger.navigateHistory(identity, direction, {
|
|
93
|
+
text: current,
|
|
94
|
+
caretStart: textarea?.selectionStart ?? current.length,
|
|
95
|
+
caretEnd: textarea?.selectionEnd ?? current.length,
|
|
58
96
|
});
|
|
59
97
|
if (!decision.handled)
|
|
60
98
|
return false;
|
|
61
|
-
|
|
62
|
-
|
|
99
|
+
forceRender();
|
|
100
|
+
setShortcutOpen(decision.text.startsWith("/"));
|
|
63
101
|
const caret = decision.text.length;
|
|
64
102
|
queueMicrotask(() => {
|
|
65
103
|
const el = textareaRef.current;
|
|
@@ -68,22 +106,76 @@ export function useComposer(params) {
|
|
|
68
106
|
}
|
|
69
107
|
});
|
|
70
108
|
return true;
|
|
71
|
-
}, [
|
|
72
|
-
/** Record a submitted text into
|
|
109
|
+
}, [ledger]);
|
|
110
|
+
/** Record a submitted text into the selected session's history. */
|
|
73
111
|
const recordHistory = useCallback((text) => {
|
|
74
|
-
|
|
75
|
-
|
|
112
|
+
const identity = activeIdentityRef.current;
|
|
113
|
+
if (identity)
|
|
114
|
+
ledger.recordHistory(identity, text);
|
|
115
|
+
}, [ledger]);
|
|
116
|
+
const claimSubmission = useCallback(() => {
|
|
117
|
+
const identity = activeIdentityRef.current;
|
|
118
|
+
if (!identity)
|
|
119
|
+
return null;
|
|
120
|
+
const attempt = ledger.beginSubmission(identity, refs.sessionGenerationRef.current, requestId("chat-submit"));
|
|
121
|
+
if (!attempt)
|
|
122
|
+
return null;
|
|
123
|
+
forceRender();
|
|
124
|
+
setShortcutOpen(false);
|
|
125
|
+
setShortcutIndex(0);
|
|
126
|
+
return attempt;
|
|
127
|
+
}, [ledger, refs]);
|
|
128
|
+
const claimSessionSubmission = useCallback((identity, text, images = []) => {
|
|
129
|
+
ledger.setDraft(identity, text);
|
|
130
|
+
ledger.setImages(identity, images);
|
|
131
|
+
const attempt = ledger.beginSubmission(identity, refs.sessionGenerationRef.current, requestId("chat-submit"));
|
|
132
|
+
if (sameComposerSession(activeIdentityRef.current, identity))
|
|
133
|
+
forceRender();
|
|
134
|
+
return attempt;
|
|
135
|
+
}, [ledger, refs]);
|
|
136
|
+
const settleSubmission = useCallback((attempt, outcome) => {
|
|
137
|
+
const result = ledger.settle(attempt, outcome);
|
|
138
|
+
if (sameComposerSession(activeIdentityRef.current, attempt.identity))
|
|
139
|
+
forceRender();
|
|
140
|
+
return result;
|
|
141
|
+
}, [ledger]);
|
|
142
|
+
const settleSubmissionByRequest = useCallback((identity, clientRequestId, outcome) => {
|
|
143
|
+
const result = ledger.settleByRequest(identity, clientRequestId, outcome);
|
|
144
|
+
if (sameComposerSession(activeIdentityRef.current, identity))
|
|
145
|
+
forceRender();
|
|
146
|
+
return result;
|
|
147
|
+
}, [ledger]);
|
|
148
|
+
const seedSession = useCallback((identity, draft) => {
|
|
149
|
+
const seeded = ledger.seedDraft(identity, draft);
|
|
150
|
+
if (seeded && sameComposerSession(activeIdentityRef.current, identity))
|
|
151
|
+
forceRender();
|
|
152
|
+
return seeded;
|
|
153
|
+
}, [ledger]);
|
|
154
|
+
const replaceSessionDraft = useCallback((identity, draft, images) => {
|
|
155
|
+
ledger.setDraft(identity, draft);
|
|
156
|
+
ledger.setImages(identity, images);
|
|
157
|
+
if (sameComposerSession(activeIdentityRef.current, identity))
|
|
158
|
+
forceRender();
|
|
159
|
+
}, [ledger]);
|
|
160
|
+
const releaseSession = useCallback((identity) => {
|
|
161
|
+
ledger.release(identity);
|
|
162
|
+
if (sameComposerSession(activeIdentityRef.current, identity))
|
|
163
|
+
forceRender();
|
|
164
|
+
}, [ledger]);
|
|
76
165
|
// Immediate invalidation on A→B even when B palette never opens (AC-2).
|
|
77
166
|
useLayoutEffect(() => {
|
|
78
167
|
commandsSessionRef.current = sessionId;
|
|
79
168
|
commandsGenerationRef.current += 1;
|
|
169
|
+
refs.isComposingRef.current = false;
|
|
170
|
+
setFileOptions([]);
|
|
171
|
+
setShortcutOpen(input.startsWith("/"));
|
|
80
172
|
setDynamicCommands([]);
|
|
81
173
|
setCommandCapabilities({ sddBingo: false, sessionMode });
|
|
82
174
|
setDynamicCommandsError(null);
|
|
83
175
|
setDynamicCommandsLoading(false);
|
|
84
176
|
}, [sessionId, sessionMode]);
|
|
85
177
|
const loadDynamicCommands = useCallback(async () => {
|
|
86
|
-
if (!sessionId) {
|
|
178
|
+
if (!sessionId || !sessionReady) {
|
|
87
179
|
setDynamicCommands([]);
|
|
88
180
|
setDynamicCommandsError(null);
|
|
89
181
|
setDynamicCommandsLoading(false);
|
|
@@ -147,21 +239,21 @@ export function useComposer(params) {
|
|
|
147
239
|
setDynamicCommandsLoading(false);
|
|
148
240
|
}
|
|
149
241
|
}
|
|
150
|
-
}, [origin, sessionId, sessionMode]);
|
|
242
|
+
}, [origin, sessionId, sessionMode, sessionReady]);
|
|
151
243
|
// Lazy-load dynamic commands when slash palette opens or session changes.
|
|
152
244
|
useEffect(() => {
|
|
153
|
-
if (!sessionId)
|
|
245
|
+
if (!sessionId || !sessionReady)
|
|
154
246
|
return;
|
|
155
247
|
if (!shortcutOpen && commandsEpoch === 0)
|
|
156
248
|
return;
|
|
157
249
|
void loadDynamicCommands();
|
|
158
|
-
}, [sessionId, shortcutOpen, commandsEpoch, loadDynamicCommands]);
|
|
250
|
+
}, [sessionId, sessionReady, shortcutOpen, commandsEpoch, loadDynamicCommands]);
|
|
159
251
|
const retryDynamicCommands = useCallback(() => {
|
|
160
252
|
setCommandsEpoch((n) => n + 1);
|
|
161
253
|
void loadDynamicCommands();
|
|
162
254
|
}, [loadDynamicCommands]);
|
|
163
255
|
useEffect(() => {
|
|
164
|
-
if (!sessionId)
|
|
256
|
+
if (!sessionId || !sessionIdentity || !sessionReady)
|
|
165
257
|
return;
|
|
166
258
|
const match = input
|
|
167
259
|
.slice(0, textareaRef.current?.selectionStart ?? input.length)
|
|
@@ -170,13 +262,29 @@ export function useComposer(params) {
|
|
|
170
262
|
setFileOptions([]);
|
|
171
263
|
return;
|
|
172
264
|
}
|
|
265
|
+
const owner = { ...sessionIdentity };
|
|
266
|
+
const ownerKey = composerSessionKey(owner);
|
|
267
|
+
const generation = refs.sessionGenerationRef.current;
|
|
268
|
+
const controller = new AbortController();
|
|
173
269
|
const timer = window.setTimeout(() => {
|
|
174
|
-
void fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}/files?q=${encodeURIComponent(match[1] ?? "")}
|
|
270
|
+
void fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}/files?q=${encodeURIComponent(match[1] ?? "")}`, { signal: controller.signal })
|
|
175
271
|
.then((r) => (r.ok ? r.json() : null))
|
|
176
|
-
.then((body) =>
|
|
272
|
+
.then((body) => {
|
|
273
|
+
const active = activeIdentityRef.current;
|
|
274
|
+
if (controller.signal.aborted ||
|
|
275
|
+
!active ||
|
|
276
|
+
composerSessionKey(active) !== ownerKey ||
|
|
277
|
+
refs.sessionGenerationRef.current !== generation)
|
|
278
|
+
return;
|
|
279
|
+
setFileOptions((body?.files ?? []).map((item) => item.path));
|
|
280
|
+
})
|
|
281
|
+
.catch(() => undefined);
|
|
177
282
|
}, 150);
|
|
178
|
-
return () =>
|
|
179
|
-
|
|
283
|
+
return () => {
|
|
284
|
+
window.clearTimeout(timer);
|
|
285
|
+
controller.abort();
|
|
286
|
+
};
|
|
287
|
+
}, [origin, sessionId, sessionIdentity, sessionReady, input, refs]);
|
|
180
288
|
const insertFileReference = useCallback((file) => {
|
|
181
289
|
const caret = textareaRef.current?.selectionStart ?? input.length;
|
|
182
290
|
const before = input.slice(0, caret);
|
|
@@ -185,33 +293,31 @@ export function useComposer(params) {
|
|
|
185
293
|
return;
|
|
186
294
|
setInput(`${input.slice(0, start)}@${file} ${input.slice(caret)}`);
|
|
187
295
|
setFileOptions([]);
|
|
188
|
-
}, [input]);
|
|
296
|
+
}, [input, setInput]);
|
|
189
297
|
const addImagesFromFiles = useCallback((files) => {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
}, [setError]);
|
|
298
|
+
const owner = activeIdentityRef.current;
|
|
299
|
+
if (!owner)
|
|
300
|
+
return;
|
|
301
|
+
const ownerIdentity = { ...owner };
|
|
302
|
+
const selected = Array.from(files).slice(0, 4);
|
|
303
|
+
const valid = selected.filter((file) => file.type.startsWith("image/") && file.size <= 5 * 1024 * 1024);
|
|
304
|
+
if (valid.length !== selected.length)
|
|
305
|
+
setError("图片仅支持 PNG/JPEG/GIF/WebP,单张不超过 5 MiB");
|
|
306
|
+
void Promise.all(valid.map(readPendingImage)).then((loaded) => {
|
|
307
|
+
if (!ledger.has(ownerIdentity) || loaded.length === 0)
|
|
308
|
+
return;
|
|
309
|
+
const current = ledger.snapshot(ownerIdentity).images;
|
|
310
|
+
ledger.setImages(ownerIdentity, [...current, ...loaded].slice(0, 4));
|
|
311
|
+
if (sameComposerSession(activeIdentityRef.current, ownerIdentity))
|
|
312
|
+
forceRender();
|
|
313
|
+
}, (error) => {
|
|
314
|
+
if (sameComposerSession(activeIdentityRef.current, ownerIdentity))
|
|
315
|
+
setError(error instanceof Error ? error.message : "图片读取失败");
|
|
316
|
+
});
|
|
317
|
+
}, [ledger, setError]);
|
|
212
318
|
const removeImage = useCallback((image) => {
|
|
213
319
|
setPendingImages((items) => items.filter((item) => item !== image));
|
|
214
|
-
}, []);
|
|
320
|
+
}, [setPendingImages]);
|
|
215
321
|
// Chromium handles growth in CSS without forcing a layout read on every key.
|
|
216
322
|
// Keep a deferred fallback for browsers that do not support field-sizing.
|
|
217
323
|
useEffect(() => {
|
|
@@ -231,7 +337,7 @@ export function useComposer(params) {
|
|
|
231
337
|
}, [input]);
|
|
232
338
|
// Debounced server-side composer draft sync.
|
|
233
339
|
useEffect(() => {
|
|
234
|
-
if (!sessionId)
|
|
340
|
+
if (!sessionId || !sessionReady)
|
|
235
341
|
return;
|
|
236
342
|
const timer = window.setTimeout(() => {
|
|
237
343
|
void fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}/draft`, {
|
|
@@ -245,7 +351,7 @@ export function useComposer(params) {
|
|
|
245
351
|
});
|
|
246
352
|
}, 400);
|
|
247
353
|
return () => window.clearTimeout(timer);
|
|
248
|
-
}, [origin, sessionId, input]);
|
|
354
|
+
}, [origin, sessionId, sessionReady, input]);
|
|
249
355
|
const mergedCommands = useMemo(() => mergeSlashCommands(dynamicCommands, commandCapabilities), [dynamicCommands, commandCapabilities]);
|
|
250
356
|
const filteredShortcuts = useMemo(() => input.startsWith("/")
|
|
251
357
|
? filterSlashCommands(mergedCommands, input)
|
|
@@ -279,17 +385,24 @@ export function useComposer(params) {
|
|
|
279
385
|
useEffect(() => {
|
|
280
386
|
attachRuntimeSnapshotController({
|
|
281
387
|
origin,
|
|
282
|
-
sessionId,
|
|
388
|
+
sessionId: sessionReady ? sessionId : undefined,
|
|
283
389
|
refs,
|
|
284
390
|
onUnrecoverableSession,
|
|
285
391
|
});
|
|
286
|
-
}, [origin, sessionId, refs, onUnrecoverableSession]);
|
|
392
|
+
}, [origin, sessionId, sessionReady, refs, onUnrecoverableSession]);
|
|
287
393
|
return {
|
|
288
394
|
input,
|
|
289
395
|
setInput,
|
|
290
396
|
handleInputChange,
|
|
291
397
|
navigateHistory,
|
|
292
398
|
recordHistory,
|
|
399
|
+
claimSubmission,
|
|
400
|
+
claimSessionSubmission,
|
|
401
|
+
settleSubmission,
|
|
402
|
+
settleSubmissionByRequest,
|
|
403
|
+
seedSession,
|
|
404
|
+
replaceSessionDraft,
|
|
405
|
+
releaseSession,
|
|
293
406
|
fileOptions,
|
|
294
407
|
pendingImages,
|
|
295
408
|
setPendingImages,
|
|
@@ -114,7 +114,7 @@ export function buildFrontendTestHandoff(input) {
|
|
|
114
114
|
* required verification evidence. Anything else downgrades to pending.
|
|
115
115
|
*/
|
|
116
116
|
export function deriveFrontendIntegrationFacts(input) {
|
|
117
|
-
const requiredEvidenceSatisfied = input.verificationTargets.
|
|
117
|
+
const requiredEvidenceSatisfied = input.verificationTargets.some((target) => target.mode === "behavior") &&
|
|
118
118
|
input.verificationTargets.every((target) => target.status === "ok");
|
|
119
119
|
if (input.realApiEvidence.present &&
|
|
120
120
|
input.realApiEvidence.passed &&
|
|
@@ -702,10 +702,10 @@ export async function runFrontendPrewriteGate(input) {
|
|
|
702
702
|
failureCode: "mock-strategy-outside-allowed",
|
|
703
703
|
});
|
|
704
704
|
}
|
|
705
|
-
//
|
|
706
|
-
//
|
|
707
|
-
// authorized when none were materialized at generation time.
|
|
705
|
+
// Behavior verification needs bound Mock evidence. Build-only implementation
|
|
706
|
+
// does not claim tested Mock behavior and may defer those commands.
|
|
708
707
|
if (mockStrategy !== "not-needed" &&
|
|
708
|
+
analysis.canonical.verificationTargets.some((target) => target.mode === "behavior") &&
|
|
709
709
|
(input.config.mockCommandLabels?.length ?? 0) === 0) {
|
|
710
710
|
return finalizePrewrite(input, {
|
|
711
711
|
...basePending,
|
|
@@ -246,7 +246,8 @@ function assertReviewEvidence(input) {
|
|
|
246
246
|
const trace = input.verificationTrace;
|
|
247
247
|
const expectedTargets = new Map(contract.data.verificationTargets.map((target) => [target.id, target]));
|
|
248
248
|
const actualTargets = Array.isArray(trace?.targets) ? trace.targets : [];
|
|
249
|
-
const mockEvidenceBound = contract.data.
|
|
249
|
+
const mockEvidenceBound = contract.data.verificationTargets.every((target) => target.mode === "static") ||
|
|
250
|
+
contract.data.mockApi.strategy === "not-needed" ||
|
|
250
251
|
(typeof trace?.mockCommandNodeId === "string" && trace.mockCommandNodeId.length > 0 &&
|
|
251
252
|
Array.isArray(trace?.mockCommandLabels) && trace.mockCommandLabels.length > 0 &&
|
|
252
253
|
Array.isArray(trace?.mockCommandTexts) && trace.mockCommandTexts.length === trace.mockCommandLabels.length &&
|
|
@@ -121,11 +121,11 @@ export function createFrontendReviewScopeProtocol(input) {
|
|
|
121
121
|
committedFacts: () => readCommittedEvents(input.getStore(), input.attemptId),
|
|
122
122
|
assertComplete() { const missing = input.inventory?.scopes.filter(s => !completedScopeIds().has(s.id)) ?? []; if (missing.length)
|
|
123
123
|
throw Object.assign(Error(`FRONTEND_REVIEW_SCOPE_INCOMPLETE: ${missing.map(s => s.id).join(", ")}`), { code: "FRONTEND_REVIEW_SCOPE_INCOMPLETE" }); },
|
|
124
|
-
customTools: !input.inventory ? [] : [{ name: "complete_review_scope", label: "complete_review_scope", description: "After reviewing the supplied complete scope and saving its findings, checkpoint its exact runtime id. This does not approve the implementation or design.", parameters: { type: "object", properties: { id: { type: "string", minLength: 1 } }, required: ["id"], additionalProperties: false },
|
|
124
|
+
customTools: !input.inventory ? [] : [{ name: "complete_review_scope", label: "complete_review_scope", description: "After reviewing the supplied complete scope and saving its findings, checkpoint its exact runtime id. This does not approve the implementation or design.", parameters: { type: "object", properties: { id: { type: "string", minLength: 1, enum: input.inventory.scopes.map(scope => scope.id) } }, required: ["id"], additionalProperties: false },
|
|
125
125
|
async execute(callId, params) {
|
|
126
126
|
const scope = input.inventory.scopes.find(s => s.id === params.id);
|
|
127
127
|
if (!scope || !active.has(params.id))
|
|
128
|
-
return receipt({ ok: false, code: "FRONTEND_INPUT_SCOPE_VIOLATION", error: "Only the supplied active review scope may be completed" });
|
|
128
|
+
return receipt({ ok: false, code: "FRONTEND_INPUT_SCOPE_VIOLATION", error: "Only the supplied active review scope may be completed; use an exact activeScopeIds entry, not the node name", activeScopeIds: [...active], completedScopeIds: [...completedScopeIds()] });
|
|
129
129
|
await input.inventory.validate();
|
|
130
130
|
const store = input.getStore();
|
|
131
131
|
const requestId = `${input.attemptId}:${callId}:${randomUUID()}`;
|
|
@@ -462,7 +462,7 @@ export async function runFrontendVerificationTraceGate(input) {
|
|
|
462
462
|
const mockCommandLabels = input.evidence?.mock?.commandLabels ?? [];
|
|
463
463
|
const mockCommandNodeId = input.evidence?.mock?.nodeId;
|
|
464
464
|
const mockCommandTexts = input.evidence?.mock?.commandTexts ?? [];
|
|
465
|
-
if (input.evidence && contract.mockApi.strategy !== "not-needed" && mockCommandLabels.length === 0) {
|
|
465
|
+
if (input.evidence && contract.verificationTargets.some((target) => target.mode === "behavior") && contract.mockApi.strategy !== "not-needed" && mockCommandLabels.length === 0) {
|
|
466
466
|
throw new Error("trace: selected Mock strategy requires successful Mock verification commands");
|
|
467
467
|
}
|
|
468
468
|
if (input.evidence) {
|