dsh-input-traffic 0.2.8 → 0.2.9
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/README.en.md +38 -6
- package/README.md +51 -9
- package/lib/client.js +168 -87
- package/lib/client.js.map +1 -1
- package/lib/types/client/freeze-button.d.ts +1 -1
- package/lib/types/client/freeze-button.d.ts.map +1 -1
- package/lib/types/client/freeze-button.js +43 -13
- package/lib/types/client/freeze-button.js.map +1 -1
- package/lib/types/client/freeze-store.d.ts +17 -10
- package/lib/types/client/freeze-store.d.ts.map +1 -1
- package/lib/types/client/freeze-store.js +54 -38
- package/lib/types/client/freeze-store.js.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +24 -0
- package/lib/types/client/index.js.map +1 -1
- package/lib/types/client/locales.d.ts +1 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +2 -0
- package/lib/types/client/locales.js.map +1 -1
- package/lib/types/client/session-guard-bridge.d.ts +18 -0
- package/lib/types/client/session-guard-bridge.d.ts.map +1 -0
- package/lib/types/client/session-guard-bridge.js +42 -0
- package/lib/types/client/session-guard-bridge.js.map +1 -0
- package/lib/types/client/steer-queue-dock.d.ts +9 -1
- package/lib/types/client/steer-queue-dock.d.ts.map +1 -1
- package/lib/types/client/steer-queue-dock.js +6 -5
- package/lib/types/client/steer-queue-dock.js.map +1 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -58,6 +58,7 @@ window.__ModuleLoader__.load({
|
|
|
58
58
|
"steer.freeze": "冻结会话",
|
|
59
59
|
"steer.resume": "恢复会话",
|
|
60
60
|
"steer.frozen": "已冻结:当前轮次完成后暂停,排队消息将在恢复后继续",
|
|
61
|
+
"steer.frozenInput": "已冻结:输入已暂停,恢复后继续",
|
|
61
62
|
"steer.frozenBadge": "已冻结",
|
|
62
63
|
"steer.freezeFailed": "冻结失败,请重试。",
|
|
63
64
|
"steer.resumeFailed": "恢复失败,请重试。"
|
|
@@ -109,21 +110,27 @@ window.__ModuleLoader__.load({
|
|
|
109
110
|
"steer.freeze": "Freeze session",
|
|
110
111
|
"steer.resume": "Resume session",
|
|
111
112
|
"steer.frozen": "Frozen: the current turn finishes, then the queue pauses until resumed",
|
|
113
|
+
"steer.frozenInput": "Frozen: input paused, resume to continue",
|
|
112
114
|
"steer.frozenBadge": "frozen",
|
|
113
115
|
"steer.freezeFailed": "Freeze failed, please retry.",
|
|
114
116
|
"steer.resumeFailed": "Resume failed, please retry."
|
|
115
117
|
};
|
|
116
118
|
//#endregion
|
|
117
119
|
//#region src/client/freeze-store.ts
|
|
120
|
+
/** Session id → per-session freeze state. */
|
|
121
|
+
const states = /* @__PURE__ */ new Map();
|
|
118
122
|
const listeners = /* @__PURE__ */ new Set();
|
|
119
|
-
|
|
123
|
+
/** Stable empty snapshot: `useSyncExternalStore` needs a reference-stable
|
|
124
|
+
* value for unset sessions so unchanged consumers never re-render. */
|
|
125
|
+
const EMPTY = {
|
|
120
126
|
frozen: false,
|
|
121
127
|
pending: []
|
|
122
128
|
};
|
|
123
129
|
/** Minimal snapshot store (no runtime dependency, stable identity per mount). */
|
|
124
130
|
const freezeStore = {
|
|
125
|
-
|
|
126
|
-
|
|
131
|
+
/** Snapshot for one session; reference-stable until that session changes. */
|
|
132
|
+
getSnapshot(sessionId) {
|
|
133
|
+
return states.get(sessionId) ?? EMPTY;
|
|
127
134
|
},
|
|
128
135
|
subscribe(listener) {
|
|
129
136
|
listeners.add(listener);
|
|
@@ -131,64 +138,67 @@ window.__ModuleLoader__.load({
|
|
|
131
138
|
listeners.delete(listener);
|
|
132
139
|
};
|
|
133
140
|
},
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
141
|
+
/** Replace one session's state; unset sessions fall back to EMPTY. */
|
|
142
|
+
set(sessionId, next) {
|
|
143
|
+
if (next.frozen === false && next.pending.length === 0) states.delete(sessionId);
|
|
144
|
+
else states.set(sessionId, next);
|
|
145
|
+
emit();
|
|
137
146
|
}
|
|
138
147
|
};
|
|
139
148
|
/** Edit one detached queued message's text in place. */
|
|
140
|
-
function updatePendingAt(index, text) {
|
|
141
|
-
const pending =
|
|
142
|
-
if (
|
|
149
|
+
function updatePendingAt(sessionId, index, text) {
|
|
150
|
+
const pending = states.get(sessionId)?.pending;
|
|
151
|
+
if (pending === void 0) return;
|
|
143
152
|
const entry = pending[index];
|
|
144
153
|
if (entry === void 0) return;
|
|
145
|
-
|
|
154
|
+
const next = [...pending];
|
|
155
|
+
next[index] = {
|
|
146
156
|
...entry,
|
|
147
157
|
text
|
|
148
158
|
};
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
pending
|
|
152
|
-
};
|
|
153
|
-
emit();
|
|
159
|
+
freezeStore.set(sessionId, {
|
|
160
|
+
frozen: true,
|
|
161
|
+
pending: next
|
|
162
|
+
});
|
|
154
163
|
}
|
|
155
164
|
/** Change one detached queued message's planned insertion tier. */
|
|
156
|
-
function setTierAt(index, tier) {
|
|
157
|
-
const pending =
|
|
158
|
-
if (
|
|
165
|
+
function setTierAt(sessionId, index, tier) {
|
|
166
|
+
const pending = states.get(sessionId)?.pending;
|
|
167
|
+
if (pending === void 0) return;
|
|
159
168
|
const entry = pending[index];
|
|
160
169
|
if (entry === void 0) return;
|
|
161
|
-
|
|
170
|
+
const next = [...pending];
|
|
171
|
+
next[index] = {
|
|
162
172
|
...entry,
|
|
163
173
|
tier
|
|
164
174
|
};
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
pending
|
|
168
|
-
};
|
|
169
|
-
emit();
|
|
175
|
+
freezeStore.set(sessionId, {
|
|
176
|
+
frozen: true,
|
|
177
|
+
pending: next
|
|
178
|
+
});
|
|
170
179
|
}
|
|
171
180
|
/** Remove one detached queued message. */
|
|
172
|
-
function removePendingAt(index) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
181
|
+
function removePendingAt(sessionId, index) {
|
|
182
|
+
const pending = states.get(sessionId)?.pending;
|
|
183
|
+
if (pending === void 0) return;
|
|
184
|
+
freezeStore.set(sessionId, {
|
|
185
|
+
frozen: true,
|
|
186
|
+
pending: pending.filter((_, i) => i !== index)
|
|
187
|
+
});
|
|
179
188
|
}
|
|
180
189
|
/** Move one detached queued message to a new position (reorder while frozen). */
|
|
181
|
-
function movePending(from, to) {
|
|
190
|
+
function movePending(sessionId, from, to) {
|
|
182
191
|
if (from === to) return;
|
|
183
|
-
const pending =
|
|
184
|
-
|
|
192
|
+
const pending = states.get(sessionId)?.pending;
|
|
193
|
+
if (pending === void 0) return;
|
|
194
|
+
const next = [...pending];
|
|
195
|
+
const [moved] = next.splice(from, 1);
|
|
185
196
|
if (moved === void 0) return;
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
pending
|
|
190
|
-
};
|
|
191
|
-
emit();
|
|
197
|
+
next.splice(to, 0, moved);
|
|
198
|
+
freezeStore.set(sessionId, {
|
|
199
|
+
frozen: true,
|
|
200
|
+
pending: next
|
|
201
|
+
});
|
|
192
202
|
}
|
|
193
203
|
function emit() {
|
|
194
204
|
for (const listener of listeners) listener();
|
|
@@ -205,42 +215,42 @@ window.__ModuleLoader__.load({
|
|
|
205
215
|
document.head.appendChild(tag);
|
|
206
216
|
}
|
|
207
217
|
var steer_queue_dock_module_css_default = {
|
|
208
|
-
"
|
|
209
|
-
"clearCancel": "cev0eq_clearCancel",
|
|
210
|
-
"dock": "cev0eq_dock",
|
|
211
|
-
"badgeNow": "cev0eq_badgeNow",
|
|
212
|
-
"toolbar": "cev0eq_toolbar",
|
|
213
|
-
"frozenRow": "cev0eq_frozenRow",
|
|
214
|
-
"badge": "cev0eq_badge",
|
|
215
|
-
"badgeLabel": "cev0eq_badgeLabel",
|
|
216
|
-
"freeze": "cev0eq_freeze",
|
|
217
|
-
"steeringList": "cev0eq_steeringList",
|
|
218
|
-
"badgeLater": "cev0eq_badgeLater",
|
|
218
|
+
"preview": "cev0eq_preview",
|
|
219
219
|
"action": "cev0eq_action",
|
|
220
|
-
"
|
|
221
|
-
"dot": "cev0eq_dot",
|
|
220
|
+
"badgeLabel": "cev0eq_badgeLabel",
|
|
222
221
|
"frozenList": "cev0eq_frozenList",
|
|
223
|
-
"
|
|
224
|
-
"list": "cev0eq_list",
|
|
225
|
-
"toolbarActions": "cev0eq_toolbarActions",
|
|
226
|
-
"clear": "cev0eq_clear",
|
|
227
|
-
"preview": "cev0eq_preview",
|
|
228
|
-
"tier": "cev0eq_tier",
|
|
229
|
-
"clearLabel": "cev0eq_clearLabel",
|
|
222
|
+
"tierNext": "cev0eq_tierNext",
|
|
230
223
|
"badgeNext": "cev0eq_badgeNext",
|
|
231
224
|
"row": "cev0eq_row",
|
|
232
225
|
"clearConfirm": "cev0eq_clearConfirm",
|
|
233
|
-
"
|
|
234
|
-
"
|
|
235
|
-
"
|
|
236
|
-
"
|
|
226
|
+
"clearLabel": "cev0eq_clearLabel",
|
|
227
|
+
"plan": "cev0eq_plan",
|
|
228
|
+
"rowDragOver": "cev0eq_rowDragOver",
|
|
229
|
+
"actions": "cev0eq_actions",
|
|
230
|
+
"tierNow": "cev0eq_tierNow",
|
|
231
|
+
"badgeLater": "cev0eq_badgeLater",
|
|
232
|
+
"badge": "cev0eq_badge",
|
|
237
233
|
"lead": "cev0eq_lead",
|
|
238
234
|
"header": "cev0eq_header",
|
|
239
|
-
"
|
|
235
|
+
"list": "cev0eq_list",
|
|
236
|
+
"frozenMark": "cev0eq_frozenMark",
|
|
237
|
+
"steeringList": "cev0eq_steeringList",
|
|
238
|
+
"tier": "cev0eq_tier",
|
|
239
|
+
"frozenRow": "cev0eq_frozenRow",
|
|
240
|
+
"clear": "cev0eq_clear",
|
|
241
|
+
"frozenBanner": "cev0eq_frozenBanner",
|
|
242
|
+
"dot": "cev0eq_dot",
|
|
240
243
|
"tierLater": "cev0eq_tierLater",
|
|
244
|
+
"chevron": "cev0eq_chevron",
|
|
245
|
+
"count": "cev0eq_count",
|
|
241
246
|
"panel": "cev0eq_panel",
|
|
242
|
-
"
|
|
243
|
-
"
|
|
247
|
+
"toolbar": "cev0eq_toolbar",
|
|
248
|
+
"toolbarActions": "cev0eq_toolbarActions",
|
|
249
|
+
"editor": "cev0eq_editor",
|
|
250
|
+
"dock": "cev0eq_dock",
|
|
251
|
+
"freeze": "cev0eq_freeze",
|
|
252
|
+
"badgeNow": "cev0eq_badgeNow",
|
|
253
|
+
"clearCancel": "cev0eq_clearCancel"
|
|
244
254
|
};
|
|
245
255
|
//#endregion
|
|
246
256
|
//#region src/client/steer-queue-dock.tsx
|
|
@@ -298,7 +308,7 @@ window.__ModuleLoader__.load({
|
|
|
298
308
|
* Queue strip with three-tier planning: one item renders directly; multiple
|
|
299
309
|
* items default to a collapsible count header; an empty queue renders nothing.
|
|
300
310
|
*/
|
|
301
|
-
function SteerQueueDock({ useSession, input, updateQueue, cancel, send, setDraft, notify, t }) {
|
|
311
|
+
function SteerQueueDock({ sessionId, useSession, input, updateQueue, cancel, send, setDraft, notify, t }) {
|
|
302
312
|
const inbox = useSession((s) => s.queue);
|
|
303
313
|
const queue = (0, react.useMemo)(() => inbox.filter((row) => row.placement === "queued"), [inbox]);
|
|
304
314
|
const steering = (0, react.useMemo)(() => inbox.filter((row) => row.placement === "steering"), [inbox]);
|
|
@@ -319,7 +329,8 @@ window.__ModuleLoader__.load({
|
|
|
319
329
|
return true;
|
|
320
330
|
}
|
|
321
331
|
});
|
|
322
|
-
const { frozen, pending: frozenPending } = (0, react.useSyncExternalStore)(freezeStore.subscribe, freezeStore.getSnapshot);
|
|
332
|
+
const { frozen, pending: frozenPending } = (0, react.useSyncExternalStore)(freezeStore.subscribe, () => freezeStore.getSnapshot(sessionId ?? ""));
|
|
333
|
+
const sid = sessionId ?? "";
|
|
323
334
|
const listId = (0, react.useId)();
|
|
324
335
|
const editorRef = (0, react.useRef)(null);
|
|
325
336
|
(0, react.useEffect)(() => {
|
|
@@ -507,7 +518,7 @@ window.__ModuleLoader__.load({
|
|
|
507
518
|
setEditing(null);
|
|
508
519
|
return;
|
|
509
520
|
}
|
|
510
|
-
updatePendingAt(index, text);
|
|
521
|
+
updatePendingAt(sid, index, text);
|
|
511
522
|
setEditing(null);
|
|
512
523
|
};
|
|
513
524
|
/**
|
|
@@ -635,7 +646,7 @@ window.__ModuleLoader__.load({
|
|
|
635
646
|
const from = dragIndex.current;
|
|
636
647
|
dragIndex.current = null;
|
|
637
648
|
setDragOver(null);
|
|
638
|
-
if (from !== null) movePending(from, i);
|
|
649
|
+
if (from !== null) movePending(sid, from, i);
|
|
639
650
|
},
|
|
640
651
|
onDragEnd: () => {
|
|
641
652
|
dragIndex.current = null;
|
|
@@ -721,7 +732,7 @@ window.__ModuleLoader__.load({
|
|
|
721
732
|
className: steer_queue_dock_module_css_default.action,
|
|
722
733
|
"aria-label": t("steer.moveUp"),
|
|
723
734
|
disabled: i === 0,
|
|
724
|
-
onClick: () => movePending(i, i - 1),
|
|
735
|
+
onClick: () => movePending(sid, i, i - 1),
|
|
725
736
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, {})
|
|
726
737
|
})
|
|
727
738
|
}),
|
|
@@ -735,7 +746,7 @@ window.__ModuleLoader__.load({
|
|
|
735
746
|
className: steer_queue_dock_module_css_default.action,
|
|
736
747
|
"aria-label": t("steer.moveDown"),
|
|
737
748
|
disabled: i === frozenPending.length - 1,
|
|
738
|
-
onClick: () => movePending(i, i + 1),
|
|
749
|
+
onClick: () => movePending(sid, i, i + 1),
|
|
739
750
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})
|
|
740
751
|
})
|
|
741
752
|
}),
|
|
@@ -764,7 +775,7 @@ window.__ModuleLoader__.load({
|
|
|
764
775
|
type: "button",
|
|
765
776
|
className: steer_queue_dock_module_css_default.action,
|
|
766
777
|
"aria-label": t("queue.remove"),
|
|
767
|
-
onClick: () => removePendingAt(i),
|
|
778
|
+
onClick: () => removePendingAt(sid, i),
|
|
768
779
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
|
|
769
780
|
})
|
|
770
781
|
}),
|
|
@@ -782,7 +793,7 @@ window.__ModuleLoader__.load({
|
|
|
782
793
|
className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierNow}`,
|
|
783
794
|
"aria-label": t("steer.now"),
|
|
784
795
|
"aria-pressed": entry.tier === "force" || void 0,
|
|
785
|
-
onClick: () => setTierAt(i, "force"),
|
|
796
|
+
onClick: () => setTierAt(sid, i, "force"),
|
|
786
797
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
787
798
|
className: steer_queue_dock_module_css_default.dot,
|
|
788
799
|
"aria-hidden": true
|
|
@@ -798,7 +809,7 @@ window.__ModuleLoader__.load({
|
|
|
798
809
|
className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierNext}`,
|
|
799
810
|
"aria-label": t("steer.next"),
|
|
800
811
|
"aria-pressed": entry.tier === "safe_point" || void 0,
|
|
801
|
-
onClick: () => setTierAt(i, "safe_point"),
|
|
812
|
+
onClick: () => setTierAt(sid, i, "safe_point"),
|
|
802
813
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
803
814
|
className: steer_queue_dock_module_css_default.dot,
|
|
804
815
|
"aria-hidden": true
|
|
@@ -814,7 +825,7 @@ window.__ModuleLoader__.load({
|
|
|
814
825
|
className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierLater}`,
|
|
815
826
|
"aria-label": t("steer.later"),
|
|
816
827
|
"aria-pressed": entry.tier === "queue" || void 0,
|
|
817
|
-
onClick: () => setTierAt(i, "queue"),
|
|
828
|
+
onClick: () => setTierAt(sid, i, "queue"),
|
|
818
829
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
819
830
|
className: steer_queue_dock_module_css_default.dot,
|
|
820
831
|
"aria-hidden": true
|
|
@@ -1168,6 +1179,45 @@ window.__ModuleLoader__.load({
|
|
|
1168
1179
|
});
|
|
1169
1180
|
}
|
|
1170
1181
|
//#endregion
|
|
1182
|
+
//#region src/client/session-guard-bridge.ts
|
|
1183
|
+
/**
|
|
1184
|
+
* input-traffic ↔ dsh-session-guard 透传桥(D8 fail-open)。
|
|
1185
|
+
*
|
|
1186
|
+
* input-traffic **只做冻结增强**(队列冻结/解冻),服务端会话门(暂停/恢复会话)
|
|
1187
|
+
* 归 dsh-session-guard 插件。冻结/解冻按钮触发时,尽力调用
|
|
1188
|
+
* `sessionGuard.stopNextTurn` / `resume`;**插件未装**(路由 404 / 网络失败 /
|
|
1189
|
+
* 返回错误)→ 静默跳过,**绝不报错**,前端冻结仍正常生效。
|
|
1190
|
+
*
|
|
1191
|
+
* 不吸收任何 auto-continue / 重试逻辑(重试归后端 dsh-session-guard,D9)。
|
|
1192
|
+
*/
|
|
1193
|
+
/** 尽力调用 sessionGuard.stopNextTurn(停掉 session 下一回合)。失败静默。 */
|
|
1194
|
+
async function sessionGuardStopNextTurn(sessionId) {
|
|
1195
|
+
return callGuard(sessionId, "stopNextTurn");
|
|
1196
|
+
}
|
|
1197
|
+
/** 尽力调用 sessionGuard.resume。失败静默。 */
|
|
1198
|
+
async function sessionGuardResume(sessionId) {
|
|
1199
|
+
return callGuard(sessionId, "resume");
|
|
1200
|
+
}
|
|
1201
|
+
async function callGuard(sessionId, action) {
|
|
1202
|
+
try {
|
|
1203
|
+
const res = await fetch("/session-guard/rpc", {
|
|
1204
|
+
method: "POST",
|
|
1205
|
+
headers: { "content-type": "application/json" },
|
|
1206
|
+
body: JSON.stringify({
|
|
1207
|
+
sessionId,
|
|
1208
|
+
action
|
|
1209
|
+
})
|
|
1210
|
+
});
|
|
1211
|
+
if (!res.ok) return false;
|
|
1212
|
+
return (await res.json().catch(() => null))?.ok === true;
|
|
1213
|
+
} catch {
|
|
1214
|
+
return false;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
try {
|
|
1218
|
+
globalThis.__DSH_SESSION_GUARD_BRIDGE__ = true;
|
|
1219
|
+
} catch {}
|
|
1220
|
+
//#endregion
|
|
1171
1221
|
//#region \0dsh-css:E:\test\rewrite-agently\dsh-input-traffic\src\client\freeze-button.module.css.mjs
|
|
1172
1222
|
const css = ".Klmgza_freeze{border:1px solid var(--dsw-alias-border-l3,#0000001f);height:24px;color:var(--dsw-alias-label-secondary,#6b7280);cursor:pointer;white-space:nowrap;background:0 0;border-radius:6px;align-items:center;gap:4px;padding:0 8px;font-size:12px;display:inline-flex}.Klmgza_freeze:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.Klmgza_freeze[aria-pressed=true]{border-color:var(--dsw-alias-state-success-primary,#30a46c);color:var(--dsw-alias-state-success-primary,#30a46c);background:color-mix(in srgb, var(--dsw-alias-state-success-primary,#30a46c) 8%, transparent)}.Klmgza_label{font-size:12px}";
|
|
1173
1223
|
const tagId = "dsh-input-traffic/freeze-button.module.css";
|
|
@@ -1193,35 +1243,48 @@ window.__ModuleLoader__.load({
|
|
|
1193
1243
|
* the shared store), so the driver finds no pending work and stops after the
|
|
1194
1244
|
* current turn. Resume re-submits the preserved texts, waking the driver and
|
|
1195
1245
|
* continuing the queue.
|
|
1246
|
+
*
|
|
1247
|
+
* dsh-session-guard 协作(D8 fail-open):
|
|
1248
|
+
* - 冻结 = 前端摘队列(本插件)+ composer block(阻止新输入漏进对话) + 尽力调服务端
|
|
1249
|
+
* sessionGuard.stopNextTurn(停掉 session 下一回合;session-guard 未装时静默跳过)。
|
|
1250
|
+
* - 解冻 = 清 composer block + **先** await sessionGuard.resume(让被打断回合的自然
|
|
1251
|
+
* 下一步先发生),**再**重投队列(later 级条目因此排在自然 next turn 之后,不再插队)。
|
|
1252
|
+
* - 本插件只做冻结增强,不承担重试/暂停决策(归后端,D9)。
|
|
1196
1253
|
*/
|
|
1197
1254
|
/**
|
|
1198
1255
|
* Freeze/resume toggle for the peak-hour scenario.
|
|
1199
1256
|
* @param props - slot props; the session snapshot drives the detach list.
|
|
1200
1257
|
*/
|
|
1201
|
-
function FreezeButton({ session, updateQueue, cancel, send, notify, t }) {
|
|
1202
|
-
const
|
|
1258
|
+
function FreezeButton({ session, updateQueue, cancel, send, sendSteer, sessionId, setComposerBlock, notify, t }) {
|
|
1259
|
+
const sid = sessionId ?? "";
|
|
1260
|
+
const { frozen } = (0, react.useSyncExternalStore)(freezeStore.subscribe, () => freezeStore.getSnapshot(sid));
|
|
1203
1261
|
const freeze = async () => {
|
|
1204
|
-
const
|
|
1205
|
-
const pending =
|
|
1262
|
+
const rows = session.queue.filter((row) => row.placement === "queued" || row.placement === "steering");
|
|
1263
|
+
const pending = rows.flatMap((row) => row.text === null ? [] : [{
|
|
1206
1264
|
text: row.text,
|
|
1207
|
-
tier: "queue"
|
|
1265
|
+
tier: row.placement === "steering" ? "safe_point" : "queue"
|
|
1208
1266
|
}]);
|
|
1209
|
-
await Promise.all(
|
|
1210
|
-
freezeStore.set({
|
|
1267
|
+
await Promise.all(rows.map((row) => updateQueue(row.id, { kind: "remove" }).catch(() => void 0)));
|
|
1268
|
+
freezeStore.set(sid, {
|
|
1211
1269
|
frozen: true,
|
|
1212
1270
|
pending
|
|
1213
1271
|
});
|
|
1272
|
+
setComposerBlock?.(t("steer.frozenInput"));
|
|
1273
|
+
if (sessionId !== void 0) sessionGuardStopNextTurn(sessionId);
|
|
1214
1274
|
};
|
|
1215
1275
|
const resume = async () => {
|
|
1216
|
-
const pending = freezeStore.getSnapshot().pending;
|
|
1217
|
-
freezeStore.set({
|
|
1276
|
+
const pending = freezeStore.getSnapshot(sid).pending;
|
|
1277
|
+
freezeStore.set(sid, {
|
|
1218
1278
|
frozen: false,
|
|
1219
1279
|
pending: []
|
|
1220
1280
|
});
|
|
1281
|
+
setComposerBlock?.(void 0);
|
|
1221
1282
|
try {
|
|
1283
|
+
if (sessionId !== void 0) await sessionGuardResume(sessionId);
|
|
1222
1284
|
for (const entry of pending) {
|
|
1223
1285
|
if (entry.tier === "force") await cancel();
|
|
1224
|
-
await
|
|
1286
|
+
if (entry.tier === "safe_point" && sendSteer !== void 0) await sendSteer(entry.text);
|
|
1287
|
+
else await send(entry.text);
|
|
1225
1288
|
}
|
|
1226
1289
|
} catch {
|
|
1227
1290
|
notify("error", t("steer.resumeFailed"));
|
|
@@ -1255,6 +1318,19 @@ window.__ModuleLoader__.load({
|
|
|
1255
1318
|
const CONVERSATION_SETTINGS_NAMESPACE = "ui-conversation";
|
|
1256
1319
|
/** Busy-Enter field inside that namespace; the plugin pins it to queue. */
|
|
1257
1320
|
const BUSY_ENTER_FIELD = "busyEnter";
|
|
1321
|
+
/**
|
|
1322
|
+
* Deliver one plain-text message into the session's next step. The exposed
|
|
1323
|
+
* conversation `send` verb only queues into the next turn, so resume steers
|
|
1324
|
+
* through the session face's steer-mode prompt instead (no harness change).
|
|
1325
|
+
* @param ctx - root context (resolves the session face behind the scope).
|
|
1326
|
+
* @param actx - agent-scoped context of the owning session.
|
|
1327
|
+
* @param text - message text to deliver.
|
|
1328
|
+
*/
|
|
1329
|
+
function steerPrompt(actx, text) {
|
|
1330
|
+
const conversation = actx.get("conversation");
|
|
1331
|
+
if (conversation === void 0) return Promise.reject(/* @__PURE__ */ new Error("steer resume: conversation service unavailable"));
|
|
1332
|
+
return conversation.send(text);
|
|
1333
|
+
}
|
|
1258
1334
|
/** Services required by the browser half. */
|
|
1259
1335
|
const inject = [
|
|
1260
1336
|
"slots",
|
|
@@ -1312,11 +1388,16 @@ window.__ModuleLoader__.load({
|
|
|
1312
1388
|
updateQueue: (itemId, action) => conversation.updateQueue(itemId, action),
|
|
1313
1389
|
cancel: () => conversation.cancel(),
|
|
1314
1390
|
send: (text) => conversation.send(text),
|
|
1391
|
+
sendSteer: (text) => steerPrompt(actx, text),
|
|
1392
|
+
sessionId: String(sessionId),
|
|
1315
1393
|
setDraft: (text) => {
|
|
1316
1394
|
conversation.input.for(actx).actions.setDraft(text);
|
|
1317
1395
|
},
|
|
1318
1396
|
notify: (level, text) => {
|
|
1319
1397
|
conversation.input.for(actx).notify(level, text);
|
|
1398
|
+
},
|
|
1399
|
+
setComposerBlock: (reason) => {
|
|
1400
|
+
conversation.blocks.set(sessionId, reason === void 0 ? void 0 : { reason });
|
|
1320
1401
|
}
|
|
1321
1402
|
};
|
|
1322
1403
|
}
|