dsh-input-traffic 0.1.0

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.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +202 -0
  3. package/README.md +202 -0
  4. package/cordis.patch.yml +11 -0
  5. package/lib/client.js +920 -0
  6. package/lib/client.js.map +1 -0
  7. package/lib/index.js +5 -0
  8. package/lib/types/client/freeze-button.d.ts +10 -0
  9. package/lib/types/client/freeze-button.d.ts.map +1 -0
  10. package/lib/types/client/freeze-button.js +51 -0
  11. package/lib/types/client/freeze-button.js.map +1 -0
  12. package/lib/types/client/freeze-store.d.ts +24 -0
  13. package/lib/types/client/freeze-store.d.ts.map +1 -0
  14. package/lib/types/client/freeze-store.js +24 -0
  15. package/lib/types/client/freeze-store.js.map +1 -0
  16. package/lib/types/client/hide-enter-row.d.ts +17 -0
  17. package/lib/types/client/hide-enter-row.d.ts.map +1 -0
  18. package/lib/types/client/hide-enter-row.js +5 -0
  19. package/lib/types/client/hide-enter-row.js.map +1 -0
  20. package/lib/types/client/index.d.ts +25 -0
  21. package/lib/types/client/index.d.ts.map +1 -0
  22. package/lib/types/client/index.js +80 -0
  23. package/lib/types/client/index.js.map +1 -0
  24. package/lib/types/client/locales.d.ts +54 -0
  25. package/lib/types/client/locales.d.ts.map +1 -0
  26. package/lib/types/client/locales.js +96 -0
  27. package/lib/types/client/locales.js.map +1 -0
  28. package/lib/types/client/steer-queue-dock.d.ts +59 -0
  29. package/lib/types/client/steer-queue-dock.d.ts.map +1 -0
  30. package/lib/types/client/steer-queue-dock.js +320 -0
  31. package/lib/types/client/steer-queue-dock.js.map +1 -0
  32. package/lib/types/index.d.ts +11 -0
  33. package/lib/types/index.d.ts.map +1 -0
  34. package/lib/types/index.js +5 -0
  35. package/lib/types/index.js.map +1 -0
  36. package/lib/types/invariant.d.ts +12 -0
  37. package/lib/types/invariant.d.ts.map +1 -0
  38. package/lib/types/invariant.js +12 -0
  39. package/lib/types/invariant.js.map +1 -0
  40. package/package.json +96 -0
package/lib/client.js ADDED
@@ -0,0 +1,920 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-input-traffic",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ //#region src/client/locales.ts
11
+ /** `steer` namespace dictionaries. */
12
+ /** Dictionary namespace owned by this plugin. */
13
+ const NS = "steer";
14
+ /** Simplified Chinese dictionary (the key-set source of truth). */
15
+ const zh = {
16
+ "queue.count": "{n} 条排队消息",
17
+ "queue.edit": "编辑排队消息",
18
+ "queue.edit.unsupported": "包含非文本内容,暂不支持编辑",
19
+ "queue.save": "保存排队消息",
20
+ "queue.cancelEdit": "取消编辑",
21
+ "queue.remove": "删除排队消息",
22
+ "queue.removeFailed": "删除失败:这条消息可能已经开始发送。",
23
+ "queue.editFailed": "编辑失败:这条消息可能已经开始发送。",
24
+ "queue.editFailed.pulledBack": "编辑失败,内容已退回主输入框,请确认后重新发送。",
25
+ "steer.inflight": "{n} 条正在插入",
26
+ "steer.now": "打断并输入",
27
+ "steer.now.aria": "红色:打断当前动作并立即输入",
28
+ "steer.now.unsupported": "包含非文本内容,暂不支持打断",
29
+ "steer.nowFailed": "打断失败,请重试。",
30
+ "steer.next": "插话发送",
31
+ "steer.next.aria": "黄色:当前动作结束后插入输入",
32
+ "steer.nextFailed": "插话发送失败,请重试。",
33
+ "steer.later": "排队到下一轮",
34
+ "steer.later.aria": "绿色:排队,上一轮输入的动作都结束后再处理",
35
+ "steer.revoke": "收回排队",
36
+ "steer.revoke.aria": "绿色:撤销插入,收回排队(下一轮处理)",
37
+ "steer.revoke.unsupported": "包含非文本内容,暂不支持收回",
38
+ "steer.revokeFailed": "收回排队失败,请重试。",
39
+ "steer.moveUp": "上移",
40
+ "steer.moveDown": "下移",
41
+ "steer.reorder.unsupported": "包含非文本内容,暂不支持排序",
42
+ "steer.reorderFailed": "调整顺序失败,请重试。",
43
+ "steer.pullBack": "打回输入框编辑",
44
+ "steer.pullBack.unsupported": "包含非文本内容,暂不支持打回编辑",
45
+ "steer.pullBack.composerBusy": "主输入框已有内容,请先发送或清空后再打回",
46
+ "steer.pullBackFailed": "打回输入框失败,请重试。",
47
+ "steer.badge.now": "打断中",
48
+ "steer.badge.next": "插话中",
49
+ "steer.badge.later": "排队",
50
+ "steer.unavailable.running": "仅运行中可规划",
51
+ "steer.clear": "取消并清空",
52
+ "steer.clear.aria": "取消当前执行并清空全部排队消息",
53
+ "steer.clearFailed": "取消并清空失败,请重试。",
54
+ "steer.freeze": "冻结会话",
55
+ "steer.resume": "恢复会话",
56
+ "steer.frozen": "已冻结:当前轮次完成后暂停,排队消息将在恢复后继续",
57
+ "steer.freezeFailed": "冻结失败,请重试。",
58
+ "steer.resumeFailed": "恢复失败,请重试。"
59
+ };
60
+ /** English dictionary (keys mirror zh). */
61
+ const en = {
62
+ "queue.count": "{n} queued messages",
63
+ "queue.edit": "Edit queued message",
64
+ "queue.edit.unsupported": "Contains non-text content; editing is unsupported",
65
+ "queue.save": "Save queued message",
66
+ "queue.cancelEdit": "Cancel edit",
67
+ "queue.remove": "Remove queued message",
68
+ "queue.removeFailed": "Remove failed: the message may have started sending.",
69
+ "queue.editFailed": "Edit failed: the message may have started sending.",
70
+ "queue.editFailed.pulledBack": "Edit failed; the content was moved back to the composer, please review and resend.",
71
+ "steer.inflight": "{n} inserting",
72
+ "steer.now": "Interrupt and send",
73
+ "steer.now.aria": "Red: interrupt the running action and send now",
74
+ "steer.now.unsupported": "Contains non-text content; interrupting is unsupported",
75
+ "steer.nowFailed": "Interrupt failed, please retry.",
76
+ "steer.next": "Send after action",
77
+ "steer.next.aria": "Yellow: insert after the current action finishes",
78
+ "steer.nextFailed": "Steering failed, please retry.",
79
+ "steer.later": "Queue for next turn",
80
+ "steer.later.aria": "Green: queue; processed after all previously steered actions finish",
81
+ "steer.revoke": "Pull back to queue",
82
+ "steer.revoke.aria": "Green: revoke the insertion and queue for the next turn",
83
+ "steer.revoke.unsupported": "Contains non-text content; revoking is unsupported",
84
+ "steer.revokeFailed": "Revoke failed, please retry.",
85
+ "steer.moveUp": "Move up",
86
+ "steer.moveDown": "Move down",
87
+ "steer.reorder.unsupported": "Contains non-text content; reordering is unsupported",
88
+ "steer.reorderFailed": "Reorder failed, please retry.",
89
+ "steer.pullBack": "Edit in composer",
90
+ "steer.pullBack.unsupported": "Contains non-text content; pulling back is unsupported",
91
+ "steer.pullBack.composerBusy": "The composer already has content; send or clear it first",
92
+ "steer.pullBackFailed": "Pull back failed, please retry.",
93
+ "steer.badge.now": "interrupting",
94
+ "steer.badge.next": "steering",
95
+ "steer.badge.later": "queued",
96
+ "steer.unavailable.running": "Planning requires a running session",
97
+ "steer.clear": "Cancel and clear",
98
+ "steer.clear.aria": "Cancel the current run and clear all queued messages",
99
+ "steer.clearFailed": "Cancel and clear failed, please retry.",
100
+ "steer.freeze": "Freeze session",
101
+ "steer.resume": "Resume session",
102
+ "steer.frozen": "Frozen: the current turn finishes, then the queue pauses until resumed",
103
+ "steer.freezeFailed": "Freeze failed, please retry.",
104
+ "steer.resumeFailed": "Resume failed, please retry."
105
+ };
106
+ //#endregion
107
+ //#region src/client/freeze-store.ts
108
+ const listeners = /* @__PURE__ */ new Set();
109
+ let state = {
110
+ frozen: false,
111
+ pending: []
112
+ };
113
+ /** Minimal snapshot store (no runtime dependency, stable identity per mount). */
114
+ const freezeStore = {
115
+ getSnapshot() {
116
+ return state;
117
+ },
118
+ subscribe(listener) {
119
+ listeners.add(listener);
120
+ return () => {
121
+ listeners.delete(listener);
122
+ };
123
+ },
124
+ set(next) {
125
+ state = next;
126
+ for (const listener of listeners) listener();
127
+ }
128
+ };
129
+ //#endregion
130
+ //#region \0dsh-css:E:\test\rewrite-agently\dsh-input-traffic\src\client\steer-queue-dock.module.css.mjs
131
+ const css$1 = ".cev0eq_dock{width:100%}.cev0eq_panel{background:var(--ds-color-surface,#fffc);border:1px solid var(--ds-color-border,#00000014);border-radius:10px;overflow:hidden}.cev0eq_toolbar{border-bottom:1px solid var(--ds-color-border,#0000000f);align-items:center;gap:8px;padding:4px 8px;display:flex}.cev0eq_header{color:var(--ds-color-text-secondary,#6b7280);cursor:pointer;background:0 0;border:0;align-items:center;gap:6px;padding:2px 6px;font-size:12px;display:inline-flex}.cev0eq_header:disabled{cursor:default}.cev0eq_lead{color:var(--ds-color-text-tertiary,#9ca3af);display:inline-flex}.cev0eq_count{font-weight:600}.cev0eq_chevron{color:var(--ds-color-text-tertiary,#9ca3af);display:inline-flex}.cev0eq_toolbarActions{margin-left:auto}.cev0eq_clear{color:var(--ds-color-text-secondary,#6b7280);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px;align-items:center;gap:4px;padding:3px 8px;font-size:12px;display:inline-flex}.cev0eq_freeze{border:1px solid var(--ds-color-border,#0000001f);color:var(--ds-color-text-secondary,#6b7280);cursor:pointer;background:0 0;border-radius:6px;align-items:center;gap:4px;padding:3px 8px;font-size:12px;display:inline-flex}.cev0eq_freeze[aria-pressed=true]{border-color:var(--ds-steer-green,#30a46c);color:var(--ds-steer-green,#30a46c)}.cev0eq_freeze:hover:not(:disabled){background:var(--ds-color-surface-hover,#0000000a)}.cev0eq_freeze:disabled{opacity:.5;cursor:default}.cev0eq_frozenBanner{border-bottom:1px solid var(--ds-color-border,#0000000f);background:color-mix(in srgb, var(--ds-steer-green,#30a46c) 8%, transparent);color:var(--ds-steer-green,#30a46c);padding:4px 10px;font-size:12px}.cev0eq_clear:hover:not(:disabled){border-color:var(--ds-color-border,#0000001f);background:var(--ds-color-surface-hover,#0000000a)}.cev0eq_clear:disabled{opacity:.5;cursor:default}.cev0eq_clearLabel{font-size:12px}.cev0eq_list{margin:0;padding:4px;list-style:none}.cev0eq_row{border-radius:6px;align-items:center;gap:8px;padding:4px 6px;display:flex}.cev0eq_row:hover{background:var(--ds-color-surface-hover,#00000008)}.cev0eq_row[data-editing]{align-items:flex-start}.cev0eq_preview{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--ds-color-text,#1f2937);flex:1;font-size:13px;overflow:hidden}.cev0eq_badge{white-space:nowrap;border-radius:999px;flex:none;align-items:center;gap:4px;padding:1px 6px;font-size:11px;line-height:16px;display:inline-flex}.cev0eq_badgeLabel{color:inherit}.cev0eq_badgeNow{color:var(--ds-steer-red,#e5484d);background:color-mix(in srgb, var(--ds-steer-red,#e5484d) 10%, transparent)}.cev0eq_badgeNext{color:var(--ds-steer-yellow,#e8a33d);background:color-mix(in srgb, var(--ds-steer-yellow,#e8a33d) 12%, transparent)}.cev0eq_badgeLater{color:var(--ds-steer-green,#30a46c);background:color-mix(in srgb, var(--ds-steer-green,#30a46c) 12%, transparent)}.cev0eq_badgeNow .cev0eq_dot{background:var(--ds-steer-red,#e5484d)}.cev0eq_badgeNext .cev0eq_dot{background:var(--ds-steer-yellow,#e8a33d)}.cev0eq_badgeLater .cev0eq_dot{background:var(--ds-steer-green,#30a46c)}.cev0eq_steeringList{margin:0;padding:0 4px 4px;list-style:none}.cev0eq_steeringList .cev0eq_row{opacity:.85}.cev0eq_editor{border:1px solid var(--ds-color-border,#00000026);background:var(--ds-color-surface,#fff);resize:none;border-radius:6px;flex:1;min-width:0;max-height:160px;padding:4px 6px;font-size:13px;line-height:20px;overflow-y:auto}.cev0eq_actions{align-items:center;gap:2px;display:inline-flex}.cev0eq_action{width:24px;height:24px;color:var(--ds-color-text-tertiary,#9ca3af);cursor:pointer;background:0 0;border:0;border-radius:6px;justify-content:center;align-items:center;display:inline-flex}.cev0eq_action:hover:not(:disabled){background:var(--ds-color-surface-hover,#0000000d);color:var(--ds-color-text,#1f2937)}.cev0eq_action:disabled{opacity:.4;cursor:default}.cev0eq_plan{border-left:1px solid var(--ds-color-border,#00000014);align-items:center;gap:2px;margin-left:4px;padding-left:6px;display:inline-flex}.cev0eq_tier{cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px;justify-content:center;align-items:center;width:20px;height:20px;padding:0;display:inline-flex}.cev0eq_tier:hover:not(:disabled){border-color:var(--ds-color-border,#0003)}.cev0eq_tier:disabled{cursor:default}.cev0eq_tierLater{border-color:var(--ds-steer-green,#30a46c);background:color-mix(in srgb, var(--ds-steer-green,#30a46c) 12%, transparent)}.cev0eq_dot{border-radius:50%;width:10px;height:10px}.cev0eq_tierNow .cev0eq_dot{background:var(--ds-steer-red,#e5484d)}.cev0eq_tierNext .cev0eq_dot{background:var(--ds-steer-yellow,#e8a33d)}.cev0eq_tierLater .cev0eq_dot{background:var(--ds-steer-green,#30a46c)}";
132
+ const tagId$1 = "dsh-input-traffic/steer-queue-dock.module.css";
133
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
134
+ const tag = document.createElement("style");
135
+ tag.dataset.plugin = "dsh-input-traffic";
136
+ tag.dataset.pluginCss = tagId$1;
137
+ tag.textContent = css$1;
138
+ document.head.appendChild(tag);
139
+ }
140
+ var steer_queue_dock_module_css_default = {
141
+ "row": "cev0eq_row",
142
+ "clear": "cev0eq_clear",
143
+ "list": "cev0eq_list",
144
+ "tierLater": "cev0eq_tierLater",
145
+ "toolbar": "cev0eq_toolbar",
146
+ "count": "cev0eq_count",
147
+ "action": "cev0eq_action",
148
+ "tierNext": "cev0eq_tierNext",
149
+ "tierNow": "cev0eq_tierNow",
150
+ "panel": "cev0eq_panel",
151
+ "clearLabel": "cev0eq_clearLabel",
152
+ "badgeLabel": "cev0eq_badgeLabel",
153
+ "badgeNow": "cev0eq_badgeNow",
154
+ "dock": "cev0eq_dock",
155
+ "badgeLater": "cev0eq_badgeLater",
156
+ "preview": "cev0eq_preview",
157
+ "frozenBanner": "cev0eq_frozenBanner",
158
+ "plan": "cev0eq_plan",
159
+ "toolbarActions": "cev0eq_toolbarActions",
160
+ "chevron": "cev0eq_chevron",
161
+ "badgeNext": "cev0eq_badgeNext",
162
+ "steeringList": "cev0eq_steeringList",
163
+ "lead": "cev0eq_lead",
164
+ "header": "cev0eq_header",
165
+ "tier": "cev0eq_tier",
166
+ "badge": "cev0eq_badge",
167
+ "editor": "cev0eq_editor",
168
+ "actions": "cev0eq_actions",
169
+ "freeze": "cev0eq_freeze",
170
+ "dot": "cev0eq_dot"
171
+ };
172
+ //#endregion
173
+ //#region src/client/steer-queue-dock.tsx
174
+ /**
175
+ * Three-tier steering queue dock: the shadowing replacement for the official
176
+ * `conversation.input.dock` entry (same cell id `queue`, priority -1, so the
177
+ * lower priority wins and the official QueueDock never renders while this
178
+ * plugin is mounted).
179
+ *
180
+ * Every queued row carries three planning buttons:
181
+ * - green (later, the default state): the row already queues for the next
182
+ * turn —plain Enter keeps feeding rows here; pressing green on a row that
183
+ * was already steered (yellow) revokes the insertion and pulls it back to
184
+ * later (the yellow flow is reversible);
185
+ * - yellow (next): strict-steer the row into the running turn, consumed at
186
+ * the next step boundary (after the current action finishes);
187
+ * - red (now): cancel the running turn, then remove the row and re-send its
188
+ * text as a fresh message —the re-send arms the harness wake latch (the
189
+ * converged driver restarts) and the interrupted input is processed as the
190
+ * next turn's input. A plain steer after cancel would re-insert a message
191
+ * that is already pending in next-turn and the inbox rejects it.
192
+ *
193
+ * The toolbar also exposes a queue-level "cancel and clear" that stops the
194
+ * current run and removes every queued row, plus a session-level
195
+ * freeze/resume toggle for the peak-hour scenario: freeze stops the current
196
+ * run while preserving the queue (keepInbox), resume re-arms the driver by
197
+ * re-sending the first queued row so the preserved work continues.
198
+ */
199
+ /** Busy marker for a whole-queue rebuild (reorder); locks every row action. */
200
+ const REORDER_MARK = "__reorder__";
201
+ /**
202
+ * Project the tier badge of one inbox row from its placement. Pure so the
203
+ * row rendering and its tests share one truth.
204
+ * @param placement - the inbox projection placement.
205
+ * @returns the tier badge, or null for non-visible context rows.
206
+ */
207
+ function badgeFor(placement) {
208
+ if (placement === "steering") return "next";
209
+ if (placement === "queued") return "later";
210
+ return null;
211
+ }
212
+ /**
213
+ * Auto-grow one edit textarea to its content. The CSS max-height caps the
214
+ * growth; beyond it the textarea scrolls internally. Height is reset first so
215
+ * shrink (deleting lines) also tracks the content.
216
+ * @param el - the textarea to resize in place.
217
+ */
218
+ function resizeEditor(el) {
219
+ el.style.height = "auto";
220
+ el.style.height = `${el.scrollHeight}px`;
221
+ }
222
+ /**
223
+ * Queue strip with three-tier planning: one item renders directly; multiple
224
+ * items default to a collapsible count header; an empty queue renders nothing.
225
+ */
226
+ function SteerQueueDock({ useSession, input, updateQueue, cancel, send, setDraft, notify, t }) {
227
+ const inbox = useSession((s) => s.queue);
228
+ const queue = (0, react.useMemo)(() => inbox.filter((row) => row.placement === "queued"), [inbox]);
229
+ const steering = (0, react.useMemo)(() => inbox.filter((row) => row.placement === "steering"), [inbox]);
230
+ const running = useSession((s) => s.running);
231
+ const queueMutable = useSession((s) => s.subagent === null);
232
+ const [editing, setEditing] = (0, react.useState)(null);
233
+ const [busy, setBusy] = (0, react.useState)(null);
234
+ const [clearing, setClearing] = (0, react.useState)(false);
235
+ const [collapsed, setCollapsed] = (0, react.useState)(true);
236
+ const { frozen } = (0, react.useSyncExternalStore)(freezeStore.subscribe, freezeStore.getSnapshot);
237
+ const listId = (0, react.useId)();
238
+ const editorRef = (0, react.useRef)(null);
239
+ (0, react.useEffect)(() => {
240
+ if (queue.length === 0 && !collapsed) setCollapsed(true);
241
+ if (editing !== null && (!queueMutable || !queue.some((row) => row.id === editing.id))) setEditing(null);
242
+ }, [
243
+ collapsed,
244
+ editing,
245
+ queue,
246
+ queueMutable
247
+ ]);
248
+ (0, react.useEffect)(() => {
249
+ if (editing === null) return;
250
+ const el = editorRef.current;
251
+ if (el !== null) resizeEditor(el);
252
+ }, [editing?.id]);
253
+ if (queue.length === 0 && steering.length === 0 && !running) return null;
254
+ const interactionActive = queueMutable && (editing !== null || busy !== null || clearing);
255
+ const expanded = !collapsed || interactionActive;
256
+ const listVisible = queue.length === 1 || expanded;
257
+ const planDisabled = !queueMutable || !running || frozen;
258
+ const nothingPending = queue.length === 0 && steering.length === 0;
259
+ const reorderUnsupported = queue.some((row) => row.text === null);
260
+ const composerEmpty = input.draft.trim() === "";
261
+ const applyAction = async (itemId, action, failure) => {
262
+ setBusy(itemId);
263
+ try {
264
+ await updateQueue(itemId, action);
265
+ return true;
266
+ } catch {
267
+ notify("error", failure);
268
+ return false;
269
+ } finally {
270
+ setBusy((current) => current === itemId ? null : current);
271
+ }
272
+ };
273
+ const saveEdit = async () => {
274
+ if (editing === null || editing.text.trim() === "") return;
275
+ const itemId = editing.id;
276
+ const text = editing.text;
277
+ setBusy(itemId);
278
+ try {
279
+ await updateQueue(itemId, {
280
+ kind: "edit",
281
+ content: [{
282
+ type: "text",
283
+ text
284
+ }]
285
+ });
286
+ setEditing(null);
287
+ } catch {
288
+ if (input.draft.trim() === "") {
289
+ setDraft(text);
290
+ setEditing(null);
291
+ notify("error", t("queue.editFailed.pulledBack"));
292
+ } else notify("error", t("queue.editFailed"));
293
+ } finally {
294
+ setBusy((current) => current === itemId ? null : current);
295
+ }
296
+ };
297
+ const steerRow = async (row, tier) => {
298
+ setBusy(row.id);
299
+ try {
300
+ if (tier === "now") {
301
+ await cancel();
302
+ await updateQueue(row.id, { kind: "remove" });
303
+ if (row.text !== null) await send(row.text);
304
+ } else await updateQueue(row.id, { kind: "steer" });
305
+ } catch {
306
+ notify("error", tier === "now" ? t("steer.nowFailed") : t("steer.nextFailed"));
307
+ } finally {
308
+ setBusy((current) => current === row.id ? null : current);
309
+ }
310
+ };
311
+ /**
312
+ * Revoke a steered (yellow) or interrupting (red) row back to later: remove
313
+ * the admitted row and re-send its text as a queued follow-up, so it lands
314
+ * in next-turn again (the yellow flow is reversible).
315
+ */
316
+ const revokeToLater = async (row) => {
317
+ if (row.text === null) return;
318
+ setBusy(row.id);
319
+ try {
320
+ await updateQueue(row.id, { kind: "remove" });
321
+ await send(row.text);
322
+ } catch {
323
+ notify("error", t("steer.revokeFailed"));
324
+ } finally {
325
+ setBusy((current) => current === row.id ? null : current);
326
+ }
327
+ };
328
+ /**
329
+ * Move one queued row up/down in the FIFO order. The queue is rebuilt in
330
+ * the new order: every queued row is removed and re-sent sequentially
331
+ * (plain-text rows only), so the agent's next-turn list matches the
332
+ * reordered view.
333
+ */
334
+ const reorder = async (rowId, delta) => {
335
+ const rows = queue;
336
+ const index = rows.findIndex((row) => row.id === rowId);
337
+ const target = index + delta;
338
+ if (index < 0 || target < 0 || target >= rows.length) return;
339
+ const next = [...rows];
340
+ const swapped = next[index];
341
+ next[index] = next[target];
342
+ next[target] = swapped;
343
+ if (next.some((row) => row.text === null)) {
344
+ notify("error", t("steer.reorder.unsupported"));
345
+ return;
346
+ }
347
+ setBusy(REORDER_MARK);
348
+ try {
349
+ for (const row of rows) await updateQueue(row.id, { kind: "remove" });
350
+ for (const row of next) await send(row.text);
351
+ } catch {
352
+ notify("error", t("steer.reorderFailed"));
353
+ } finally {
354
+ setBusy(null);
355
+ }
356
+ };
357
+ /**
358
+ * Pull one queued row back into the composer draft for editing: the text
359
+ * back-fills the input box and the row leaves the queue; the user edits and
360
+ * resubmits it as a fresh message.
361
+ */
362
+ const pullBackToComposer = async (row) => {
363
+ if (row.text === null) return;
364
+ setDraft(row.text);
365
+ setBusy(row.id);
366
+ try {
367
+ await updateQueue(row.id, { kind: "remove" });
368
+ } catch {
369
+ notify("error", t("steer.pullBackFailed"));
370
+ } finally {
371
+ setBusy((current) => current === row.id ? null : current);
372
+ }
373
+ };
374
+ const clearAll = async () => {
375
+ setClearing(true);
376
+ const pending = [...queue, ...steering];
377
+ try {
378
+ await cancel();
379
+ await Promise.all(pending.map((row) => updateQueue(row.id, { kind: "remove" }).catch(() => void 0)));
380
+ } catch {
381
+ notify("error", t("steer.clearFailed"));
382
+ } finally {
383
+ setClearing(false);
384
+ }
385
+ };
386
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
387
+ className: steer_queue_dock_module_css_default.dock,
388
+ "data-steer-dock": "",
389
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
390
+ className: steer_queue_dock_module_css_default.panel,
391
+ children: [
392
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
393
+ className: steer_queue_dock_module_css_default.toolbar,
394
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
395
+ type: "button",
396
+ className: steer_queue_dock_module_css_default.header,
397
+ "aria-controls": listId,
398
+ "aria-expanded": expanded,
399
+ disabled: queue.length <= 1 || interactionActive,
400
+ onClick: () => {
401
+ setCollapsed((value) => !value);
402
+ },
403
+ children: [
404
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
405
+ className: steer_queue_dock_module_css_default.lead,
406
+ "aria-hidden": true,
407
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutline14, {})
408
+ }),
409
+ queue.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
410
+ className: steer_queue_dock_module_css_default.count,
411
+ children: t("queue.count", { n: queue.length })
412
+ }),
413
+ queue.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
414
+ className: steer_queue_dock_module_css_default.chevron,
415
+ "aria-hidden": true,
416
+ children: expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, {})
417
+ })
418
+ ]
419
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
420
+ className: steer_queue_dock_module_css_default.toolbarActions,
421
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
422
+ label: t("steer.clear"),
423
+ side: "top",
424
+ delayMs: 500,
425
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
426
+ type: "button",
427
+ className: steer_queue_dock_module_css_default.clear,
428
+ "aria-label": t("steer.clear"),
429
+ disabled: clearing || busy !== null || nothingPending,
430
+ onClick: () => {
431
+ clearAll();
432
+ },
433
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
434
+ className: steer_queue_dock_module_css_default.clearLabel,
435
+ children: t("steer.clear")
436
+ })]
437
+ })
438
+ })
439
+ })]
440
+ }),
441
+ frozen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
442
+ className: steer_queue_dock_module_css_default.frozenBanner,
443
+ role: "status",
444
+ children: t("steer.frozen")
445
+ }),
446
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
447
+ id: listId,
448
+ className: steer_queue_dock_module_css_default.list,
449
+ hidden: !listVisible,
450
+ children: listVisible && queue.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
451
+ className: steer_queue_dock_module_css_default.row,
452
+ "data-tier": badgeFor("queued") ?? void 0,
453
+ "data-editing": editing?.id === row.id ? "" : void 0,
454
+ children: [
455
+ queue.length === 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
456
+ className: steer_queue_dock_module_css_default.lead,
457
+ "aria-hidden": true,
458
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutline14, {})
459
+ }),
460
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SteerBadge, {
461
+ tier: badgeFor("queued"),
462
+ t
463
+ }),
464
+ editing?.id === row.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
465
+ ref: editorRef,
466
+ autoFocus: true,
467
+ className: steer_queue_dock_module_css_default.editor,
468
+ "aria-label": t("queue.edit"),
469
+ rows: 1,
470
+ value: editing.text,
471
+ onChange: (event) => {
472
+ setEditing({
473
+ id: row.id,
474
+ text: event.currentTarget.value
475
+ });
476
+ },
477
+ onInput: (event) => {
478
+ resizeEditor(event.currentTarget);
479
+ },
480
+ onKeyDown: (event) => {
481
+ if (event.key === "Escape") {
482
+ setEditing(null);
483
+ return;
484
+ }
485
+ if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
486
+ event.preventDefault();
487
+ saveEdit();
488
+ }
489
+ }
490
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
491
+ className: steer_queue_dock_module_css_default.preview,
492
+ children: row.preview
493
+ }),
494
+ queueMutable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
495
+ className: steer_queue_dock_module_css_default.actions,
496
+ children: editing?.id === row.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
497
+ label: t("queue.save"),
498
+ side: "bottom",
499
+ delayMs: 500,
500
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
501
+ type: "button",
502
+ className: steer_queue_dock_module_css_default.action,
503
+ "aria-label": t("queue.save"),
504
+ disabled: busy !== null || editing.text.trim() === "",
505
+ onClick: () => {
506
+ saveEdit();
507
+ },
508
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 14 })
509
+ })
510
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
511
+ label: t("queue.cancelEdit"),
512
+ side: "bottom",
513
+ delayMs: 500,
514
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
515
+ type: "button",
516
+ className: steer_queue_dock_module_css_default.action,
517
+ "aria-label": t("queue.cancelEdit"),
518
+ disabled: busy !== null,
519
+ onClick: () => {
520
+ setEditing(null);
521
+ },
522
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
523
+ })
524
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
525
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
526
+ label: t("steer.moveUp"),
527
+ side: "bottom",
528
+ delayMs: 500,
529
+ disabled: frozen || reorderUnsupported,
530
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
531
+ type: "button",
532
+ className: steer_queue_dock_module_css_default.action,
533
+ "aria-label": t("steer.moveUp"),
534
+ title: reorderUnsupported ? t("steer.reorder.unsupported") : void 0,
535
+ disabled: busy !== null || frozen || reorderUnsupported,
536
+ onClick: () => {
537
+ reorder(row.id, -1);
538
+ },
539
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, {})
540
+ })
541
+ }),
542
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
543
+ label: t("steer.moveDown"),
544
+ side: "bottom",
545
+ delayMs: 500,
546
+ disabled: frozen || reorderUnsupported,
547
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
548
+ type: "button",
549
+ className: steer_queue_dock_module_css_default.action,
550
+ "aria-label": t("steer.moveDown"),
551
+ title: reorderUnsupported ? t("steer.reorder.unsupported") : void 0,
552
+ disabled: busy !== null || frozen || reorderUnsupported,
553
+ onClick: () => {
554
+ reorder(row.id, 1);
555
+ },
556
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})
557
+ })
558
+ }),
559
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
560
+ label: t("steer.pullBack"),
561
+ side: "bottom",
562
+ delayMs: 500,
563
+ disabled: row.text === null || !composerEmpty,
564
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
565
+ type: "button",
566
+ className: steer_queue_dock_module_css_default.action,
567
+ "aria-label": t("steer.pullBack"),
568
+ title: row.text === null ? t("steer.pullBack.unsupported") : !composerEmpty ? t("steer.pullBack.composerBusy") : void 0,
569
+ disabled: busy !== null || frozen || row.text === null || !composerEmpty,
570
+ onClick: () => {
571
+ pullBackToComposer(row);
572
+ },
573
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRightUpOutline16, {})
574
+ })
575
+ }),
576
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
577
+ label: t("queue.edit"),
578
+ side: "bottom",
579
+ delayMs: 500,
580
+ disabled: row.text === null,
581
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
582
+ type: "button",
583
+ className: steer_queue_dock_module_css_default.action,
584
+ "aria-label": t("queue.edit"),
585
+ title: row.text === null ? t("queue.edit.unsupported") : void 0,
586
+ disabled: busy !== null || row.text === null,
587
+ onClick: () => {
588
+ if (row.text !== null) setEditing({
589
+ id: row.id,
590
+ text: row.text
591
+ });
592
+ },
593
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 })
594
+ })
595
+ }),
596
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
597
+ label: t("queue.remove"),
598
+ side: "bottom",
599
+ delayMs: 500,
600
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
601
+ type: "button",
602
+ className: steer_queue_dock_module_css_default.action,
603
+ "aria-label": t("queue.remove"),
604
+ disabled: busy !== null,
605
+ onClick: () => {
606
+ applyAction(row.id, { kind: "remove" }, t("queue.removeFailed"));
607
+ },
608
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
609
+ })
610
+ }),
611
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
612
+ className: steer_queue_dock_module_css_default.plan,
613
+ role: "group",
614
+ "aria-label": t("steer.later.aria"),
615
+ children: [
616
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
617
+ label: t("steer.now"),
618
+ side: "bottom",
619
+ delayMs: 500,
620
+ disabled: planDisabled || row.text === null,
621
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
622
+ type: "button",
623
+ className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierNow}`,
624
+ "aria-label": t("steer.now"),
625
+ title: planDisabled ? t("steer.unavailable.running") : row.text === null ? t("steer.now.unsupported") : void 0,
626
+ disabled: busy !== null || planDisabled || row.text === null,
627
+ onClick: () => {
628
+ steerRow(row, "now");
629
+ },
630
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
631
+ className: steer_queue_dock_module_css_default.dot,
632
+ "aria-hidden": true
633
+ })
634
+ })
635
+ }),
636
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
637
+ label: t("steer.next"),
638
+ side: "bottom",
639
+ delayMs: 500,
640
+ disabled: planDisabled,
641
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
642
+ type: "button",
643
+ className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierNext}`,
644
+ "aria-label": t("steer.next"),
645
+ title: planDisabled ? t("steer.unavailable.running") : void 0,
646
+ disabled: busy !== null || planDisabled,
647
+ onClick: () => {
648
+ steerRow(row, "next");
649
+ },
650
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
651
+ className: steer_queue_dock_module_css_default.dot,
652
+ "aria-hidden": true
653
+ })
654
+ })
655
+ }),
656
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
657
+ label: t("steer.later"),
658
+ side: "bottom",
659
+ delayMs: 500,
660
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
661
+ type: "button",
662
+ className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierLater}`,
663
+ "aria-label": t("steer.later"),
664
+ "aria-pressed": true,
665
+ disabled: busy !== null || clearing,
666
+ onClick: () => {},
667
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
668
+ className: steer_queue_dock_module_css_default.dot,
669
+ "aria-hidden": true
670
+ })
671
+ })
672
+ })
673
+ ]
674
+ })
675
+ ] })
676
+ })
677
+ ]
678
+ }, row.id))
679
+ }),
680
+ steering.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
681
+ className: steer_queue_dock_module_css_default.steeringList,
682
+ "data-steering-list": "",
683
+ children: steering.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
684
+ className: steer_queue_dock_module_css_default.row,
685
+ "data-tier": badgeFor("steering") ?? void 0,
686
+ children: [
687
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
688
+ className: steer_queue_dock_module_css_default.lead,
689
+ "aria-hidden": true,
690
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutline14, {})
691
+ }),
692
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SteerBadge, {
693
+ tier: badgeFor("steering"),
694
+ t
695
+ }),
696
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
697
+ className: steer_queue_dock_module_css_default.preview,
698
+ children: row.preview
699
+ }),
700
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
701
+ className: steer_queue_dock_module_css_default.actions,
702
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
703
+ label: t("steer.revoke"),
704
+ side: "bottom",
705
+ delayMs: 500,
706
+ disabled: row.text === null,
707
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
708
+ type: "button",
709
+ className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierLater}`,
710
+ "aria-label": t("steer.revoke"),
711
+ title: row.text === null ? t("steer.revoke.unsupported") : void 0,
712
+ disabled: busy !== null || row.text === null || frozen,
713
+ onClick: () => {
714
+ revokeToLater(row);
715
+ },
716
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
717
+ className: steer_queue_dock_module_css_default.dot,
718
+ "aria-hidden": true
719
+ })
720
+ })
721
+ })
722
+ })
723
+ ]
724
+ }, row.id))
725
+ })
726
+ ]
727
+ })
728
+ });
729
+ }
730
+ /** Badge label keys per tier. */
731
+ const BADGE_KEYS = {
732
+ now: "steer.badge.now",
733
+ next: "steer.badge.next",
734
+ later: "steer.badge.later"
735
+ };
736
+ /** Badge tint classes per tier (present at bundle time; typed through the css-modules declaration). */
737
+ const BADGE_CLASSES = {
738
+ now: steer_queue_dock_module_css_default.badgeNow,
739
+ next: steer_queue_dock_module_css_default.badgeNext,
740
+ later: steer_queue_dock_module_css_default.badgeLater
741
+ };
742
+ /** One row's tier badge: colored dot + label; context rows render nothing. */
743
+ function SteerBadge({ tier, t }) {
744
+ if (tier === null) return null;
745
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
746
+ className: `${steer_queue_dock_module_css_default.badge} ${BADGE_CLASSES[tier]}`,
747
+ "data-badge": tier,
748
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
749
+ className: steer_queue_dock_module_css_default.dot,
750
+ "aria-hidden": true
751
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
752
+ className: steer_queue_dock_module_css_default.badgeLabel,
753
+ children: t(BADGE_KEYS[tier])
754
+ })]
755
+ });
756
+ }
757
+ //#endregion
758
+ //#region \0dsh-css:E:\test\rewrite-agently\dsh-input-traffic\src\client\freeze-button.module.css.mjs
759
+ const css = ".Klmgza_freeze{border:1px solid var(--ds-color-border,#0000001f);height:24px;color:var(--ds-color-text-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(--ds-color-surface-hover,#0000000a)}.Klmgza_freeze[aria-pressed=true]{border-color:var(--ds-steer-green,#30a46c);color:var(--ds-steer-green,#30a46c);background:color-mix(in srgb, var(--ds-steer-green,#30a46c) 8%, transparent)}.Klmgza_label{font-size:12px}";
760
+ const tagId = "dsh-input-traffic/freeze-button.module.css";
761
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
762
+ const tag = document.createElement("style");
763
+ tag.dataset.plugin = "dsh-input-traffic";
764
+ tag.dataset.pluginCss = tagId;
765
+ tag.textContent = css;
766
+ document.head.appendChild(tag);
767
+ }
768
+ var freeze_button_module_css_default = {
769
+ "freeze": "Klmgza_freeze",
770
+ "label": "Klmgza_label"
771
+ };
772
+ //#endregion
773
+ //#region src/client/freeze-button.tsx
774
+ /**
775
+ * Session freeze/resume control, mounted in the composer's right tool row
776
+ * (`conversation.input.right`) so it is reachable before anything queues.
777
+ *
778
+ * Peak-hour pause semantics: freeze does NOT interrupt the running turn —it
779
+ * finishes naturally. The queued messages are detached (removed, preserved in
780
+ * the shared store), so the driver finds no pending work and stops after the
781
+ * current turn. Resume re-submits the preserved texts, waking the driver and
782
+ * continuing the queue.
783
+ */
784
+ /**
785
+ * Freeze/resume toggle for the peak-hour scenario.
786
+ * @param props - slot props; the session snapshot drives the detach list.
787
+ */
788
+ function FreezeButton({ session, updateQueue, send, notify, t }) {
789
+ const { frozen } = (0, react.useSyncExternalStore)(freezeStore.subscribe, freezeStore.getSnapshot);
790
+ const freeze = async () => {
791
+ const queued = session.queue.filter((row) => row.placement === "queued");
792
+ const pending = queued.flatMap((row) => row.text === null ? [] : [row.text]);
793
+ await Promise.all(queued.map((row) => updateQueue(row.id, { kind: "remove" }).catch(() => void 0)));
794
+ freezeStore.set({
795
+ frozen: true,
796
+ pending
797
+ });
798
+ };
799
+ const resume = async () => {
800
+ const pending = freezeStore.getSnapshot().pending;
801
+ freezeStore.set({
802
+ frozen: false,
803
+ pending: []
804
+ });
805
+ try {
806
+ for (const text of pending) await send(text);
807
+ } catch {
808
+ notify("error", t("steer.resumeFailed"));
809
+ }
810
+ };
811
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
812
+ type: "button",
813
+ className: freeze_button_module_css_default.freeze,
814
+ "aria-label": frozen ? t("steer.resume") : t("steer.freeze"),
815
+ "aria-pressed": frozen || void 0,
816
+ title: frozen ? t("steer.frozen") : void 0,
817
+ onClick: () => {
818
+ if (frozen) resume();
819
+ else freeze();
820
+ },
821
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
822
+ className: freeze_button_module_css_default.label,
823
+ children: frozen ? t("steer.resume") : t("steer.freeze")
824
+ })
825
+ });
826
+ }
827
+ //#endregion
828
+ //#region src/client/hide-enter-row.tsx
829
+ /** Render nothing: the official busy-Enter row is hidden while mounted. */
830
+ function HideEnterRow(_props) {
831
+ return null;
832
+ }
833
+ //#endregion
834
+ //#region src/client/index.ts
835
+ /** Durable conversation settings namespace owned by ui-conversation. */
836
+ const CONVERSATION_SETTINGS_NAMESPACE = "ui-conversation";
837
+ /** Busy-Enter field inside that namespace; the plugin pins it to queue. */
838
+ const BUSY_ENTER_FIELD = "busyEnter";
839
+ /** Services required by the browser half. */
840
+ const inject = [
841
+ "slots",
842
+ "locale",
843
+ "sessions",
844
+ "conversation",
845
+ "settingsScope"
846
+ ];
847
+ /**
848
+ * Client plugin body: dictionaries, busy-Enter pinning, and the two slot
849
+ * shadowings.
850
+ * @param ctx - client root context.
851
+ */
852
+ function apply(ctx) {
853
+ ctx.effect(() => ctx.locale.register(NS, {
854
+ zh,
855
+ en
856
+ }), "dsh-input-traffic: dictionaries");
857
+ ctx.settingsScope.bind({ namespace: CONVERSATION_SETTINGS_NAMESPACE }).set(BUSY_ENTER_FIELD, "queue");
858
+ ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
859
+ name: "conversation.input.dock",
860
+ id: "queue",
861
+ order: 20,
862
+ priority: -1,
863
+ locale: NS,
864
+ inject: (sessionId) => {
865
+ const actx = ctx.sessions.scope(sessionId);
866
+ if (actx === void 0) throw new Error(`steer dock: session "${sessionId}" resolved no scope`);
867
+ const conversation = actx.get("conversation");
868
+ if (conversation === void 0) throw new Error("steer dock: conversation service unavailable");
869
+ return {
870
+ updateQueue: (itemId, action) => conversation.updateQueue(itemId, action),
871
+ cancel: () => conversation.cancel(),
872
+ send: (text) => conversation.send(text),
873
+ setDraft: (text) => {
874
+ conversation.input.for(actx).actions.setDraft(text);
875
+ },
876
+ notify: (level, text) => {
877
+ conversation.input.for(actx).notify(level, text);
878
+ }
879
+ };
880
+ }
881
+ }, SteerQueueDock));
882
+ ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
883
+ name: "conversation.input.right",
884
+ id: "steer-freeze",
885
+ order: 30,
886
+ locale: NS,
887
+ inject: (sessionId) => {
888
+ const actx = ctx.sessions.scope(sessionId);
889
+ if (actx === void 0) throw new Error(`steer freeze: session "${sessionId}" resolved no scope`);
890
+ const conversation = actx.get("conversation");
891
+ if (conversation === void 0) throw new Error("steer freeze: conversation service unavailable");
892
+ return {
893
+ updateQueue: (itemId, action) => conversation.updateQueue(itemId, action),
894
+ cancel: () => conversation.cancel(),
895
+ send: (text) => conversation.send(text),
896
+ setDraft: (text) => {
897
+ conversation.input.for(actx).actions.setDraft(text);
898
+ },
899
+ notify: (level, text) => {
900
+ conversation.input.for(actx).notify(level, text);
901
+ }
902
+ };
903
+ }
904
+ }, FreezeButton));
905
+ ctx.slots.inject("settings.general.item", () => ctx.slots.register({
906
+ name: "settings.general.item",
907
+ id: "composer-enter",
908
+ order: 20,
909
+ priority: -1,
910
+ locale: NS
911
+ }, HideEnterRow));
912
+ }
913
+ //#endregion
914
+ exports.apply = apply;
915
+ exports.inject = inject;
916
+ return module.exports;
917
+ }
918
+ });
919
+
920
+ //# sourceMappingURL=client.js.map