dsh-input-traffic 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -1,1434 +1,1493 @@
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` client dictionaries (zh / en / ja / ko). */
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.reorderStale": "队列已变化,本次排序已取消,请重试。",
44
- "steer.dragReorder": "拖动调整顺序",
45
- "steer.pullBack": "打回输入框编辑",
46
- "steer.pullBack.unsupported": "包含非文本内容,暂不支持打回编辑",
47
- "steer.pullBack.composerBusy": "主输入框已有内容,请先发送或清空后再打回",
48
- "steer.pullBackFailed": "打回输入框失败,请重试。",
49
- "steer.badge.now": "打断中",
50
- "steer.badge.next": "插话中",
51
- "steer.badge.later": "排队",
52
- "steer.unavailable.running": "仅运行中可规划",
53
- "steer.clear": "取消并清空",
54
- "steer.clear.confirm": "确认清空?",
55
- "steer.clear.cancel": "取消清空",
56
- "steer.clear.aria": "取消当前执行并清空全部排队消息",
57
- "steer.clearFailed": "取消并清空失败,请重试。",
58
- "steer.freeze": "冻结追加",
59
- "steer.resume": "恢复追加",
60
- "steer.frozen": "已冻结:当前轮次完成后暂停,排队消息将在恢复后继续",
61
- "steer.frozenInput": "已冻结:输入已暂停,恢复后继续",
62
- "steer.frozenBadge": "已冻结",
63
- "steer.freezeFailed": "冻结失败,请重试。",
64
- "steer.resumeFailed": "恢复失败,请重试。"
65
- };
66
- /** English dictionary (keys mirror zh). */
67
- const en = {
68
- "queue.count": "{n} queued messages",
69
- "queue.edit": "Edit queued message",
70
- "queue.edit.unsupported": "Contains non-text content; editing is unsupported",
71
- "queue.save": "Save queued message",
72
- "queue.cancelEdit": "Cancel edit",
73
- "queue.remove": "Remove queued message",
74
- "queue.removeFailed": "Remove failed: the message may have started sending.",
75
- "queue.editFailed": "Edit failed: the message may have started sending.",
76
- "queue.editFailed.pulledBack": "Edit failed; the content was moved back to the composer, please review and resend.",
77
- "steer.inflight": "{n} inserting",
78
- "steer.now": "Interrupt and send",
79
- "steer.now.aria": "Red: interrupt the running action and send now",
80
- "steer.now.unsupported": "Contains non-text content; interrupting is unsupported",
81
- "steer.nowFailed": "Interrupt failed, please retry.",
82
- "steer.next": "Send after action",
83
- "steer.next.aria": "Yellow: insert after the current action finishes",
84
- "steer.nextFailed": "Steering failed, please retry.",
85
- "steer.later": "Queue for next turn",
86
- "steer.later.aria": "Green: queue; processed after all previously steered actions finish",
87
- "steer.revoke": "Pull back to queue",
88
- "steer.revoke.aria": "Green: revoke the insertion and queue for the next turn",
89
- "steer.revoke.unsupported": "Contains non-text content; revoking is unsupported",
90
- "steer.revokeFailed": "Revoke failed, please retry.",
91
- "steer.moveUp": "Move up",
92
- "steer.moveDown": "Move down",
93
- "steer.reorder.unsupported": "Contains non-text content; reordering is unsupported",
94
- "steer.reorderFailed": "Reorder failed, please retry.",
95
- "steer.reorderStale": "The queue changed; this reorder was cancelled, please retry.",
96
- "steer.dragReorder": "Drag to reorder",
97
- "steer.pullBack": "Edit in composer",
98
- "steer.pullBack.unsupported": "Contains non-text content; pulling back is unsupported",
99
- "steer.pullBack.composerBusy": "The composer already has content; send or clear it first",
100
- "steer.pullBackFailed": "Pull back failed, please retry.",
101
- "steer.badge.now": "interrupting",
102
- "steer.badge.next": "steering",
103
- "steer.badge.later": "queued",
104
- "steer.unavailable.running": "Planning requires a running session",
105
- "steer.clear": "Cancel and clear",
106
- "steer.clear.confirm": "Confirm clear?",
107
- "steer.clear.cancel": "Cancel clearing",
108
- "steer.clear.aria": "Cancel the current run and clear all queued messages",
109
- "steer.clearFailed": "Cancel and clear failed, please retry.",
110
- "steer.freeze": "Freeze & append",
111
- "steer.resume": "Resume & append",
112
- "steer.frozen": "Frozen: the current turn finishes, then the queue pauses until resumed",
113
- "steer.frozenInput": "Frozen: input paused, resume to continue",
114
- "steer.frozenBadge": "frozen",
115
- "steer.freezeFailed": "Freeze failed, please retry.",
116
- "steer.resumeFailed": "Resume failed, please retry."
117
- };
118
- //#endregion
119
- //#region src/client/freeze-store.ts
120
- /** Session id → per-session freeze state. */
121
- const states = /* @__PURE__ */ new Map();
122
- const listeners = /* @__PURE__ */ new Set();
123
- /** Stable empty snapshot: `useSyncExternalStore` needs a reference-stable
124
- * value for unset sessions so unchanged consumers never re-render. */
125
- const EMPTY = {
126
- frozen: false,
127
- pending: []
128
- };
129
- /** Minimal snapshot store (no runtime dependency, stable identity per mount). */
130
- const freezeStore = {
131
- /** Snapshot for one session; reference-stable until that session changes. */
132
- getSnapshot(sessionId) {
133
- return states.get(sessionId) ?? EMPTY;
134
- },
135
- subscribe(listener) {
136
- listeners.add(listener);
137
- return () => {
138
- listeners.delete(listener);
139
- };
140
- },
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();
146
- }
147
- };
148
- /** Edit one detached queued message's text in place. */
149
- function updatePendingAt(sessionId, index, text) {
150
- const pending = states.get(sessionId)?.pending;
151
- if (pending === void 0) return;
152
- const entry = pending[index];
153
- if (entry === void 0) return;
154
- const next = [...pending];
155
- next[index] = {
156
- ...entry,
157
- text
158
- };
159
- freezeStore.set(sessionId, {
160
- frozen: true,
161
- pending: next
162
- });
163
- }
164
- /** Change one detached queued message's planned insertion tier. */
165
- function setTierAt(sessionId, index, tier) {
166
- const pending = states.get(sessionId)?.pending;
167
- if (pending === void 0) return;
168
- const entry = pending[index];
169
- if (entry === void 0) return;
170
- const next = [...pending];
171
- next[index] = {
172
- ...entry,
173
- tier
174
- };
175
- freezeStore.set(sessionId, {
176
- frozen: true,
177
- pending: next
178
- });
179
- }
180
- /** Remove one detached queued message. */
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
- });
188
- }
189
- /** Move one detached queued message to a new position (reorder while frozen). */
190
- function movePending(sessionId, from, to) {
191
- if (from === to) return;
192
- const pending = states.get(sessionId)?.pending;
193
- if (pending === void 0) return;
194
- const next = [...pending];
195
- const [moved] = next.splice(from, 1);
196
- if (moved === void 0) return;
197
- next.splice(to, 0, moved);
198
- freezeStore.set(sessionId, {
199
- frozen: true,
200
- pending: next
201
- });
202
- }
203
- function emit() {
204
- for (const listener of listeners) listener();
205
- }
206
- //#endregion
207
- //#region \0dsh-css:E:\test\rewrite-agently\mine-dsh-plugins\dsh-input-traffic\src\client\steer-queue-dock.module.css.mjs
208
- const css$1 = ".FNESVa_dock{box-sizing:border-box;width:min(calc(100% - var(--dsh-composer-side-clearance,16px) - var(--dsh-composer-side-clearance,16px) - var(--dsh-composer-dock-inset,8px) - var(--dsh-composer-dock-inset,8px)), calc(var(--dsh-chat-content-width,100%) - var(--dsh-composer-dock-inset,8px) - var(--dsh-composer-dock-inset,8px)));max-width:calc(var(--dsh-composer-card-max-width,var(--dsh-chat-content-width,100%)) - var(--dsh-composer-dock-inset,8px) - var(--dsh-composer-dock-inset,8px));padding:0 var(--dsh-composer-dock-inset,8px);flex:none;margin:0 auto}.FNESVa_panel{background:var(--dsw-alias-bg-layer-1,#fffc);border:1px solid var(--dsw-alias-border-l2,#00000014);border-radius:10px;overflow:hidden}.FNESVa_toolbar{border-bottom:1px solid var(--dsw-alias-border-l2,#0000000f);align-items:center;gap:8px;padding:4px 8px;display:flex}.FNESVa_header{color:var(--dsw-alias-label-secondary,#6b7280);cursor:pointer;background:0 0;border:0;align-items:center;gap:6px;padding:2px 6px;font-size:12px;display:inline-flex}.FNESVa_header:disabled{cursor:default}.FNESVa_lead{color:var(--dsw-alias-label-tertiary,#9ca3af);display:inline-flex}.FNESVa_count{font-weight:600}.FNESVa_chevron{color:var(--dsw-alias-label-tertiary,#9ca3af);display:inline-flex}.FNESVa_toolbarActions{margin-left:auto}.FNESVa_clear{color:var(--dsw-alias-label-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}.FNESVa_freeze{border:1px solid var(--dsw-alias-border-l3,#0000001f);color:var(--dsw-alias-label-secondary,#6b7280);cursor:pointer;background:0 0;border-radius:6px;align-items:center;gap:4px;padding:3px 8px;font-size:12px;display:inline-flex}.FNESVa_freeze[aria-pressed=true]{border-color:var(--dsw-alias-state-success-primary,#30a46c);color:var(--dsw-alias-state-success-primary,#30a46c)}.FNESVa_freeze:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.FNESVa_freeze:disabled{opacity:.5;cursor:default}.FNESVa_frozenBanner{border-bottom:1px solid var(--dsw-alias-border-l2,#0000000f);background:color-mix(in srgb, var(--dsw-alias-state-success-primary,#30a46c) 8%, transparent);color:var(--dsw-alias-state-success-primary,#30a46c);padding:4px 10px;font-size:12px}.FNESVa_frozenList{margin:0;padding:4px;list-style:none}.FNESVa_frozenRow{border-radius:6px;align-items:center;gap:8px;padding:4px 6px;display:flex}.FNESVa_frozenMark{color:var(--dsw-alias-label-tertiary,#9ca3af);flex:none;margin-left:auto;font-size:11px}.FNESVa_clear:hover:not(:disabled){border-color:var(--dsw-alias-border-l3,#0000001f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.FNESVa_clear:disabled{opacity:.5;cursor:default}.FNESVa_clearConfirm{border-color:var(--dsw-alias-state-error-primary,#e5484d);color:var(--dsw-alias-state-error-primary,#e5484d)}.FNESVa_clearConfirm:hover:not(:disabled){border-color:var(--dsw-alias-state-error-primary,#e5484d);background:color-mix(in srgb, var(--dsw-alias-state-error-primary,#e5484d) 8%, transparent)}.FNESVa_clearCancel{color:var(--dsw-alias-label-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}.FNESVa_clearCancel:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.FNESVa_clearLabel{font-size:12px}.FNESVa_list{margin:0;padding:4px;list-style:none}.FNESVa_row{border-radius:6px;align-items:center;gap:8px;padding:4px 6px;display:flex}.FNESVa_row[draggable=true]{cursor:grab}.FNESVa_row[draggable=true]:active{cursor:grabbing}.FNESVa_rowDragOver{outline:2px dashed var(--dsw-alias-border-l4,#00000040);outline-offset:-2px;background:var(--dsw-alias-interactive-bg-hover,#0000000d)}.FNESVa_row:hover{background:var(--dsw-alias-interactive-bg-hover,#00000008)}.FNESVa_row[data-editing]{align-items:flex-start}.FNESVa_preview{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary,#1f2937);flex:1;font-size:13px;overflow:hidden}.FNESVa_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}.FNESVa_badgeLabel{color:inherit}.FNESVa_badgeNow{color:var(--dsw-alias-state-error-primary,#e5484d);background:color-mix(in srgb, var(--dsw-alias-state-error-primary,#e5484d) 10%, transparent)}.FNESVa_badgeNext{color:var(--dsw-alias-state-warn-primary,#e8a33d);background:color-mix(in srgb, var(--dsw-alias-state-warn-primary,#e8a33d) 12%, transparent)}.FNESVa_badgeLater{color:var(--dsw-alias-state-success-primary,#30a46c);background:color-mix(in srgb, var(--dsw-alias-state-success-primary,#30a46c) 12%, transparent)}.FNESVa_badgeNow .FNESVa_dot{background:var(--dsw-alias-state-error-primary,#e5484d)}.FNESVa_badgeNext .FNESVa_dot{background:var(--dsw-alias-state-warn-primary,#e8a33d)}.FNESVa_badgeLater .FNESVa_dot{background:var(--dsw-alias-state-success-primary,#30a46c)}.FNESVa_steeringList{margin:0;padding:0 4px 4px;list-style:none}.FNESVa_steeringList .FNESVa_row{opacity:.85}.FNESVa_editor{border:1px solid var(--dsw-alias-border-l3,#00000026);background:var(--dsw-alias-bg-layer-2,#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}.FNESVa_actions{align-items:center;gap:2px;display:inline-flex}.FNESVa_action{width:24px;height:24px;color:var(--dsw-alias-label-tertiary,#9ca3af);cursor:pointer;background:0 0;border:0;border-radius:6px;justify-content:center;align-items:center;display:inline-flex}.FNESVa_action:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-primary,#1f2937)}.FNESVa_action:disabled{opacity:.4;cursor:default}.FNESVa_plan{border-left:1px solid var(--dsw-alias-border-l2,#00000014);align-items:center;gap:2px;margin-left:4px;padding-left:6px;display:inline-flex}.FNESVa_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}.FNESVa_tier:hover:not(:disabled){border-color:var(--dsw-alias-border-l4,#0003)}.FNESVa_tier:disabled{cursor:default}.FNESVa_tierLater{border-color:var(--dsw-alias-state-success-primary,#30a46c);background:color-mix(in srgb, var(--dsw-alias-state-success-primary,#30a46c) 12%, transparent)}.FNESVa_dot{border-radius:50%;width:10px;height:10px}.FNESVa_tierNow .FNESVa_dot{background:var(--dsw-alias-state-error-primary,#e5484d)}.FNESVa_tierNext .FNESVa_dot{background:var(--dsw-alias-state-warn-primary,#e8a33d)}.FNESVa_tierLater .FNESVa_dot{background:var(--dsw-alias-state-success-primary,#30a46c)}";
209
- const tagId$1 = "dsh-input-traffic/steer-queue-dock.module.css";
210
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
211
- const tag = document.createElement("style");
212
- tag.dataset.plugin = "dsh-input-traffic";
213
- tag.dataset.pluginCss = tagId$1;
214
- tag.textContent = css$1;
215
- document.head.appendChild(tag);
216
- }
217
- var steer_queue_dock_module_css_default = {
218
- "header": "FNESVa_header",
219
- "list": "FNESVa_list",
220
- "panel": "FNESVa_panel",
221
- "lead": "FNESVa_lead",
222
- "plan": "FNESVa_plan",
223
- "tierLater": "FNESVa_tierLater",
224
- "tierNext": "FNESVa_tierNext",
225
- "toolbar": "FNESVa_toolbar",
226
- "toolbarActions": "FNESVa_toolbarActions",
227
- "badge": "FNESVa_badge",
228
- "badgeLater": "FNESVa_badgeLater",
229
- "clearLabel": "FNESVa_clearLabel",
230
- "row": "FNESVa_row",
231
- "frozenRow": "FNESVa_frozenRow",
232
- "action": "FNESVa_action",
233
- "rowDragOver": "FNESVa_rowDragOver",
234
- "steeringList": "FNESVa_steeringList",
235
- "dot": "FNESVa_dot",
236
- "badgeLabel": "FNESVa_badgeLabel",
237
- "clear": "FNESVa_clear",
238
- "dock": "FNESVa_dock",
239
- "frozenBanner": "FNESVa_frozenBanner",
240
- "tier": "FNESVa_tier",
241
- "badgeNow": "FNESVa_badgeNow",
242
- "frozenMark": "FNESVa_frozenMark",
243
- "chevron": "FNESVa_chevron",
244
- "clearConfirm": "FNESVa_clearConfirm",
245
- "actions": "FNESVa_actions",
246
- "frozenList": "FNESVa_frozenList",
247
- "tierNow": "FNESVa_tierNow",
248
- "clearCancel": "FNESVa_clearCancel",
249
- "count": "FNESVa_count",
250
- "preview": "FNESVa_preview",
251
- "editor": "FNESVa_editor",
252
- "freeze": "FNESVa_freeze",
253
- "badgeNext": "FNESVa_badgeNext"
254
- };
255
- //#endregion
256
- //#region src/client/steer-queue-dock.tsx
257
- /**
258
- * Three-tier steering queue dock: the shadowing replacement for the official
259
- * `conversation.input.dock` entry (same cell id `queue`, priority -1, so the
260
- * lower priority wins and the official QueueDock never renders while this
261
- * plugin is mounted).
262
- *
263
- * Every queued row carries three planning buttons:
264
- * - green (later, the default state): the row already queues for the next
265
- * turn —plain Enter keeps feeding rows here; pressing green on a row that
266
- * was already steered (yellow) revokes the insertion and pulls it back to
267
- * later (the yellow flow is reversible);
268
- * - yellow (next): strict-steer the row into the running turn, consumed at
269
- * the next step boundary (after the current action finishes);
270
- * - red (now): cancel the running turn, then remove the row and re-send its
271
- * text as a fresh message —the re-send arms the harness wake latch (the
272
- * converged driver restarts) and the interrupted input is processed as the
273
- * next turn's input. A plain steer after cancel would re-insert a message
274
- * that is already pending in next-turn and the inbox rejects it.
275
- *
276
- * The toolbar also exposes a queue-level "cancel and clear" that stops the
277
- * current run and removes every queued row, plus a session-level
278
- * freeze/resume toggle for the peak-hour scenario: freeze stops the current
279
- * run while preserving the queue (keepInbox), resume re-arms the driver by
280
- * re-sending the first queued row so the preserved work continues.
281
- */
282
- /** Busy marker for a whole-queue rebuild (reorder); locks every row action. */
283
- const REORDER_MARK = "__reorder__";
284
- /** localStorage key for the manual collapse state (dsh-queue-plus parity). */
285
- const COLLAPSE_KEY = "dsh-input-traffic:collapsed";
286
- /**
287
- * Project the tier badge of one inbox row from its placement. Pure so the
288
- * row rendering and its tests share one truth.
289
- * @param placement - the inbox projection placement.
290
- * @returns the tier badge, or null for non-visible context rows.
291
- */
292
- function badgeFor(placement) {
293
- if (placement === "steering") return "next";
294
- if (placement === "queued") return "later";
295
- return null;
296
- }
297
- /**
298
- * Auto-grow one edit textarea to its content. The CSS max-height caps the
299
- * growth; beyond it the textarea scrolls internally. Height is reset first so
300
- * shrink (deleting lines) also tracks the content.
301
- * @param el - the textarea to resize in place.
302
- */
303
- function resizeEditor(el) {
304
- el.style.height = "auto";
305
- el.style.height = `${el.scrollHeight}px`;
306
- }
307
- /**
308
- * Queue strip with three-tier planning: one item renders directly; multiple
309
- * items default to a collapsible count header; an empty queue renders nothing.
310
- */
311
- function SteerQueueDock({ sessionId, useSession, input, updateQueue, cancel, send, setDraft, notify, t }) {
312
- const inbox = useSession((s) => s.queue);
313
- const queue = (0, react.useMemo)(() => inbox.filter((row) => row.placement === "queued"), [inbox]);
314
- const steering = (0, react.useMemo)(() => inbox.filter((row) => row.placement === "steering"), [inbox]);
315
- const running = useSession((s) => s.running);
316
- const queueMutable = useSession((s) => s.subagent === null);
317
- const [editing, setEditing] = (0, react.useState)(null);
318
- const [busy, setBusy] = (0, react.useState)(null);
319
- const [clearing, setClearing] = (0, react.useState)(false);
320
- const [confirmClear, setConfirmClear] = (0, react.useState)(false);
321
- const confirmTimer = (0, react.useRef)(null);
322
- const dragIndex = (0, react.useRef)(null);
323
- const [dragOver, setDragOver] = (0, react.useState)(null);
324
- const [collapsed, setCollapsed] = (0, react.useState)(() => {
325
- try {
326
- const v = localStorage.getItem(COLLAPSE_KEY);
327
- return v === null ? true : v === "1";
328
- } catch {
329
- return true;
330
- }
331
- });
332
- const { frozen, pending: frozenPending } = (0, react.useSyncExternalStore)(freezeStore.subscribe, () => freezeStore.getSnapshot(sessionId ?? ""));
333
- const sid = sessionId ?? "";
334
- const listId = (0, react.useId)();
335
- const editorRef = (0, react.useRef)(null);
336
- (0, react.useEffect)(() => {
337
- if (queue.length === 0 && !collapsed) setCollapsed(true);
338
- if (editing !== null && !editing.id.startsWith("frozen:") && (!queueMutable || !queue.some((row) => row.id === editing.id))) setEditing(null);
339
- }, [
340
- collapsed,
341
- editing,
342
- queue,
343
- queueMutable
344
- ]);
345
- (0, react.useEffect)(() => {
346
- try {
347
- localStorage.setItem(COLLAPSE_KEY, collapsed ? "1" : "0");
348
- } catch {}
349
- }, [collapsed]);
350
- (0, react.useEffect)(() => () => {
351
- if (confirmTimer.current !== null) clearTimeout(confirmTimer.current);
352
- }, []);
353
- const editingId = editing?.id;
354
- (0, react.useEffect)(() => {
355
- if (editingId === void 0) return;
356
- const el = editorRef.current;
357
- if (el !== null) resizeEditor(el);
358
- }, [editingId]);
359
- if (queue.length === 0 && steering.length === 0 && !running && !frozen) return null;
360
- const interactionActive = queueMutable && (editing !== null || busy !== null || clearing);
361
- const expanded = !collapsed || interactionActive;
362
- const listVisible = queue.length === 1 || expanded;
363
- const planDisabled = !queueMutable || !running || frozen;
364
- const nothingPending = queue.length === 0 && steering.length === 0;
365
- const reorderUnsupported = queue.some((row) => row.text === null);
366
- const composerEmpty = input.draft.trim() === "";
367
- const applyAction = async (itemId, action, failure) => {
368
- setBusy(itemId);
369
- try {
370
- await updateQueue(itemId, action);
371
- return true;
372
- } catch {
373
- notify("error", failure);
374
- return false;
375
- } finally {
376
- setBusy((current) => current === itemId ? null : current);
377
- }
378
- };
379
- const saveEdit = async () => {
380
- if (editing === null || editing.text.trim() === "") return;
381
- const itemId = editing.id;
382
- const text = editing.text;
383
- setBusy(itemId);
384
- try {
385
- await updateQueue(itemId, {
386
- kind: "edit",
387
- content: [{
388
- type: "text",
389
- text
390
- }]
391
- });
392
- setEditing(null);
393
- } catch {
394
- if (input.draft.trim() === "") {
395
- setDraft(text);
396
- setEditing(null);
397
- notify("error", t("queue.editFailed.pulledBack"));
398
- } else notify("error", t("queue.editFailed"));
399
- } finally {
400
- setBusy((current) => current === itemId ? null : current);
401
- }
402
- };
403
- const steerRow = async (row, tier) => {
404
- setBusy(row.id);
405
- try {
406
- if (tier === "now") {
407
- await cancel();
408
- await updateQueue(row.id, { kind: "remove" });
409
- if (row.text !== null) await send(row.text);
410
- } else await updateQueue(row.id, { kind: "steer" });
411
- } catch {
412
- notify("error", tier === "now" ? t("steer.nowFailed") : t("steer.nextFailed"));
413
- } finally {
414
- setBusy((current) => current === row.id ? null : current);
415
- }
416
- };
417
- /**
418
- * Revoke a steered (yellow) or interrupting (red) row back to later: remove
419
- * the admitted row and re-send its text as a queued follow-up, so it lands
420
- * in next-turn again (the yellow flow is reversible).
421
- */
422
- const revokeToLater = async (row) => {
423
- if (row.text === null) return;
424
- setBusy(row.id);
425
- try {
426
- await updateQueue(row.id, { kind: "remove" });
427
- await send(row.text);
428
- } catch {
429
- notify("error", t("steer.revokeFailed"));
430
- } finally {
431
- setBusy((current) => current === row.id ? null : current);
432
- }
433
- };
434
- /**
435
- * Rebuild the queue in a new order: remove every queued row, then re-send
436
- * the texts sequentially so the agent's next-turn list matches the new
437
- * order. Concurrency protection (dsh-queue-plus parity): if a remove fails
438
- * with queue-item-not-found, the agent already claimed that row — the
439
- * rebuild stops immediately and nothing is re-sent, so the changed queue
440
- * is never scrambled.
441
- */
442
- const rebuildQueue = async (rows, next) => {
443
- setBusy(REORDER_MARK);
444
- try {
445
- for (const row of rows) try {
446
- await updateQueue(row.id, { kind: "remove" });
447
- } catch (error) {
448
- if (error instanceof Error && error.message.includes("queue-item-not-found")) {
449
- notify("error", t("steer.reorderStale"));
450
- return;
451
- }
452
- throw error;
453
- }
454
- for (const row of next) if (row.text !== null) await send(row.text);
455
- } catch {
456
- notify("error", t("steer.reorderFailed"));
457
- } finally {
458
- setBusy(null);
459
- }
460
- };
461
- /**
462
- * Move one queued row up/down in the FIFO order (arrow buttons).
463
- */
464
- const reorder = async (rowId, delta) => {
465
- const rows = queue;
466
- const index = rows.findIndex((row) => row.id === rowId);
467
- const target = index + delta;
468
- if (index < 0 || target < 0 || target >= rows.length) return;
469
- const next = [...rows];
470
- const swapped = next[index];
471
- next[index] = next[target];
472
- next[target] = swapped;
473
- if (next.some((row) => row.text === null)) {
474
- notify("error", t("steer.reorder.unsupported"));
475
- return;
476
- }
477
- await rebuildQueue(rows, next);
478
- };
479
- /**
480
- * Move one row to an absolute position (drag-and-drop drop handler).
481
- */
482
- const reorderTo = async (fromIndex, toIndex) => {
483
- if (fromIndex === toIndex) return;
484
- const rows = queue;
485
- if (rows[fromIndex] === void 0 || rows[toIndex] === void 0) return;
486
- const next = [...rows];
487
- const [moved] = next.splice(fromIndex, 1);
488
- if (moved === void 0) return;
489
- next.splice(toIndex, 0, moved);
490
- if (next.some((row) => row.text === null)) {
491
- notify("error", t("steer.reorder.unsupported"));
492
- return;
493
- }
494
- await rebuildQueue(rows, next);
495
- };
496
- /** Arm or execute the two-step clear confirmation. */
497
- const armClear = () => {
498
- if (confirmClear) {
499
- if (confirmTimer.current !== null) clearTimeout(confirmTimer.current);
500
- setConfirmClear(false);
501
- clearAll();
502
- return;
503
- }
504
- setConfirmClear(true);
505
- if (confirmTimer.current !== null) clearTimeout(confirmTimer.current);
506
- confirmTimer.current = setTimeout(() => setConfirmClear(false), 3e3);
507
- };
508
- /** Abort the armed clear confirmation (the explicit cancel button). */
509
- const cancelClear = () => {
510
- if (confirmTimer.current !== null) clearTimeout(confirmTimer.current);
511
- setConfirmClear(false);
512
- };
513
- /** Save an in-place edit of one detached (frozen) queued message. */
514
- const saveFrozenEdit = async (index) => {
515
- if (editing === null) return;
516
- const text = editing.text.trim();
517
- if (text === "") {
518
- setEditing(null);
519
- return;
520
- }
521
- updatePendingAt(sid, index, text);
522
- setEditing(null);
523
- };
524
- /**
525
- * Pull one queued row back into the composer draft for editing: the text
526
- * back-fills the input box and the row leaves the queue; the user edits and
527
- * resubmits it as a fresh message.
528
- */
529
- const pullBackToComposer = async (row) => {
530
- if (row.text === null) return;
531
- setDraft(row.text);
532
- setBusy(row.id);
533
- try {
534
- await updateQueue(row.id, { kind: "remove" });
535
- } catch {
536
- notify("error", t("steer.pullBackFailed"));
537
- } finally {
538
- setBusy((current) => current === row.id ? null : current);
539
- }
540
- };
541
- const clearAll = async () => {
542
- setClearing(true);
543
- const pending = [...queue, ...steering];
544
- try {
545
- await cancel();
546
- await Promise.all(pending.map((row) => updateQueue(row.id, { kind: "remove" }).catch(() => void 0)));
547
- } catch {
548
- notify("error", t("steer.clearFailed"));
549
- } finally {
550
- setClearing(false);
551
- }
552
- };
553
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
554
- className: steer_queue_dock_module_css_default.dock,
555
- "data-steer-dock": "",
556
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
557
- className: steer_queue_dock_module_css_default.panel,
558
- children: [
559
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
560
- className: steer_queue_dock_module_css_default.toolbar,
561
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
562
- type: "button",
563
- className: steer_queue_dock_module_css_default.header,
564
- "aria-controls": listId,
565
- "aria-expanded": expanded,
566
- disabled: queue.length <= 1 || interactionActive,
567
- onClick: () => {
568
- setCollapsed((value) => !value);
569
- },
570
- children: [
571
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
572
- className: steer_queue_dock_module_css_default.lead,
573
- "aria-hidden": true,
574
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutline14, {})
575
- }),
576
- queue.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
577
- className: steer_queue_dock_module_css_default.count,
578
- children: t("queue.count", { n: queue.length })
579
- }),
580
- queue.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
581
- className: steer_queue_dock_module_css_default.chevron,
582
- "aria-hidden": true,
583
- 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, {})
584
- })
585
- ]
586
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
587
- className: steer_queue_dock_module_css_default.toolbarActions,
588
- children: [confirmClear && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
589
- type: "button",
590
- className: steer_queue_dock_module_css_default.clearCancel,
591
- "aria-label": t("steer.clear.cancel"),
592
- disabled: clearing || busy !== null || nothingPending,
593
- onClick: cancelClear,
594
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
595
- className: steer_queue_dock_module_css_default.clearLabel,
596
- children: t("steer.clear.cancel")
597
- })
598
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
599
- label: confirmClear ? t("steer.clear.confirm") : t("steer.clear"),
600
- side: "top",
601
- delayMs: 500,
602
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
603
- type: "button",
604
- className: `${steer_queue_dock_module_css_default.clear} ${confirmClear ? steer_queue_dock_module_css_default.clearConfirm : ""}`,
605
- "aria-label": confirmClear ? t("steer.clear.confirm") : t("steer.clear"),
606
- disabled: clearing || busy !== null || nothingPending,
607
- onClick: armClear,
608
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
609
- className: steer_queue_dock_module_css_default.clearLabel,
610
- children: confirmClear ? t("steer.clear.confirm") : t("steer.clear")
611
- })]
612
- })
613
- })]
614
- })]
615
- }),
616
- frozen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
617
- className: steer_queue_dock_module_css_default.frozenBanner,
618
- role: "status",
619
- children: t("steer.frozen")
620
- }),
621
- frozen && frozenPending.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
622
- className: steer_queue_dock_module_css_default.frozenList,
623
- "data-testid": "frozen-list",
624
- children: frozenPending.map((entry, i) => {
625
- const editingFrozen = editing?.id === `frozen:${i}`;
626
- const text = entry.text;
627
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
628
- className: `${steer_queue_dock_module_css_default.frozenRow} ${dragOver === i && frozen ? steer_queue_dock_module_css_default.rowDragOver : ""}`,
629
- "data-tier": entry.tier === "force" ? "now" : entry.tier === "safe_point" ? "next" : "later",
630
- "data-editing": editingFrozen ? "" : void 0,
631
- draggable: !editingFrozen,
632
- title: !editingFrozen ? t("steer.dragReorder") : void 0,
633
- onDragStart: (event) => {
634
- if (editingFrozen) return;
635
- dragIndex.current = i;
636
- event.dataTransfer.effectAllowed = "move";
637
- event.dataTransfer.setData("text/plain", String(i));
638
- },
639
- onDragOver: (event) => {
640
- if (dragIndex.current === null) return;
641
- event.preventDefault();
642
- setDragOver(i);
643
- },
644
- onDrop: (event) => {
645
- event.preventDefault();
646
- const from = dragIndex.current;
647
- dragIndex.current = null;
648
- setDragOver(null);
649
- if (from !== null) movePending(sid, from, i);
650
- },
651
- onDragEnd: () => {
652
- dragIndex.current = null;
653
- setDragOver(null);
654
- },
655
- children: [
656
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
657
- className: steer_queue_dock_module_css_default.lead,
658
- "aria-hidden": true,
659
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutline14, {})
660
- }),
661
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SteerBadge, {
662
- tier: entry.tier === "force" ? "now" : entry.tier === "safe_point" ? "next" : "later",
663
- t
664
- }),
665
- editingFrozen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
666
- ref: editorRef,
667
- autoFocus: true,
668
- className: steer_queue_dock_module_css_default.editor,
669
- "aria-label": t("queue.edit"),
670
- rows: 1,
671
- value: editing?.text ?? text,
672
- onChange: (event) => {
673
- setEditing({
674
- id: `frozen:${i}`,
675
- text: event.currentTarget.value
676
- });
677
- },
678
- onInput: (event) => {
679
- resizeEditor(event.currentTarget);
680
- },
681
- onKeyDown: (event) => {
682
- if (event.key === "Escape") {
683
- setEditing(null);
684
- return;
685
- }
686
- if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
687
- event.preventDefault();
688
- saveFrozenEdit(i);
689
- }
690
- }
691
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
692
- className: steer_queue_dock_module_css_default.preview,
693
- children: text
694
- }),
695
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
696
- className: steer_queue_dock_module_css_default.actions,
697
- children: editingFrozen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
698
- label: t("queue.save"),
699
- side: "bottom",
700
- delayMs: 500,
701
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
702
- type: "button",
703
- className: steer_queue_dock_module_css_default.action,
704
- "aria-label": t("queue.save"),
705
- disabled: editing === null || editing.text.trim() === "",
706
- onClick: () => {
707
- saveFrozenEdit(i);
708
- },
709
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 14 })
710
- })
711
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
712
- label: t("queue.cancelEdit"),
713
- side: "bottom",
714
- delayMs: 500,
715
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
716
- type: "button",
717
- className: steer_queue_dock_module_css_default.action,
718
- "aria-label": t("queue.cancelEdit"),
719
- onClick: () => {
720
- setEditing(null);
721
- },
722
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
723
- })
724
- })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
725
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
726
- label: t("steer.moveUp"),
727
- side: "bottom",
728
- delayMs: 500,
729
- disabled: i === 0,
730
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
731
- type: "button",
732
- className: steer_queue_dock_module_css_default.action,
733
- "aria-label": t("steer.moveUp"),
734
- disabled: i === 0,
735
- onClick: () => movePending(sid, i, i - 1),
736
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, {})
737
- })
738
- }),
739
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
740
- label: t("steer.moveDown"),
741
- side: "bottom",
742
- delayMs: 500,
743
- disabled: i === frozenPending.length - 1,
744
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
745
- type: "button",
746
- className: steer_queue_dock_module_css_default.action,
747
- "aria-label": t("steer.moveDown"),
748
- disabled: i === frozenPending.length - 1,
749
- onClick: () => movePending(sid, i, i + 1),
750
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})
751
- })
752
- }),
753
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
754
- label: t("queue.edit"),
755
- side: "bottom",
756
- delayMs: 500,
757
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
758
- type: "button",
759
- className: steer_queue_dock_module_css_default.action,
760
- "aria-label": t("queue.edit"),
761
- onClick: () => {
762
- setEditing({
763
- id: `frozen:${i}`,
764
- text
765
- });
766
- },
767
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 })
768
- })
769
- }),
770
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
771
- label: t("queue.remove"),
772
- side: "bottom",
773
- delayMs: 500,
774
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
775
- type: "button",
776
- className: steer_queue_dock_module_css_default.action,
777
- "aria-label": t("queue.remove"),
778
- onClick: () => removePendingAt(sid, i),
779
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
780
- })
781
- }),
782
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
783
- className: steer_queue_dock_module_css_default.plan,
784
- role: "group",
785
- "aria-label": t("steer.later.aria"),
786
- children: [
787
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
788
- label: t("steer.now"),
789
- side: "bottom",
790
- delayMs: 500,
791
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
792
- type: "button",
793
- className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierNow}`,
794
- "aria-label": t("steer.now"),
795
- "aria-pressed": entry.tier === "force" || void 0,
796
- onClick: () => setTierAt(sid, i, "force"),
797
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
798
- className: steer_queue_dock_module_css_default.dot,
799
- "aria-hidden": true
800
- })
801
- })
802
- }),
803
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
804
- label: t("steer.next"),
805
- side: "bottom",
806
- delayMs: 500,
807
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
808
- type: "button",
809
- className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierNext}`,
810
- "aria-label": t("steer.next"),
811
- "aria-pressed": entry.tier === "safe_point" || void 0,
812
- onClick: () => setTierAt(sid, i, "safe_point"),
813
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
814
- className: steer_queue_dock_module_css_default.dot,
815
- "aria-hidden": true
816
- })
817
- })
818
- }),
819
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
820
- label: t("steer.later"),
821
- side: "bottom",
822
- delayMs: 500,
823
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
824
- type: "button",
825
- className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierLater}`,
826
- "aria-label": t("steer.later"),
827
- "aria-pressed": entry.tier === "queue" || void 0,
828
- onClick: () => setTierAt(sid, i, "queue"),
829
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
830
- className: steer_queue_dock_module_css_default.dot,
831
- "aria-hidden": true
832
- })
833
- })
834
- })
835
- ]
836
- })
837
- ] })
838
- }),
839
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
840
- className: steer_queue_dock_module_css_default.frozenMark,
841
- children: t("steer.frozenBadge")
842
- })
843
- ]
844
- }, i);
845
- })
846
- }),
847
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
848
- id: listId,
849
- className: steer_queue_dock_module_css_default.list,
850
- hidden: !listVisible,
851
- children: listVisible && queue.map((row, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
852
- className: `${steer_queue_dock_module_css_default.row} ${dragOver === index ? steer_queue_dock_module_css_default.rowDragOver : ""}`,
853
- "data-tier": badgeFor("queued") ?? void 0,
854
- "data-editing": editing?.id === row.id ? "" : void 0,
855
- draggable: queueMutable && !reorderUnsupported && busy === null && !frozen,
856
- title: queueMutable && !reorderUnsupported && busy === null && !frozen ? t("steer.dragReorder") : void 0,
857
- onDragStart: (event) => {
858
- dragIndex.current = index;
859
- event.dataTransfer.effectAllowed = "move";
860
- event.dataTransfer.setData("text/plain", String(index));
861
- },
862
- onDragOver: (event) => {
863
- if (dragIndex.current === null) return;
864
- event.preventDefault();
865
- setDragOver(index);
866
- },
867
- onDrop: (event) => {
868
- event.preventDefault();
869
- const from = dragIndex.current;
870
- dragIndex.current = null;
871
- setDragOver(null);
872
- if (from !== null) reorderTo(from, index);
873
- },
874
- onDragEnd: () => {
875
- dragIndex.current = null;
876
- setDragOver(null);
877
- },
878
- children: [
879
- queue.length === 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
880
- className: steer_queue_dock_module_css_default.lead,
881
- "aria-hidden": true,
882
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutline14, {})
883
- }),
884
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SteerBadge, {
885
- tier: badgeFor("queued"),
886
- t
887
- }),
888
- editing?.id === row.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
889
- ref: editorRef,
890
- autoFocus: true,
891
- className: steer_queue_dock_module_css_default.editor,
892
- "aria-label": t("queue.edit"),
893
- rows: 1,
894
- value: editing.text,
895
- onChange: (event) => {
896
- setEditing({
897
- id: row.id,
898
- text: event.currentTarget.value
899
- });
900
- },
901
- onInput: (event) => {
902
- resizeEditor(event.currentTarget);
903
- },
904
- onKeyDown: (event) => {
905
- if (event.key === "Escape") {
906
- setEditing(null);
907
- return;
908
- }
909
- if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
910
- event.preventDefault();
911
- saveEdit();
912
- }
913
- }
914
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
915
- className: steer_queue_dock_module_css_default.preview,
916
- children: row.preview
917
- }),
918
- queueMutable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
919
- className: steer_queue_dock_module_css_default.actions,
920
- 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, {
921
- label: t("queue.save"),
922
- side: "bottom",
923
- delayMs: 500,
924
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
925
- type: "button",
926
- className: steer_queue_dock_module_css_default.action,
927
- "aria-label": t("queue.save"),
928
- disabled: busy !== null || editing.text.trim() === "",
929
- onClick: () => {
930
- saveEdit();
931
- },
932
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 14 })
933
- })
934
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
935
- label: t("queue.cancelEdit"),
936
- side: "bottom",
937
- delayMs: 500,
938
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
939
- type: "button",
940
- className: steer_queue_dock_module_css_default.action,
941
- "aria-label": t("queue.cancelEdit"),
942
- disabled: busy !== null,
943
- onClick: () => {
944
- setEditing(null);
945
- },
946
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
947
- })
948
- })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
949
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
950
- label: t("steer.moveUp"),
951
- side: "bottom",
952
- delayMs: 500,
953
- disabled: frozen || reorderUnsupported,
954
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
955
- type: "button",
956
- className: steer_queue_dock_module_css_default.action,
957
- "aria-label": t("steer.moveUp"),
958
- title: reorderUnsupported ? t("steer.reorder.unsupported") : void 0,
959
- disabled: busy !== null || frozen || reorderUnsupported,
960
- onClick: () => {
961
- reorder(row.id, -1);
962
- },
963
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, {})
964
- })
965
- }),
966
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
967
- label: t("steer.moveDown"),
968
- side: "bottom",
969
- delayMs: 500,
970
- disabled: frozen || reorderUnsupported,
971
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
972
- type: "button",
973
- className: steer_queue_dock_module_css_default.action,
974
- "aria-label": t("steer.moveDown"),
975
- title: reorderUnsupported ? t("steer.reorder.unsupported") : void 0,
976
- disabled: busy !== null || frozen || reorderUnsupported,
977
- onClick: () => {
978
- reorder(row.id, 1);
979
- },
980
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})
981
- })
982
- }),
983
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
984
- label: t("steer.pullBack"),
985
- side: "bottom",
986
- delayMs: 500,
987
- disabled: row.text === null || !composerEmpty,
988
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
989
- type: "button",
990
- className: steer_queue_dock_module_css_default.action,
991
- "aria-label": t("steer.pullBack"),
992
- title: row.text === null ? t("steer.pullBack.unsupported") : !composerEmpty ? t("steer.pullBack.composerBusy") : void 0,
993
- disabled: busy !== null || frozen || row.text === null || !composerEmpty,
994
- onClick: () => {
995
- pullBackToComposer(row);
996
- },
997
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRightUpOutline16, {})
998
- })
999
- }),
1000
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1001
- label: t("queue.edit"),
1002
- side: "bottom",
1003
- delayMs: 500,
1004
- disabled: row.text === null,
1005
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1006
- type: "button",
1007
- className: steer_queue_dock_module_css_default.action,
1008
- "aria-label": t("queue.edit"),
1009
- title: row.text === null ? t("queue.edit.unsupported") : void 0,
1010
- disabled: busy !== null || row.text === null,
1011
- onClick: () => {
1012
- if (row.text !== null) setEditing({
1013
- id: row.id,
1014
- text: row.text
1015
- });
1016
- },
1017
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 })
1018
- })
1019
- }),
1020
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1021
- label: t("queue.remove"),
1022
- side: "bottom",
1023
- delayMs: 500,
1024
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1025
- type: "button",
1026
- className: steer_queue_dock_module_css_default.action,
1027
- "aria-label": t("queue.remove"),
1028
- disabled: busy !== null,
1029
- onClick: () => {
1030
- applyAction(row.id, { kind: "remove" }, t("queue.removeFailed"));
1031
- },
1032
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
1033
- })
1034
- }),
1035
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1036
- className: steer_queue_dock_module_css_default.plan,
1037
- role: "group",
1038
- "aria-label": t("steer.later.aria"),
1039
- children: [
1040
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1041
- label: t("steer.now"),
1042
- side: "bottom",
1043
- delayMs: 500,
1044
- disabled: planDisabled || row.text === null,
1045
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1046
- type: "button",
1047
- className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierNow}`,
1048
- "aria-label": t("steer.now"),
1049
- title: planDisabled ? t("steer.unavailable.running") : row.text === null ? t("steer.now.unsupported") : void 0,
1050
- disabled: busy !== null || planDisabled || row.text === null,
1051
- onClick: () => {
1052
- steerRow(row, "now");
1053
- },
1054
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1055
- className: steer_queue_dock_module_css_default.dot,
1056
- "aria-hidden": true
1057
- })
1058
- })
1059
- }),
1060
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1061
- label: t("steer.next"),
1062
- side: "bottom",
1063
- delayMs: 500,
1064
- disabled: planDisabled,
1065
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1066
- type: "button",
1067
- className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierNext}`,
1068
- "aria-label": t("steer.next"),
1069
- title: planDisabled ? t("steer.unavailable.running") : void 0,
1070
- disabled: busy !== null || planDisabled,
1071
- onClick: () => {
1072
- steerRow(row, "next");
1073
- },
1074
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1075
- className: steer_queue_dock_module_css_default.dot,
1076
- "aria-hidden": true
1077
- })
1078
- })
1079
- }),
1080
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1081
- label: t("steer.later"),
1082
- side: "bottom",
1083
- delayMs: 500,
1084
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1085
- type: "button",
1086
- className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierLater}`,
1087
- "aria-label": t("steer.later"),
1088
- "aria-pressed": true,
1089
- disabled: busy !== null || clearing,
1090
- onClick: () => {},
1091
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1092
- className: steer_queue_dock_module_css_default.dot,
1093
- "aria-hidden": true
1094
- })
1095
- })
1096
- })
1097
- ]
1098
- })
1099
- ] })
1100
- })
1101
- ]
1102
- }, row.id))
1103
- }),
1104
- steering.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1105
- className: steer_queue_dock_module_css_default.steeringList,
1106
- "data-steering-list": "",
1107
- children: steering.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1108
- className: steer_queue_dock_module_css_default.row,
1109
- "data-tier": badgeFor("steering") ?? void 0,
1110
- children: [
1111
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1112
- className: steer_queue_dock_module_css_default.lead,
1113
- "aria-hidden": true,
1114
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutline14, {})
1115
- }),
1116
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SteerBadge, {
1117
- tier: badgeFor("steering"),
1118
- t
1119
- }),
1120
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1121
- className: steer_queue_dock_module_css_default.preview,
1122
- children: row.preview
1123
- }),
1124
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1125
- className: steer_queue_dock_module_css_default.actions,
1126
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1127
- label: t("steer.revoke"),
1128
- side: "bottom",
1129
- delayMs: 500,
1130
- disabled: row.text === null,
1131
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1132
- type: "button",
1133
- className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierLater}`,
1134
- "aria-label": t("steer.revoke"),
1135
- title: row.text === null ? t("steer.revoke.unsupported") : void 0,
1136
- disabled: busy !== null || row.text === null || frozen,
1137
- onClick: () => {
1138
- revokeToLater(row);
1139
- },
1140
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1141
- className: steer_queue_dock_module_css_default.dot,
1142
- "aria-hidden": true
1143
- })
1144
- })
1145
- })
1146
- })
1147
- ]
1148
- }, row.id))
1149
- })
1150
- ]
1151
- })
1152
- });
1153
- }
1154
- /** Badge label keys per tier. */
1155
- const BADGE_KEYS = {
1156
- now: "steer.badge.now",
1157
- next: "steer.badge.next",
1158
- later: "steer.badge.later"
1159
- };
1160
- /** Badge tint classes per tier (present at bundle time; typed through the css-modules declaration). */
1161
- const BADGE_CLASSES = {
1162
- now: steer_queue_dock_module_css_default.badgeNow,
1163
- next: steer_queue_dock_module_css_default.badgeNext,
1164
- later: steer_queue_dock_module_css_default.badgeLater
1165
- };
1166
- /** One row's tier badge: colored dot + label; context rows render nothing. */
1167
- function SteerBadge({ tier, t }) {
1168
- if (tier === null) return null;
1169
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1170
- className: `${steer_queue_dock_module_css_default.badge} ${BADGE_CLASSES[tier]}`,
1171
- "data-badge": tier,
1172
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1173
- className: steer_queue_dock_module_css_default.dot,
1174
- "aria-hidden": true
1175
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1176
- className: steer_queue_dock_module_css_default.badgeLabel,
1177
- children: t(BADGE_KEYS[tier])
1178
- })]
1179
- });
1180
- }
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
1221
- //#region \0dsh-css:E:\test\rewrite-agently\mine-dsh-plugins\dsh-input-traffic\src\client\freeze-button.module.css.mjs
1222
- const css = ".BUSMDW_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}.BUSMDW_freeze:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.BUSMDW_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)}.BUSMDW_label{font-size:12px}";
1223
- const tagId = "dsh-input-traffic/freeze-button.module.css";
1224
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
1225
- const tag = document.createElement("style");
1226
- tag.dataset.plugin = "dsh-input-traffic";
1227
- tag.dataset.pluginCss = tagId;
1228
- tag.textContent = css;
1229
- document.head.appendChild(tag);
1230
- }
1231
- var freeze_button_module_css_default = {
1232
- "freeze": "BUSMDW_freeze",
1233
- "label": "BUSMDW_label"
1234
- };
1235
- //#endregion
1236
- //#region src/client/freeze-button.tsx
1237
- /**
1238
- * Session freeze/resume control, mounted in the composer's right tool row
1239
- * (`conversation.input.right`) so it is reachable before anything queues.
1240
- *
1241
- * Peak-hour pause semantics: freeze does NOT interrupt the running turn —it
1242
- * finishes naturally. The queued messages are detached (removed, preserved in
1243
- * the shared store), so the driver finds no pending work and stops after the
1244
- * current turn. Resume re-submits the preserved texts, waking the driver and
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)。
1253
- */
1254
- /**
1255
- * Freeze/resume toggle for the peak-hour scenario.
1256
- * @param props - slot props; the session snapshot drives the detach list.
1257
- */
1258
- function FreezeButton({ useSession, updateQueue, cancel, send, sendSteer, sessionId, setComposerBlock, notify, t }) {
1259
- const sid = sessionId ?? "";
1260
- const { frozen } = (0, react.useSyncExternalStore)(freezeStore.subscribe, () => freezeStore.getSnapshot(sid));
1261
- const queue = useSession((s) => s.queue);
1262
- const freeze = async () => {
1263
- const rows = queue.filter((row) => row.placement === "queued" || row.placement === "steering");
1264
- const pending = rows.flatMap((row) => row.text === null ? [] : [{
1265
- text: row.text,
1266
- tier: row.placement === "steering" ? "safe_point" : "queue"
1267
- }]);
1268
- await Promise.all(rows.map((row) => updateQueue(row.id, { kind: "remove" }).catch(() => void 0)));
1269
- freezeStore.set(sid, {
1270
- frozen: true,
1271
- pending
1272
- });
1273
- setComposerBlock?.(t("steer.frozenInput"));
1274
- if (sessionId !== void 0) sessionGuardStopNextTurn(sessionId);
1275
- };
1276
- const resume = async () => {
1277
- const pending = freezeStore.getSnapshot(sid).pending;
1278
- freezeStore.set(sid, {
1279
- frozen: false,
1280
- pending: []
1281
- });
1282
- setComposerBlock?.(void 0);
1283
- try {
1284
- if (sessionId !== void 0) await sessionGuardResume(sessionId);
1285
- for (const entry of pending) {
1286
- if (entry.tier === "force") await cancel();
1287
- if (entry.tier === "safe_point" && sendSteer !== void 0) await sendSteer(entry.text);
1288
- else await send(entry.text);
1289
- }
1290
- } catch {
1291
- notify("error", t("steer.resumeFailed"));
1292
- }
1293
- };
1294
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1295
- type: "button",
1296
- className: freeze_button_module_css_default.freeze,
1297
- "aria-label": frozen ? t("steer.resume") : t("steer.freeze"),
1298
- "aria-pressed": frozen || void 0,
1299
- title: frozen ? t("steer.frozen") : void 0,
1300
- onClick: () => {
1301
- if (frozen) resume();
1302
- else freeze();
1303
- },
1304
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1305
- className: freeze_button_module_css_default.label,
1306
- children: frozen ? t("steer.resume") : t("steer.freeze")
1307
- })
1308
- });
1309
- }
1310
- //#endregion
1311
- //#region src/client/hide-enter-row.tsx
1312
- /** Render nothing: the official busy-Enter row is hidden while mounted. */
1313
- function HideEnterRow(_props) {
1314
- return null;
1315
- }
1316
- //#endregion
1317
- //#region src/client/index.ts
1318
- /** Durable conversation settings namespace owned by ui-conversation. */
1319
- const CONVERSATION_SETTINGS_NAMESPACE = "ui-conversation";
1320
- /** Busy-Enter field inside that namespace; the plugin pins it to queue. */
1321
- const BUSY_ENTER_FIELD = "busyEnter";
1322
- /**
1323
- * Display order of the queue strip inside `conversation.input.dock`.
1324
- *
1325
- * List rows render sorted by `order` ascending, so a value above every other
1326
- * contributor keeps the strip as the bottom-most entry of the band — directly
1327
- * on top of the composer card. Known contributors on 0.1.5-rc.2: `todo` 0,
1328
- * `goal` 10, official `queue` 20, `dsh-perm-gate.notice` 30. DSH has no "last"
1329
- * slot semantics, so this is a convention, not a
1330
- * structural guarantee: a third party registering a larger value could still
1331
- * land below us.
1332
- */
1333
- const QUEUE_DOCK_ORDER = 1e3;
1334
- /**
1335
- * Deliver one plain-text message into the session's next step. The exposed
1336
- * conversation `send` verb only queues into the next turn, so resume steers
1337
- * through the session face's steer-mode prompt instead (no harness change).
1338
- * @param ctx - root context (resolves the session face behind the scope).
1339
- * @param actx - agent-scoped context of the owning session.
1340
- * @param text - message text to deliver.
1341
- */
1342
- function steerPrompt(actx, text) {
1343
- const conversation = actx.get("conversation");
1344
- if (conversation === void 0) return Promise.reject(/* @__PURE__ */ new Error("steer resume: conversation service unavailable"));
1345
- return conversation.send(text);
1346
- }
1347
- /** Services required by the browser half. */
1348
- const inject = [
1349
- "slots",
1350
- "locale",
1351
- "sessions",
1352
- "conversation",
1353
- "configForms"
1354
- ];
1355
- /**
1356
- * Client plugin body: dictionaries, busy-Enter pinning, and the two slot
1357
- * shadowings.
1358
- * @param ctx - client root context.
1359
- */
1360
- function apply(ctx) {
1361
- ctx.effect(() => ctx.locale.register(NS, {
1362
- zh,
1363
- en
1364
- }), "dsh-input-traffic: dictionaries");
1365
- ctx.configForms.get(CONVERSATION_SETTINGS_NAMESPACE).set(BUSY_ENTER_FIELD, "queue");
1366
- ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
1367
- name: "conversation.input.dock",
1368
- id: "queue",
1369
- order: QUEUE_DOCK_ORDER,
1370
- priority: -1,
1371
- locale: NS,
1372
- inject: (sessionId) => {
1373
- const actx = ctx.sessions.scope(sessionId);
1374
- if (actx === void 0) throw new Error(`steer dock: session "${sessionId}" resolved no scope`);
1375
- const conversation = actx.get("conversation");
1376
- if (conversation === void 0) throw new Error("steer dock: conversation service unavailable");
1377
- return {
1378
- updateQueue: (itemId, action) => conversation.updateQueue(itemId, action),
1379
- cancel: () => conversation.cancel(),
1380
- send: (text) => conversation.send(text),
1381
- setDraft: (text) => {
1382
- conversation.input.for(actx).setDraft(text);
1383
- },
1384
- notify: (level, text) => {
1385
- conversation.input.for(actx).notify(level, text);
1386
- }
1387
- };
1388
- }
1389
- }, SteerQueueDock));
1390
- ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
1391
- name: "conversation.input.right",
1392
- id: "steer-freeze",
1393
- order: 30,
1394
- locale: NS,
1395
- inject: (sessionId) => {
1396
- const actx = ctx.sessions.scope(sessionId);
1397
- if (actx === void 0) throw new Error(`steer freeze: session "${sessionId}" resolved no scope`);
1398
- const conversation = actx.get("conversation");
1399
- if (conversation === void 0) throw new Error("steer freeze: conversation service unavailable");
1400
- return {
1401
- updateQueue: (itemId, action) => conversation.updateQueue(itemId, action),
1402
- cancel: () => conversation.cancel(),
1403
- send: (text) => conversation.send(text),
1404
- sendSteer: (text) => steerPrompt(actx, text),
1405
- sessionId: String(sessionId),
1406
- setDraft: (text) => {
1407
- conversation.input.for(actx).setDraft(text);
1408
- },
1409
- notify: (level, text) => {
1410
- conversation.input.for(actx).notify(level, text);
1411
- },
1412
- setComposerBlock: (reason) => {
1413
- conversation.blocks.set(sessionId, reason === void 0 ? void 0 : { reason });
1414
- }
1415
- };
1416
- }
1417
- }, FreezeButton));
1418
- ctx.slots.inject("settings.general.item", () => ctx.slots.register({
1419
- name: "settings.general.item",
1420
- id: "composer-enter",
1421
- order: 20,
1422
- priority: -1,
1423
- locale: NS
1424
- }, HideEnterRow));
1425
- }
1426
- //#endregion
1427
- exports.QUEUE_DOCK_ORDER = QUEUE_DOCK_ORDER;
1428
- exports.apply = apply;
1429
- exports.inject = inject;
1430
- return module.exports;
1431
- }
1432
- });
1433
-
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` client dictionaries (zh / en / ja / ko). */
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
+ "family.title": "输入流量",
17
+ "family.busyEnter": "busy-Enter 行为(本插件接管)",
18
+ "family.desc": "普通回车在繁忙时进入排队(queue),不会打断当前回答;打断请用输入区的冻结/转向按钮。该行为由本插件自动接管,无需配置。",
19
+ "queue.count": "{n} 条排队消息",
20
+ "queue.edit": "编辑排队消息",
21
+ "queue.edit.unsupported": "包含非文本内容,暂不支持编辑",
22
+ "queue.save": "保存排队消息",
23
+ "queue.cancelEdit": "取消编辑",
24
+ "queue.remove": "删除排队消息",
25
+ "queue.removeFailed": "删除失败:这条消息可能已经开始发送。",
26
+ "queue.editFailed": "编辑失败:这条消息可能已经开始发送。",
27
+ "queue.editFailed.pulledBack": "编辑失败,内容已退回主输入框,请确认后重新发送。",
28
+ "steer.inflight": "{n} 条正在插入",
29
+ "steer.now": "打断并输入",
30
+ "steer.now.aria": "红色:打断当前动作并立即输入",
31
+ "steer.now.unsupported": "包含非文本内容,暂不支持打断",
32
+ "steer.nowFailed": "打断失败,请重试。",
33
+ "steer.next": "插话发送",
34
+ "steer.next.aria": "黄色:当前动作结束后插入输入",
35
+ "steer.nextFailed": "插话发送失败,请重试。",
36
+ "steer.later": "排队到下一轮",
37
+ "steer.later.aria": "绿色:排队,上一轮输入的动作都结束后再处理",
38
+ "steer.revoke": "收回排队",
39
+ "steer.revoke.aria": "绿色:撤销插入,收回排队(下一轮处理)",
40
+ "steer.revoke.unsupported": "包含非文本内容,暂不支持收回",
41
+ "steer.revokeFailed": "收回排队失败,请重试。",
42
+ "steer.moveUp": "上移",
43
+ "steer.moveDown": "下移",
44
+ "steer.reorder.unsupported": "包含非文本内容,暂不支持排序",
45
+ "steer.reorderFailed": "调整顺序失败,请重试。",
46
+ "steer.reorderStale": "队列已变化,本次排序已取消,请重试。",
47
+ "steer.dragReorder": "拖动调整顺序",
48
+ "steer.pullBack": "打回输入框编辑",
49
+ "steer.pullBack.unsupported": "包含非文本内容,暂不支持打回编辑",
50
+ "steer.pullBack.composerBusy": "主输入框已有内容,请先发送或清空后再打回",
51
+ "steer.pullBackFailed": "打回输入框失败,请重试。",
52
+ "steer.badge.now": "打断中",
53
+ "steer.badge.next": "插话中",
54
+ "steer.badge.later": "排队",
55
+ "steer.unavailable.running": "仅运行中可规划",
56
+ "steer.clear": "取消并清空",
57
+ "steer.clear.confirm": "确认清空?",
58
+ "steer.clear.cancel": "取消清空",
59
+ "steer.clear.aria": "取消当前执行并清空全部排队消息",
60
+ "steer.clearFailed": "取消并清空失败,请重试。",
61
+ "steer.freeze": "冻结追加",
62
+ "steer.resume": "恢复追加",
63
+ "steer.frozen": "已冻结:当前轮次完成后暂停,排队消息将在恢复后继续",
64
+ "steer.frozenInput": "已冻结:输入已暂停,恢复后继续",
65
+ "steer.frozenBadge": "已冻结",
66
+ "steer.freezeFailed": "冻结失败,请重试。",
67
+ "steer.resumeFailed": "恢复失败,请重试。"
68
+ };
69
+ /** English dictionary (keys mirror zh). */
70
+ const en = {
71
+ "family.title": "Input Traffic",
72
+ "family.busyEnter": "busy-Enter behavior (managed by this plugin)",
73
+ "family.desc": "Plain Enter queues while busy instead of interrupting; use the freeze/steer control in the composer to interrupt. Managed automatically — nothing to configure.",
74
+ "queue.count": "{n} queued messages",
75
+ "queue.edit": "Edit queued message",
76
+ "queue.edit.unsupported": "Contains non-text content; editing is unsupported",
77
+ "queue.save": "Save queued message",
78
+ "queue.cancelEdit": "Cancel edit",
79
+ "queue.remove": "Remove queued message",
80
+ "queue.removeFailed": "Remove failed: the message may have started sending.",
81
+ "queue.editFailed": "Edit failed: the message may have started sending.",
82
+ "queue.editFailed.pulledBack": "Edit failed; the content was moved back to the composer, please review and resend.",
83
+ "steer.inflight": "{n} inserting",
84
+ "steer.now": "Interrupt and send",
85
+ "steer.now.aria": "Red: interrupt the running action and send now",
86
+ "steer.now.unsupported": "Contains non-text content; interrupting is unsupported",
87
+ "steer.nowFailed": "Interrupt failed, please retry.",
88
+ "steer.next": "Send after action",
89
+ "steer.next.aria": "Yellow: insert after the current action finishes",
90
+ "steer.nextFailed": "Steering failed, please retry.",
91
+ "steer.later": "Queue for next turn",
92
+ "steer.later.aria": "Green: queue; processed after all previously steered actions finish",
93
+ "steer.revoke": "Pull back to queue",
94
+ "steer.revoke.aria": "Green: revoke the insertion and queue for the next turn",
95
+ "steer.revoke.unsupported": "Contains non-text content; revoking is unsupported",
96
+ "steer.revokeFailed": "Revoke failed, please retry.",
97
+ "steer.moveUp": "Move up",
98
+ "steer.moveDown": "Move down",
99
+ "steer.reorder.unsupported": "Contains non-text content; reordering is unsupported",
100
+ "steer.reorderFailed": "Reorder failed, please retry.",
101
+ "steer.reorderStale": "The queue changed; this reorder was cancelled, please retry.",
102
+ "steer.dragReorder": "Drag to reorder",
103
+ "steer.pullBack": "Edit in composer",
104
+ "steer.pullBack.unsupported": "Contains non-text content; pulling back is unsupported",
105
+ "steer.pullBack.composerBusy": "The composer already has content; send or clear it first",
106
+ "steer.pullBackFailed": "Pull back failed, please retry.",
107
+ "steer.badge.now": "interrupting",
108
+ "steer.badge.next": "steering",
109
+ "steer.badge.later": "queued",
110
+ "steer.unavailable.running": "Planning requires a running session",
111
+ "steer.clear": "Cancel and clear",
112
+ "steer.clear.confirm": "Confirm clear?",
113
+ "steer.clear.cancel": "Cancel clearing",
114
+ "steer.clear.aria": "Cancel the current run and clear all queued messages",
115
+ "steer.clearFailed": "Cancel and clear failed, please retry.",
116
+ "steer.freeze": "Freeze & append",
117
+ "steer.resume": "Resume & append",
118
+ "steer.frozen": "Frozen: the current turn finishes, then the queue pauses until resumed",
119
+ "steer.frozenInput": "Frozen: input paused, resume to continue",
120
+ "steer.frozenBadge": "frozen",
121
+ "steer.freezeFailed": "Freeze failed, please retry.",
122
+ "steer.resumeFailed": "Resume failed, please retry."
123
+ };
124
+ //#endregion
125
+ //#region src/client/freeze-store.ts
126
+ /** Session id → per-session freeze state. */
127
+ const states = /* @__PURE__ */ new Map();
128
+ const listeners = /* @__PURE__ */ new Set();
129
+ /** Stable empty snapshot: `useSyncExternalStore` needs a reference-stable
130
+ * value for unset sessions so unchanged consumers never re-render. */
131
+ const EMPTY = {
132
+ frozen: false,
133
+ pending: []
134
+ };
135
+ /** Minimal snapshot store (no runtime dependency, stable identity per mount). */
136
+ const freezeStore = {
137
+ /** Snapshot for one session; reference-stable until that session changes. */
138
+ getSnapshot(sessionId) {
139
+ return states.get(sessionId) ?? EMPTY;
140
+ },
141
+ subscribe(listener) {
142
+ listeners.add(listener);
143
+ return () => {
144
+ listeners.delete(listener);
145
+ };
146
+ },
147
+ /** Replace one session's state; unset sessions fall back to EMPTY. */
148
+ set(sessionId, next) {
149
+ if (next.frozen === false && next.pending.length === 0) states.delete(sessionId);
150
+ else states.set(sessionId, next);
151
+ emit();
152
+ }
153
+ };
154
+ /** Edit one detached queued message's text in place. */
155
+ function updatePendingAt(sessionId, index, text) {
156
+ const pending = states.get(sessionId)?.pending;
157
+ if (pending === void 0) return;
158
+ const entry = pending[index];
159
+ if (entry === void 0) return;
160
+ const next = [...pending];
161
+ next[index] = {
162
+ ...entry,
163
+ text
164
+ };
165
+ freezeStore.set(sessionId, {
166
+ frozen: true,
167
+ pending: next
168
+ });
169
+ }
170
+ /** Change one detached queued message's planned insertion tier. */
171
+ function setTierAt(sessionId, index, tier) {
172
+ const pending = states.get(sessionId)?.pending;
173
+ if (pending === void 0) return;
174
+ const entry = pending[index];
175
+ if (entry === void 0) return;
176
+ const next = [...pending];
177
+ next[index] = {
178
+ ...entry,
179
+ tier
180
+ };
181
+ freezeStore.set(sessionId, {
182
+ frozen: true,
183
+ pending: next
184
+ });
185
+ }
186
+ /** Remove one detached queued message. */
187
+ function removePendingAt(sessionId, index) {
188
+ const pending = states.get(sessionId)?.pending;
189
+ if (pending === void 0) return;
190
+ freezeStore.set(sessionId, {
191
+ frozen: true,
192
+ pending: pending.filter((_, i) => i !== index)
193
+ });
194
+ }
195
+ /** Move one detached queued message to a new position (reorder while frozen). */
196
+ function movePending(sessionId, from, to) {
197
+ if (from === to) return;
198
+ const pending = states.get(sessionId)?.pending;
199
+ if (pending === void 0) return;
200
+ const next = [...pending];
201
+ const [moved] = next.splice(from, 1);
202
+ if (moved === void 0) return;
203
+ next.splice(to, 0, moved);
204
+ freezeStore.set(sessionId, {
205
+ frozen: true,
206
+ pending: next
207
+ });
208
+ }
209
+ function emit() {
210
+ for (const listener of listeners) listener();
211
+ }
212
+ //#endregion
213
+ //#region \0dsh-css:E:\test\rewrite-agently\mine-dsh-plugins\dsh-input-traffic\src\client\steer-queue-dock.module.css.mjs
214
+ const css$1 = ".FNESVa_dock{box-sizing:border-box;width:min(calc(100% - var(--dsh-composer-side-clearance,16px) - var(--dsh-composer-side-clearance,16px) - var(--dsh-composer-dock-inset,8px) - var(--dsh-composer-dock-inset,8px)), calc(var(--dsh-chat-content-width,100%) - var(--dsh-composer-dock-inset,8px) - var(--dsh-composer-dock-inset,8px)));max-width:calc(var(--dsh-composer-card-max-width,var(--dsh-chat-content-width,100%)) - var(--dsh-composer-dock-inset,8px) - var(--dsh-composer-dock-inset,8px));padding:0 var(--dsh-composer-dock-inset,8px);flex:none;margin:0 auto}.FNESVa_panel{background:var(--dsw-alias-bg-layer-1,#fffc);border:1px solid var(--dsw-alias-border-l2,#00000014);border-radius:10px;overflow:hidden}.FNESVa_toolbar{border-bottom:1px solid var(--dsw-alias-border-l2,#0000000f);align-items:center;gap:8px;padding:4px 8px;display:flex}.FNESVa_header{color:var(--dsw-alias-label-secondary,#6b7280);cursor:pointer;background:0 0;border:0;align-items:center;gap:6px;padding:2px 6px;font-size:12px;display:inline-flex}.FNESVa_header:disabled{cursor:default}.FNESVa_lead{color:var(--dsw-alias-label-tertiary,#9ca3af);display:inline-flex}.FNESVa_count{font-weight:600}.FNESVa_chevron{color:var(--dsw-alias-label-tertiary,#9ca3af);display:inline-flex}.FNESVa_toolbarActions{margin-left:auto}.FNESVa_clear{color:var(--dsw-alias-label-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}.FNESVa_freeze{border:1px solid var(--dsw-alias-border-l3,#0000001f);color:var(--dsw-alias-label-secondary,#6b7280);cursor:pointer;background:0 0;border-radius:6px;align-items:center;gap:4px;padding:3px 8px;font-size:12px;display:inline-flex}.FNESVa_freeze[aria-pressed=true]{border-color:var(--dsw-alias-state-success-primary,#30a46c);color:var(--dsw-alias-state-success-primary,#30a46c)}.FNESVa_freeze:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.FNESVa_freeze:disabled{opacity:.5;cursor:default}.FNESVa_frozenBanner{border-bottom:1px solid var(--dsw-alias-border-l2,#0000000f);background:color-mix(in srgb, var(--dsw-alias-state-success-primary,#30a46c) 8%, transparent);color:var(--dsw-alias-state-success-primary,#30a46c);padding:4px 10px;font-size:12px}.FNESVa_frozenList{margin:0;padding:4px;list-style:none}.FNESVa_frozenRow{border-radius:6px;align-items:center;gap:8px;padding:4px 6px;display:flex}.FNESVa_frozenMark{color:var(--dsw-alias-label-tertiary,#9ca3af);flex:none;margin-left:auto;font-size:11px}.FNESVa_clear:hover:not(:disabled){border-color:var(--dsw-alias-border-l3,#0000001f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.FNESVa_clear:disabled{opacity:.5;cursor:default}.FNESVa_clearConfirm{border-color:var(--dsw-alias-state-error-primary,#e5484d);color:var(--dsw-alias-state-error-primary,#e5484d)}.FNESVa_clearConfirm:hover:not(:disabled){border-color:var(--dsw-alias-state-error-primary,#e5484d);background:color-mix(in srgb, var(--dsw-alias-state-error-primary,#e5484d) 8%, transparent)}.FNESVa_clearCancel{color:var(--dsw-alias-label-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}.FNESVa_clearCancel:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.FNESVa_clearLabel{font-size:12px}.FNESVa_list{margin:0;padding:4px;list-style:none}.FNESVa_row{border-radius:6px;align-items:center;gap:8px;padding:4px 6px;display:flex}.FNESVa_row[draggable=true]{cursor:grab}.FNESVa_row[draggable=true]:active{cursor:grabbing}.FNESVa_rowDragOver{outline:2px dashed var(--dsw-alias-border-l4,#00000040);outline-offset:-2px;background:var(--dsw-alias-interactive-bg-hover,#0000000d)}.FNESVa_row:hover{background:var(--dsw-alias-interactive-bg-hover,#00000008)}.FNESVa_row[data-editing]{align-items:flex-start}.FNESVa_preview{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary,#1f2937);flex:1;font-size:13px;overflow:hidden}.FNESVa_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}.FNESVa_badgeLabel{color:inherit}.FNESVa_badgeNow{color:var(--dsw-alias-state-error-primary,#e5484d);background:color-mix(in srgb, var(--dsw-alias-state-error-primary,#e5484d) 10%, transparent)}.FNESVa_badgeNext{color:var(--dsw-alias-state-warn-primary,#e8a33d);background:color-mix(in srgb, var(--dsw-alias-state-warn-primary,#e8a33d) 12%, transparent)}.FNESVa_badgeLater{color:var(--dsw-alias-state-success-primary,#30a46c);background:color-mix(in srgb, var(--dsw-alias-state-success-primary,#30a46c) 12%, transparent)}.FNESVa_badgeNow .FNESVa_dot{background:var(--dsw-alias-state-error-primary,#e5484d)}.FNESVa_badgeNext .FNESVa_dot{background:var(--dsw-alias-state-warn-primary,#e8a33d)}.FNESVa_badgeLater .FNESVa_dot{background:var(--dsw-alias-state-success-primary,#30a46c)}.FNESVa_steeringList{margin:0;padding:0 4px 4px;list-style:none}.FNESVa_steeringList .FNESVa_row{opacity:.85}.FNESVa_editor{border:1px solid var(--dsw-alias-border-l3,#00000026);background:var(--dsw-alias-bg-layer-2,#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}.FNESVa_actions{align-items:center;gap:2px;display:inline-flex}.FNESVa_action{width:24px;height:24px;color:var(--dsw-alias-label-tertiary,#9ca3af);cursor:pointer;background:0 0;border:0;border-radius:6px;justify-content:center;align-items:center;display:inline-flex}.FNESVa_action:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#0000000d);color:var(--dsw-alias-label-primary,#1f2937)}.FNESVa_action:disabled{opacity:.4;cursor:default}.FNESVa_plan{border-left:1px solid var(--dsw-alias-border-l2,#00000014);align-items:center;gap:2px;margin-left:4px;padding-left:6px;display:inline-flex}.FNESVa_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}.FNESVa_tier:hover:not(:disabled){border-color:var(--dsw-alias-border-l4,#0003)}.FNESVa_tier:disabled{cursor:default}.FNESVa_tierLater{border-color:var(--dsw-alias-state-success-primary,#30a46c);background:color-mix(in srgb, var(--dsw-alias-state-success-primary,#30a46c) 12%, transparent)}.FNESVa_dot{border-radius:50%;width:10px;height:10px}.FNESVa_tierNow .FNESVa_dot{background:var(--dsw-alias-state-error-primary,#e5484d)}.FNESVa_tierNext .FNESVa_dot{background:var(--dsw-alias-state-warn-primary,#e8a33d)}.FNESVa_tierLater .FNESVa_dot{background:var(--dsw-alias-state-success-primary,#30a46c)}";
215
+ const tagId$1 = "dsh-input-traffic/steer-queue-dock.module.css";
216
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
217
+ const tag = document.createElement("style");
218
+ tag.dataset.plugin = "dsh-input-traffic";
219
+ tag.dataset.pluginCss = tagId$1;
220
+ tag.textContent = css$1;
221
+ document.head.appendChild(tag);
222
+ }
223
+ var steer_queue_dock_module_css_default = {
224
+ "frozenRow": "FNESVa_frozenRow",
225
+ "toolbarActions": "FNESVa_toolbarActions",
226
+ "chevron": "FNESVa_chevron",
227
+ "frozenList": "FNESVa_frozenList",
228
+ "editor": "FNESVa_editor",
229
+ "list": "FNESVa_list",
230
+ "badgeLater": "FNESVa_badgeLater",
231
+ "clearConfirm": "FNESVa_clearConfirm",
232
+ "tierNow": "FNESVa_tierNow",
233
+ "clearCancel": "FNESVa_clearCancel",
234
+ "clearLabel": "FNESVa_clearLabel",
235
+ "plan": "FNESVa_plan",
236
+ "preview": "FNESVa_preview",
237
+ "badgeNow": "FNESVa_badgeNow",
238
+ "dock": "FNESVa_dock",
239
+ "toolbar": "FNESVa_toolbar",
240
+ "action": "FNESVa_action",
241
+ "tierLater": "FNESVa_tierLater",
242
+ "row": "FNESVa_row",
243
+ "count": "FNESVa_count",
244
+ "tier": "FNESVa_tier",
245
+ "header": "FNESVa_header",
246
+ "panel": "FNESVa_panel",
247
+ "freeze": "FNESVa_freeze",
248
+ "badgeLabel": "FNESVa_badgeLabel",
249
+ "steeringList": "FNESVa_steeringList",
250
+ "lead": "FNESVa_lead",
251
+ "actions": "FNESVa_actions",
252
+ "dot": "FNESVa_dot",
253
+ "badgeNext": "FNESVa_badgeNext",
254
+ "clear": "FNESVa_clear",
255
+ "rowDragOver": "FNESVa_rowDragOver",
256
+ "frozenBanner": "FNESVa_frozenBanner",
257
+ "frozenMark": "FNESVa_frozenMark",
258
+ "tierNext": "FNESVa_tierNext",
259
+ "badge": "FNESVa_badge"
260
+ };
261
+ //#endregion
262
+ //#region src/client/steer-queue-dock.tsx
263
+ /**
264
+ * Three-tier steering queue dock: the shadowing replacement for the official
265
+ * `conversation.input.dock` entry (same cell id `queue`, priority -1, so the
266
+ * lower priority wins and the official QueueDock never renders while this
267
+ * plugin is mounted).
268
+ *
269
+ * Every queued row carries three planning buttons:
270
+ * - green (later, the default state): the row already queues for the next
271
+ * turn —plain Enter keeps feeding rows here; pressing green on a row that
272
+ * was already steered (yellow) revokes the insertion and pulls it back to
273
+ * later (the yellow flow is reversible);
274
+ * - yellow (next): strict-steer the row into the running turn, consumed at
275
+ * the next step boundary (after the current action finishes);
276
+ * - red (now): cancel the running turn, then remove the row and re-send its
277
+ * text as a fresh message —the re-send arms the harness wake latch (the
278
+ * converged driver restarts) and the interrupted input is processed as the
279
+ * next turn's input. A plain steer after cancel would re-insert a message
280
+ * that is already pending in next-turn and the inbox rejects it.
281
+ *
282
+ * The toolbar also exposes a queue-level "cancel and clear" that stops the
283
+ * current run and removes every queued row, plus a session-level
284
+ * freeze/resume toggle for the peak-hour scenario: freeze stops the current
285
+ * run while preserving the queue (keepInbox), resume re-arms the driver by
286
+ * re-sending the first queued row so the preserved work continues.
287
+ */
288
+ /** Busy marker for a whole-queue rebuild (reorder); locks every row action. */
289
+ const REORDER_MARK = "__reorder__";
290
+ /** localStorage key for the manual collapse state (dsh-queue-plus parity). */
291
+ const COLLAPSE_KEY = "dsh-input-traffic:collapsed";
292
+ /**
293
+ * Project the tier badge of one inbox row from its placement. Pure so the
294
+ * row rendering and its tests share one truth.
295
+ * @param placement - the inbox projection placement.
296
+ * @returns the tier badge, or null for non-visible context rows.
297
+ */
298
+ function badgeFor(placement) {
299
+ if (placement === "steering") return "next";
300
+ if (placement === "queued") return "later";
301
+ return null;
302
+ }
303
+ /**
304
+ * Auto-grow one edit textarea to its content. The CSS max-height caps the
305
+ * growth; beyond it the textarea scrolls internally. Height is reset first so
306
+ * shrink (deleting lines) also tracks the content.
307
+ * @param el - the textarea to resize in place.
308
+ */
309
+ function resizeEditor(el) {
310
+ el.style.height = "auto";
311
+ el.style.height = `${el.scrollHeight}px`;
312
+ }
313
+ /**
314
+ * Queue strip with three-tier planning: one item renders directly; multiple
315
+ * items default to a collapsible count header; an empty queue renders nothing.
316
+ */
317
+ function SteerQueueDock({ sessionId, useSession, input, updateQueue, cancel, send, setDraft, notify, t }) {
318
+ const inbox = useSession((s) => s.queue);
319
+ const queue = (0, react.useMemo)(() => inbox.filter((row) => row.placement === "queued"), [inbox]);
320
+ const steering = (0, react.useMemo)(() => inbox.filter((row) => row.placement === "steering"), [inbox]);
321
+ const running = useSession((s) => s.running);
322
+ const queueMutable = useSession((s) => s.subagent === null);
323
+ const [editing, setEditing] = (0, react.useState)(null);
324
+ const [busy, setBusy] = (0, react.useState)(null);
325
+ const [clearing, setClearing] = (0, react.useState)(false);
326
+ const [confirmClear, setConfirmClear] = (0, react.useState)(false);
327
+ const confirmTimer = (0, react.useRef)(null);
328
+ const dragIndex = (0, react.useRef)(null);
329
+ const [dragOver, setDragOver] = (0, react.useState)(null);
330
+ const [collapsed, setCollapsed] = (0, react.useState)(() => {
331
+ try {
332
+ const v = localStorage.getItem(COLLAPSE_KEY);
333
+ return v === null ? true : v === "1";
334
+ } catch {
335
+ return true;
336
+ }
337
+ });
338
+ const { frozen, pending: frozenPending } = (0, react.useSyncExternalStore)(freezeStore.subscribe, () => freezeStore.getSnapshot(sessionId ?? ""));
339
+ const sid = sessionId ?? "";
340
+ const listId = (0, react.useId)();
341
+ const editorRef = (0, react.useRef)(null);
342
+ (0, react.useEffect)(() => {
343
+ if (queue.length === 0 && !collapsed) setCollapsed(true);
344
+ if (editing !== null && !editing.id.startsWith("frozen:") && (!queueMutable || !queue.some((row) => row.id === editing.id))) setEditing(null);
345
+ }, [
346
+ collapsed,
347
+ editing,
348
+ queue,
349
+ queueMutable
350
+ ]);
351
+ (0, react.useEffect)(() => {
352
+ try {
353
+ localStorage.setItem(COLLAPSE_KEY, collapsed ? "1" : "0");
354
+ } catch {}
355
+ }, [collapsed]);
356
+ (0, react.useEffect)(() => () => {
357
+ if (confirmTimer.current !== null) clearTimeout(confirmTimer.current);
358
+ }, []);
359
+ const editingId = editing?.id;
360
+ (0, react.useEffect)(() => {
361
+ if (editingId === void 0) return;
362
+ const el = editorRef.current;
363
+ if (el !== null) resizeEditor(el);
364
+ }, [editingId]);
365
+ if (queue.length === 0 && steering.length === 0 && !running && !frozen) return null;
366
+ const interactionActive = queueMutable && (editing !== null || busy !== null || clearing);
367
+ const expanded = !collapsed || interactionActive;
368
+ const listVisible = queue.length === 1 || expanded;
369
+ const planDisabled = !queueMutable || !running || frozen;
370
+ const nothingPending = queue.length === 0 && steering.length === 0;
371
+ const reorderUnsupported = queue.some((row) => row.text === null);
372
+ const composerEmpty = input.draft.trim() === "";
373
+ const applyAction = async (itemId, action, failure) => {
374
+ setBusy(itemId);
375
+ try {
376
+ await updateQueue(itemId, action);
377
+ return true;
378
+ } catch {
379
+ notify("error", failure);
380
+ return false;
381
+ } finally {
382
+ setBusy((current) => current === itemId ? null : current);
383
+ }
384
+ };
385
+ const saveEdit = async () => {
386
+ if (editing === null || editing.text.trim() === "") return;
387
+ const itemId = editing.id;
388
+ const text = editing.text;
389
+ setBusy(itemId);
390
+ try {
391
+ await updateQueue(itemId, {
392
+ kind: "edit",
393
+ content: [{
394
+ type: "text",
395
+ text
396
+ }]
397
+ });
398
+ setEditing(null);
399
+ } catch {
400
+ if (input.draft.trim() === "") {
401
+ setDraft(text);
402
+ setEditing(null);
403
+ notify("error", t("queue.editFailed.pulledBack"));
404
+ } else notify("error", t("queue.editFailed"));
405
+ } finally {
406
+ setBusy((current) => current === itemId ? null : current);
407
+ }
408
+ };
409
+ const steerRow = async (row, tier) => {
410
+ setBusy(row.id);
411
+ try {
412
+ if (tier === "now") {
413
+ await cancel();
414
+ await updateQueue(row.id, { kind: "remove" });
415
+ if (row.text !== null) await send(row.text);
416
+ } else await updateQueue(row.id, { kind: "steer" });
417
+ } catch {
418
+ notify("error", tier === "now" ? t("steer.nowFailed") : t("steer.nextFailed"));
419
+ } finally {
420
+ setBusy((current) => current === row.id ? null : current);
421
+ }
422
+ };
423
+ /**
424
+ * Revoke a steered (yellow) or interrupting (red) row back to later: remove
425
+ * the admitted row and re-send its text as a queued follow-up, so it lands
426
+ * in next-turn again (the yellow flow is reversible).
427
+ */
428
+ const revokeToLater = async (row) => {
429
+ if (row.text === null) return;
430
+ setBusy(row.id);
431
+ try {
432
+ await updateQueue(row.id, { kind: "remove" });
433
+ await send(row.text);
434
+ } catch {
435
+ notify("error", t("steer.revokeFailed"));
436
+ } finally {
437
+ setBusy((current) => current === row.id ? null : current);
438
+ }
439
+ };
440
+ /**
441
+ * Rebuild the queue in a new order: remove every queued row, then re-send
442
+ * the texts sequentially so the agent's next-turn list matches the new
443
+ * order. Concurrency protection (dsh-queue-plus parity): if a remove fails
444
+ * with queue-item-not-found, the agent already claimed that row — the
445
+ * rebuild stops immediately and nothing is re-sent, so the changed queue
446
+ * is never scrambled.
447
+ */
448
+ const rebuildQueue = async (rows, next) => {
449
+ setBusy(REORDER_MARK);
450
+ try {
451
+ for (const row of rows) try {
452
+ await updateQueue(row.id, { kind: "remove" });
453
+ } catch (error) {
454
+ if (error instanceof Error && error.message.includes("queue-item-not-found")) {
455
+ notify("error", t("steer.reorderStale"));
456
+ return;
457
+ }
458
+ throw error;
459
+ }
460
+ for (const row of next) if (row.text !== null) await send(row.text);
461
+ } catch {
462
+ notify("error", t("steer.reorderFailed"));
463
+ } finally {
464
+ setBusy(null);
465
+ }
466
+ };
467
+ /**
468
+ * Move one queued row up/down in the FIFO order (arrow buttons).
469
+ */
470
+ const reorder = async (rowId, delta) => {
471
+ const rows = queue;
472
+ const index = rows.findIndex((row) => row.id === rowId);
473
+ const target = index + delta;
474
+ if (index < 0 || target < 0 || target >= rows.length) return;
475
+ const next = [...rows];
476
+ const swapped = next[index];
477
+ next[index] = next[target];
478
+ next[target] = swapped;
479
+ if (next.some((row) => row.text === null)) {
480
+ notify("error", t("steer.reorder.unsupported"));
481
+ return;
482
+ }
483
+ await rebuildQueue(rows, next);
484
+ };
485
+ /**
486
+ * Move one row to an absolute position (drag-and-drop drop handler).
487
+ */
488
+ const reorderTo = async (fromIndex, toIndex) => {
489
+ if (fromIndex === toIndex) return;
490
+ const rows = queue;
491
+ if (rows[fromIndex] === void 0 || rows[toIndex] === void 0) return;
492
+ const next = [...rows];
493
+ const [moved] = next.splice(fromIndex, 1);
494
+ if (moved === void 0) return;
495
+ next.splice(toIndex, 0, moved);
496
+ if (next.some((row) => row.text === null)) {
497
+ notify("error", t("steer.reorder.unsupported"));
498
+ return;
499
+ }
500
+ await rebuildQueue(rows, next);
501
+ };
502
+ /** Arm or execute the two-step clear confirmation. */
503
+ const armClear = () => {
504
+ if (confirmClear) {
505
+ if (confirmTimer.current !== null) clearTimeout(confirmTimer.current);
506
+ setConfirmClear(false);
507
+ clearAll();
508
+ return;
509
+ }
510
+ setConfirmClear(true);
511
+ if (confirmTimer.current !== null) clearTimeout(confirmTimer.current);
512
+ confirmTimer.current = setTimeout(() => setConfirmClear(false), 3e3);
513
+ };
514
+ /** Abort the armed clear confirmation (the explicit cancel button). */
515
+ const cancelClear = () => {
516
+ if (confirmTimer.current !== null) clearTimeout(confirmTimer.current);
517
+ setConfirmClear(false);
518
+ };
519
+ /** Save an in-place edit of one detached (frozen) queued message. */
520
+ const saveFrozenEdit = async (index) => {
521
+ if (editing === null) return;
522
+ const text = editing.text.trim();
523
+ if (text === "") {
524
+ setEditing(null);
525
+ return;
526
+ }
527
+ updatePendingAt(sid, index, text);
528
+ setEditing(null);
529
+ };
530
+ /**
531
+ * Pull one queued row back into the composer draft for editing: the text
532
+ * back-fills the input box and the row leaves the queue; the user edits and
533
+ * resubmits it as a fresh message.
534
+ */
535
+ const pullBackToComposer = async (row) => {
536
+ if (row.text === null) return;
537
+ setDraft(row.text);
538
+ setBusy(row.id);
539
+ try {
540
+ await updateQueue(row.id, { kind: "remove" });
541
+ } catch {
542
+ notify("error", t("steer.pullBackFailed"));
543
+ } finally {
544
+ setBusy((current) => current === row.id ? null : current);
545
+ }
546
+ };
547
+ const clearAll = async () => {
548
+ setClearing(true);
549
+ const pending = [...queue, ...steering];
550
+ try {
551
+ await cancel();
552
+ await Promise.all(pending.map((row) => updateQueue(row.id, { kind: "remove" }).catch(() => void 0)));
553
+ } catch {
554
+ notify("error", t("steer.clearFailed"));
555
+ } finally {
556
+ setClearing(false);
557
+ }
558
+ };
559
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
560
+ className: steer_queue_dock_module_css_default.dock,
561
+ "data-steer-dock": "",
562
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
563
+ className: steer_queue_dock_module_css_default.panel,
564
+ children: [
565
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
566
+ className: steer_queue_dock_module_css_default.toolbar,
567
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
568
+ type: "button",
569
+ className: steer_queue_dock_module_css_default.header,
570
+ "aria-controls": listId,
571
+ "aria-expanded": expanded,
572
+ disabled: queue.length <= 1 || interactionActive,
573
+ onClick: () => {
574
+ setCollapsed((value) => !value);
575
+ },
576
+ children: [
577
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
578
+ className: steer_queue_dock_module_css_default.lead,
579
+ "aria-hidden": true,
580
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutline14, {})
581
+ }),
582
+ queue.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
583
+ className: steer_queue_dock_module_css_default.count,
584
+ children: t("queue.count", { n: queue.length })
585
+ }),
586
+ queue.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
587
+ className: steer_queue_dock_module_css_default.chevron,
588
+ "aria-hidden": true,
589
+ 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, {})
590
+ })
591
+ ]
592
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
593
+ className: steer_queue_dock_module_css_default.toolbarActions,
594
+ children: [confirmClear && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
595
+ type: "button",
596
+ className: steer_queue_dock_module_css_default.clearCancel,
597
+ "aria-label": t("steer.clear.cancel"),
598
+ disabled: clearing || busy !== null || nothingPending,
599
+ onClick: cancelClear,
600
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
601
+ className: steer_queue_dock_module_css_default.clearLabel,
602
+ children: t("steer.clear.cancel")
603
+ })
604
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
605
+ label: confirmClear ? t("steer.clear.confirm") : t("steer.clear"),
606
+ side: "top",
607
+ delayMs: 500,
608
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
609
+ type: "button",
610
+ className: `${steer_queue_dock_module_css_default.clear} ${confirmClear ? steer_queue_dock_module_css_default.clearConfirm : ""}`,
611
+ "aria-label": confirmClear ? t("steer.clear.confirm") : t("steer.clear"),
612
+ disabled: clearing || busy !== null || nothingPending,
613
+ onClick: armClear,
614
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
615
+ className: steer_queue_dock_module_css_default.clearLabel,
616
+ children: confirmClear ? t("steer.clear.confirm") : t("steer.clear")
617
+ })]
618
+ })
619
+ })]
620
+ })]
621
+ }),
622
+ frozen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
623
+ className: steer_queue_dock_module_css_default.frozenBanner,
624
+ role: "status",
625
+ children: t("steer.frozen")
626
+ }),
627
+ frozen && frozenPending.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
628
+ className: steer_queue_dock_module_css_default.frozenList,
629
+ "data-testid": "frozen-list",
630
+ children: frozenPending.map((entry, i) => {
631
+ const editingFrozen = editing?.id === `frozen:${i}`;
632
+ const text = entry.text;
633
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
634
+ className: `${steer_queue_dock_module_css_default.frozenRow} ${dragOver === i && frozen ? steer_queue_dock_module_css_default.rowDragOver : ""}`,
635
+ "data-tier": entry.tier === "force" ? "now" : entry.tier === "safe_point" ? "next" : "later",
636
+ "data-editing": editingFrozen ? "" : void 0,
637
+ draggable: !editingFrozen,
638
+ title: !editingFrozen ? t("steer.dragReorder") : void 0,
639
+ onDragStart: (event) => {
640
+ if (editingFrozen) return;
641
+ dragIndex.current = i;
642
+ event.dataTransfer.effectAllowed = "move";
643
+ event.dataTransfer.setData("text/plain", String(i));
644
+ },
645
+ onDragOver: (event) => {
646
+ if (dragIndex.current === null) return;
647
+ event.preventDefault();
648
+ setDragOver(i);
649
+ },
650
+ onDrop: (event) => {
651
+ event.preventDefault();
652
+ const from = dragIndex.current;
653
+ dragIndex.current = null;
654
+ setDragOver(null);
655
+ if (from !== null) movePending(sid, from, i);
656
+ },
657
+ onDragEnd: () => {
658
+ dragIndex.current = null;
659
+ setDragOver(null);
660
+ },
661
+ children: [
662
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
663
+ className: steer_queue_dock_module_css_default.lead,
664
+ "aria-hidden": true,
665
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutline14, {})
666
+ }),
667
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SteerBadge, {
668
+ tier: entry.tier === "force" ? "now" : entry.tier === "safe_point" ? "next" : "later",
669
+ t
670
+ }),
671
+ editingFrozen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
672
+ ref: editorRef,
673
+ autoFocus: true,
674
+ className: steer_queue_dock_module_css_default.editor,
675
+ "aria-label": t("queue.edit"),
676
+ rows: 1,
677
+ value: editing?.text ?? text,
678
+ onChange: (event) => {
679
+ setEditing({
680
+ id: `frozen:${i}`,
681
+ text: event.currentTarget.value
682
+ });
683
+ },
684
+ onInput: (event) => {
685
+ resizeEditor(event.currentTarget);
686
+ },
687
+ onKeyDown: (event) => {
688
+ if (event.key === "Escape") {
689
+ setEditing(null);
690
+ return;
691
+ }
692
+ if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
693
+ event.preventDefault();
694
+ saveFrozenEdit(i);
695
+ }
696
+ }
697
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
698
+ className: steer_queue_dock_module_css_default.preview,
699
+ children: text
700
+ }),
701
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
702
+ className: steer_queue_dock_module_css_default.actions,
703
+ children: editingFrozen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
704
+ label: t("queue.save"),
705
+ side: "bottom",
706
+ delayMs: 500,
707
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
708
+ type: "button",
709
+ className: steer_queue_dock_module_css_default.action,
710
+ "aria-label": t("queue.save"),
711
+ disabled: editing === null || editing.text.trim() === "",
712
+ onClick: () => {
713
+ saveFrozenEdit(i);
714
+ },
715
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 14 })
716
+ })
717
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
718
+ label: t("queue.cancelEdit"),
719
+ side: "bottom",
720
+ delayMs: 500,
721
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
722
+ type: "button",
723
+ className: steer_queue_dock_module_css_default.action,
724
+ "aria-label": t("queue.cancelEdit"),
725
+ onClick: () => {
726
+ setEditing(null);
727
+ },
728
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
729
+ })
730
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
731
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
732
+ label: t("steer.moveUp"),
733
+ side: "bottom",
734
+ delayMs: 500,
735
+ disabled: i === 0,
736
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
737
+ type: "button",
738
+ className: steer_queue_dock_module_css_default.action,
739
+ "aria-label": t("steer.moveUp"),
740
+ disabled: i === 0,
741
+ onClick: () => movePending(sid, i, i - 1),
742
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, {})
743
+ })
744
+ }),
745
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
746
+ label: t("steer.moveDown"),
747
+ side: "bottom",
748
+ delayMs: 500,
749
+ disabled: i === frozenPending.length - 1,
750
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
751
+ type: "button",
752
+ className: steer_queue_dock_module_css_default.action,
753
+ "aria-label": t("steer.moveDown"),
754
+ disabled: i === frozenPending.length - 1,
755
+ onClick: () => movePending(sid, i, i + 1),
756
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})
757
+ })
758
+ }),
759
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
760
+ label: t("queue.edit"),
761
+ side: "bottom",
762
+ delayMs: 500,
763
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
764
+ type: "button",
765
+ className: steer_queue_dock_module_css_default.action,
766
+ "aria-label": t("queue.edit"),
767
+ onClick: () => {
768
+ setEditing({
769
+ id: `frozen:${i}`,
770
+ text
771
+ });
772
+ },
773
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 })
774
+ })
775
+ }),
776
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
777
+ label: t("queue.remove"),
778
+ side: "bottom",
779
+ delayMs: 500,
780
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
781
+ type: "button",
782
+ className: steer_queue_dock_module_css_default.action,
783
+ "aria-label": t("queue.remove"),
784
+ onClick: () => removePendingAt(sid, i),
785
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
786
+ })
787
+ }),
788
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
789
+ className: steer_queue_dock_module_css_default.plan,
790
+ role: "group",
791
+ "aria-label": t("steer.later.aria"),
792
+ children: [
793
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
794
+ label: t("steer.now"),
795
+ side: "bottom",
796
+ delayMs: 500,
797
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
798
+ type: "button",
799
+ className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierNow}`,
800
+ "aria-label": t("steer.now"),
801
+ "aria-pressed": entry.tier === "force" || void 0,
802
+ onClick: () => setTierAt(sid, i, "force"),
803
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
804
+ className: steer_queue_dock_module_css_default.dot,
805
+ "aria-hidden": true
806
+ })
807
+ })
808
+ }),
809
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
810
+ label: t("steer.next"),
811
+ side: "bottom",
812
+ delayMs: 500,
813
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
814
+ type: "button",
815
+ className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierNext}`,
816
+ "aria-label": t("steer.next"),
817
+ "aria-pressed": entry.tier === "safe_point" || void 0,
818
+ onClick: () => setTierAt(sid, i, "safe_point"),
819
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
820
+ className: steer_queue_dock_module_css_default.dot,
821
+ "aria-hidden": true
822
+ })
823
+ })
824
+ }),
825
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
826
+ label: t("steer.later"),
827
+ side: "bottom",
828
+ delayMs: 500,
829
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
830
+ type: "button",
831
+ className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierLater}`,
832
+ "aria-label": t("steer.later"),
833
+ "aria-pressed": entry.tier === "queue" || void 0,
834
+ onClick: () => setTierAt(sid, i, "queue"),
835
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
836
+ className: steer_queue_dock_module_css_default.dot,
837
+ "aria-hidden": true
838
+ })
839
+ })
840
+ })
841
+ ]
842
+ })
843
+ ] })
844
+ }),
845
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
846
+ className: steer_queue_dock_module_css_default.frozenMark,
847
+ children: t("steer.frozenBadge")
848
+ })
849
+ ]
850
+ }, i);
851
+ })
852
+ }),
853
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
854
+ id: listId,
855
+ className: steer_queue_dock_module_css_default.list,
856
+ hidden: !listVisible,
857
+ children: listVisible && queue.map((row, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
858
+ className: `${steer_queue_dock_module_css_default.row} ${dragOver === index ? steer_queue_dock_module_css_default.rowDragOver : ""}`,
859
+ "data-tier": badgeFor("queued") ?? void 0,
860
+ "data-editing": editing?.id === row.id ? "" : void 0,
861
+ draggable: queueMutable && !reorderUnsupported && busy === null && !frozen,
862
+ title: queueMutable && !reorderUnsupported && busy === null && !frozen ? t("steer.dragReorder") : void 0,
863
+ onDragStart: (event) => {
864
+ dragIndex.current = index;
865
+ event.dataTransfer.effectAllowed = "move";
866
+ event.dataTransfer.setData("text/plain", String(index));
867
+ },
868
+ onDragOver: (event) => {
869
+ if (dragIndex.current === null) return;
870
+ event.preventDefault();
871
+ setDragOver(index);
872
+ },
873
+ onDrop: (event) => {
874
+ event.preventDefault();
875
+ const from = dragIndex.current;
876
+ dragIndex.current = null;
877
+ setDragOver(null);
878
+ if (from !== null) reorderTo(from, index);
879
+ },
880
+ onDragEnd: () => {
881
+ dragIndex.current = null;
882
+ setDragOver(null);
883
+ },
884
+ children: [
885
+ queue.length === 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
886
+ className: steer_queue_dock_module_css_default.lead,
887
+ "aria-hidden": true,
888
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutline14, {})
889
+ }),
890
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SteerBadge, {
891
+ tier: badgeFor("queued"),
892
+ t
893
+ }),
894
+ editing?.id === row.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
895
+ ref: editorRef,
896
+ autoFocus: true,
897
+ className: steer_queue_dock_module_css_default.editor,
898
+ "aria-label": t("queue.edit"),
899
+ rows: 1,
900
+ value: editing.text,
901
+ onChange: (event) => {
902
+ setEditing({
903
+ id: row.id,
904
+ text: event.currentTarget.value
905
+ });
906
+ },
907
+ onInput: (event) => {
908
+ resizeEditor(event.currentTarget);
909
+ },
910
+ onKeyDown: (event) => {
911
+ if (event.key === "Escape") {
912
+ setEditing(null);
913
+ return;
914
+ }
915
+ if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
916
+ event.preventDefault();
917
+ saveEdit();
918
+ }
919
+ }
920
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
921
+ className: steer_queue_dock_module_css_default.preview,
922
+ children: row.preview
923
+ }),
924
+ queueMutable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
925
+ className: steer_queue_dock_module_css_default.actions,
926
+ 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, {
927
+ label: t("queue.save"),
928
+ side: "bottom",
929
+ delayMs: 500,
930
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
931
+ type: "button",
932
+ className: steer_queue_dock_module_css_default.action,
933
+ "aria-label": t("queue.save"),
934
+ disabled: busy !== null || editing.text.trim() === "",
935
+ onClick: () => {
936
+ saveEdit();
937
+ },
938
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 14 })
939
+ })
940
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
941
+ label: t("queue.cancelEdit"),
942
+ side: "bottom",
943
+ delayMs: 500,
944
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
945
+ type: "button",
946
+ className: steer_queue_dock_module_css_default.action,
947
+ "aria-label": t("queue.cancelEdit"),
948
+ disabled: busy !== null,
949
+ onClick: () => {
950
+ setEditing(null);
951
+ },
952
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
953
+ })
954
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
955
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
956
+ label: t("steer.moveUp"),
957
+ side: "bottom",
958
+ delayMs: 500,
959
+ disabled: frozen || reorderUnsupported,
960
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
961
+ type: "button",
962
+ className: steer_queue_dock_module_css_default.action,
963
+ "aria-label": t("steer.moveUp"),
964
+ title: reorderUnsupported ? t("steer.reorder.unsupported") : void 0,
965
+ disabled: busy !== null || frozen || reorderUnsupported,
966
+ onClick: () => {
967
+ reorder(row.id, -1);
968
+ },
969
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronUpOutline14, {})
970
+ })
971
+ }),
972
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
973
+ label: t("steer.moveDown"),
974
+ side: "bottom",
975
+ delayMs: 500,
976
+ disabled: frozen || reorderUnsupported,
977
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
978
+ type: "button",
979
+ className: steer_queue_dock_module_css_default.action,
980
+ "aria-label": t("steer.moveDown"),
981
+ title: reorderUnsupported ? t("steer.reorder.unsupported") : void 0,
982
+ disabled: busy !== null || frozen || reorderUnsupported,
983
+ onClick: () => {
984
+ reorder(row.id, 1);
985
+ },
986
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {})
987
+ })
988
+ }),
989
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
990
+ label: t("steer.pullBack"),
991
+ side: "bottom",
992
+ delayMs: 500,
993
+ disabled: row.text === null || !composerEmpty,
994
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
995
+ type: "button",
996
+ className: steer_queue_dock_module_css_default.action,
997
+ "aria-label": t("steer.pullBack"),
998
+ title: row.text === null ? t("steer.pullBack.unsupported") : !composerEmpty ? t("steer.pullBack.composerBusy") : void 0,
999
+ disabled: busy !== null || frozen || row.text === null || !composerEmpty,
1000
+ onClick: () => {
1001
+ pullBackToComposer(row);
1002
+ },
1003
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRightUpOutline16, {})
1004
+ })
1005
+ }),
1006
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1007
+ label: t("queue.edit"),
1008
+ side: "bottom",
1009
+ delayMs: 500,
1010
+ disabled: row.text === null,
1011
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1012
+ type: "button",
1013
+ className: steer_queue_dock_module_css_default.action,
1014
+ "aria-label": t("queue.edit"),
1015
+ title: row.text === null ? t("queue.edit.unsupported") : void 0,
1016
+ disabled: busy !== null || row.text === null,
1017
+ onClick: () => {
1018
+ if (row.text !== null) setEditing({
1019
+ id: row.id,
1020
+ text: row.text
1021
+ });
1022
+ },
1023
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 })
1024
+ })
1025
+ }),
1026
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1027
+ label: t("queue.remove"),
1028
+ side: "bottom",
1029
+ delayMs: 500,
1030
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1031
+ type: "button",
1032
+ className: steer_queue_dock_module_css_default.action,
1033
+ "aria-label": t("queue.remove"),
1034
+ disabled: busy !== null,
1035
+ onClick: () => {
1036
+ applyAction(row.id, { kind: "remove" }, t("queue.removeFailed"));
1037
+ },
1038
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
1039
+ })
1040
+ }),
1041
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1042
+ className: steer_queue_dock_module_css_default.plan,
1043
+ role: "group",
1044
+ "aria-label": t("steer.later.aria"),
1045
+ children: [
1046
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1047
+ label: t("steer.now"),
1048
+ side: "bottom",
1049
+ delayMs: 500,
1050
+ disabled: planDisabled || row.text === null,
1051
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1052
+ type: "button",
1053
+ className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierNow}`,
1054
+ "aria-label": t("steer.now"),
1055
+ title: planDisabled ? t("steer.unavailable.running") : row.text === null ? t("steer.now.unsupported") : void 0,
1056
+ disabled: busy !== null || planDisabled || row.text === null,
1057
+ onClick: () => {
1058
+ steerRow(row, "now");
1059
+ },
1060
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1061
+ className: steer_queue_dock_module_css_default.dot,
1062
+ "aria-hidden": true
1063
+ })
1064
+ })
1065
+ }),
1066
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1067
+ label: t("steer.next"),
1068
+ side: "bottom",
1069
+ delayMs: 500,
1070
+ disabled: planDisabled,
1071
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1072
+ type: "button",
1073
+ className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierNext}`,
1074
+ "aria-label": t("steer.next"),
1075
+ title: planDisabled ? t("steer.unavailable.running") : void 0,
1076
+ disabled: busy !== null || planDisabled,
1077
+ onClick: () => {
1078
+ steerRow(row, "next");
1079
+ },
1080
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1081
+ className: steer_queue_dock_module_css_default.dot,
1082
+ "aria-hidden": true
1083
+ })
1084
+ })
1085
+ }),
1086
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1087
+ label: t("steer.later"),
1088
+ side: "bottom",
1089
+ delayMs: 500,
1090
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1091
+ type: "button",
1092
+ className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierLater}`,
1093
+ "aria-label": t("steer.later"),
1094
+ "aria-pressed": true,
1095
+ disabled: busy !== null || clearing,
1096
+ onClick: () => {},
1097
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1098
+ className: steer_queue_dock_module_css_default.dot,
1099
+ "aria-hidden": true
1100
+ })
1101
+ })
1102
+ })
1103
+ ]
1104
+ })
1105
+ ] })
1106
+ })
1107
+ ]
1108
+ }, row.id))
1109
+ }),
1110
+ steering.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1111
+ className: steer_queue_dock_module_css_default.steeringList,
1112
+ "data-steering-list": "",
1113
+ children: steering.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1114
+ className: steer_queue_dock_module_css_default.row,
1115
+ "data-tier": badgeFor("steering") ?? void 0,
1116
+ children: [
1117
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1118
+ className: steer_queue_dock_module_css_default.lead,
1119
+ "aria-hidden": true,
1120
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutline14, {})
1121
+ }),
1122
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SteerBadge, {
1123
+ tier: badgeFor("steering"),
1124
+ t
1125
+ }),
1126
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1127
+ className: steer_queue_dock_module_css_default.preview,
1128
+ children: row.preview
1129
+ }),
1130
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1131
+ className: steer_queue_dock_module_css_default.actions,
1132
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1133
+ label: t("steer.revoke"),
1134
+ side: "bottom",
1135
+ delayMs: 500,
1136
+ disabled: row.text === null,
1137
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1138
+ type: "button",
1139
+ className: `${steer_queue_dock_module_css_default.tier} ${steer_queue_dock_module_css_default.tierLater}`,
1140
+ "aria-label": t("steer.revoke"),
1141
+ title: row.text === null ? t("steer.revoke.unsupported") : void 0,
1142
+ disabled: busy !== null || row.text === null || frozen,
1143
+ onClick: () => {
1144
+ revokeToLater(row);
1145
+ },
1146
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1147
+ className: steer_queue_dock_module_css_default.dot,
1148
+ "aria-hidden": true
1149
+ })
1150
+ })
1151
+ })
1152
+ })
1153
+ ]
1154
+ }, row.id))
1155
+ })
1156
+ ]
1157
+ })
1158
+ });
1159
+ }
1160
+ /** Badge label keys per tier. */
1161
+ const BADGE_KEYS = {
1162
+ now: "steer.badge.now",
1163
+ next: "steer.badge.next",
1164
+ later: "steer.badge.later"
1165
+ };
1166
+ /** Badge tint classes per tier (present at bundle time; typed through the css-modules declaration). */
1167
+ const BADGE_CLASSES = {
1168
+ now: steer_queue_dock_module_css_default.badgeNow,
1169
+ next: steer_queue_dock_module_css_default.badgeNext,
1170
+ later: steer_queue_dock_module_css_default.badgeLater
1171
+ };
1172
+ /** One row's tier badge: colored dot + label; context rows render nothing. */
1173
+ function SteerBadge({ tier, t }) {
1174
+ if (tier === null) return null;
1175
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1176
+ className: `${steer_queue_dock_module_css_default.badge} ${BADGE_CLASSES[tier]}`,
1177
+ "data-badge": tier,
1178
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1179
+ className: steer_queue_dock_module_css_default.dot,
1180
+ "aria-hidden": true
1181
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1182
+ className: steer_queue_dock_module_css_default.badgeLabel,
1183
+ children: t(BADGE_KEYS[tier])
1184
+ })]
1185
+ });
1186
+ }
1187
+ //#endregion
1188
+ //#region src/client/session-guard-bridge.ts
1189
+ /**
1190
+ * input-traffic ↔ dsh-session-guard 透传桥(D8 fail-open)。
1191
+ *
1192
+ * input-traffic **只做冻结增强**(队列冻结/解冻),服务端会话门(暂停/恢复会话)
1193
+ * 归 dsh-session-guard 插件。冻结/解冻按钮触发时,尽力调用
1194
+ * `sessionGuard.stopNextTurn` / `resume`;**插件未装**(路由 404 / 网络失败 /
1195
+ * 返回错误)→ 静默跳过,**绝不报错**,前端冻结仍正常生效。
1196
+ *
1197
+ * 不吸收任何 auto-continue / 重试逻辑(重试归后端 dsh-session-guard,D9)。
1198
+ */
1199
+ /** 尽力调用 sessionGuard.stopNextTurn(停掉 session 下一回合)。失败静默。 */
1200
+ async function sessionGuardStopNextTurn(sessionId) {
1201
+ return callGuard(sessionId, "stopNextTurn");
1202
+ }
1203
+ /** 尽力调用 sessionGuard.resume。失败静默。 */
1204
+ async function sessionGuardResume(sessionId) {
1205
+ return callGuard(sessionId, "resume");
1206
+ }
1207
+ async function callGuard(sessionId, action) {
1208
+ try {
1209
+ const res = await fetch("/session-guard/rpc", {
1210
+ method: "POST",
1211
+ headers: { "content-type": "application/json" },
1212
+ body: JSON.stringify({
1213
+ sessionId,
1214
+ action
1215
+ })
1216
+ });
1217
+ if (!res.ok) return false;
1218
+ return (await res.json().catch(() => null))?.ok === true;
1219
+ } catch {
1220
+ return false;
1221
+ }
1222
+ }
1223
+ try {
1224
+ globalThis.__DSH_SESSION_GUARD_BRIDGE__ = true;
1225
+ } catch {}
1226
+ //#endregion
1227
+ //#region \0dsh-css:E:\test\rewrite-agently\mine-dsh-plugins\dsh-input-traffic\src\client\freeze-button.module.css.mjs
1228
+ const css = ".BUSMDW_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}.BUSMDW_freeze:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.BUSMDW_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)}.BUSMDW_label{font-size:12px}";
1229
+ const tagId = "dsh-input-traffic/freeze-button.module.css";
1230
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
1231
+ const tag = document.createElement("style");
1232
+ tag.dataset.plugin = "dsh-input-traffic";
1233
+ tag.dataset.pluginCss = tagId;
1234
+ tag.textContent = css;
1235
+ document.head.appendChild(tag);
1236
+ }
1237
+ var freeze_button_module_css_default = {
1238
+ "label": "BUSMDW_label",
1239
+ "freeze": "BUSMDW_freeze"
1240
+ };
1241
+ //#endregion
1242
+ //#region src/client/freeze-button.tsx
1243
+ /**
1244
+ * Session freeze/resume control, mounted in the composer's right tool row
1245
+ * (`conversation.input.right`) so it is reachable before anything queues.
1246
+ *
1247
+ * Peak-hour pause semantics: freeze does NOT interrupt the running turn —it
1248
+ * finishes naturally. The queued messages are detached (removed, preserved in
1249
+ * the shared store), so the driver finds no pending work and stops after the
1250
+ * current turn. Resume re-submits the preserved texts, waking the driver and
1251
+ * continuing the queue.
1252
+ *
1253
+ * dsh-session-guard 协作(D8 fail-open):
1254
+ * - 冻结 = 前端摘队列(本插件)+ composer block(阻止新输入漏进对话) + 尽力调服务端
1255
+ * sessionGuard.stopNextTurn(停掉 session 下一回合;session-guard 未装时静默跳过)。
1256
+ * - 解冻 = 清 composer block + **先** await sessionGuard.resume(让被打断回合的自然
1257
+ * 下一步先发生),**再**重投队列(later 级条目因此排在自然 next turn 之后,不再插队)。
1258
+ * - 本插件只做冻结增强,不承担重试/暂停决策(归后端,D9)。
1259
+ */
1260
+ /**
1261
+ * Freeze/resume toggle for the peak-hour scenario.
1262
+ * @param props - slot props; the session snapshot drives the detach list.
1263
+ */
1264
+ function FreezeButton({ useSession, updateQueue, cancel, send, sendSteer, sessionId, setComposerBlock, notify, t }) {
1265
+ const sid = sessionId ?? "";
1266
+ const { frozen } = (0, react.useSyncExternalStore)(freezeStore.subscribe, () => freezeStore.getSnapshot(sid));
1267
+ const queue = useSession((s) => s.queue);
1268
+ const freeze = async () => {
1269
+ const rows = queue.filter((row) => row.placement === "queued" || row.placement === "steering");
1270
+ const pending = rows.flatMap((row) => row.text === null ? [] : [{
1271
+ text: row.text,
1272
+ tier: row.placement === "steering" ? "safe_point" : "queue"
1273
+ }]);
1274
+ await Promise.all(rows.map((row) => updateQueue(row.id, { kind: "remove" }).catch(() => void 0)));
1275
+ freezeStore.set(sid, {
1276
+ frozen: true,
1277
+ pending
1278
+ });
1279
+ setComposerBlock?.(t("steer.frozenInput"));
1280
+ if (sessionId !== void 0) sessionGuardStopNextTurn(sessionId);
1281
+ };
1282
+ const resume = async () => {
1283
+ const pending = freezeStore.getSnapshot(sid).pending;
1284
+ freezeStore.set(sid, {
1285
+ frozen: false,
1286
+ pending: []
1287
+ });
1288
+ setComposerBlock?.(void 0);
1289
+ try {
1290
+ if (sessionId !== void 0) await sessionGuardResume(sessionId);
1291
+ for (const entry of pending) {
1292
+ if (entry.tier === "force") await cancel();
1293
+ if (entry.tier === "safe_point" && sendSteer !== void 0) await sendSteer(entry.text);
1294
+ else await send(entry.text);
1295
+ }
1296
+ } catch {
1297
+ notify("error", t("steer.resumeFailed"));
1298
+ }
1299
+ };
1300
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1301
+ type: "button",
1302
+ className: freeze_button_module_css_default.freeze,
1303
+ "aria-label": frozen ? t("steer.resume") : t("steer.freeze"),
1304
+ "aria-pressed": frozen || void 0,
1305
+ title: frozen ? t("steer.frozen") : void 0,
1306
+ onClick: () => {
1307
+ if (frozen) resume();
1308
+ else freeze();
1309
+ },
1310
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1311
+ className: freeze_button_module_css_default.label,
1312
+ children: frozen ? t("steer.resume") : t("steer.freeze")
1313
+ })
1314
+ });
1315
+ }
1316
+ //#endregion
1317
+ //#region src/client/family-card.tsx
1318
+ /**
1319
+ * 输入流量 —— 「起子插件设置」family tab 的只读状态卡。
1320
+ *
1321
+ * 本插件是纯 client 接管型:没有自有 Config,busyEnter 在 apply 时钉死为
1322
+ * `queue`(写 ui-conversation entry),官方 General 行同时被压掉。因此这张卡
1323
+ * **只读**:展示钉死状态与使用说明,不提供任何写入口——提供 busyEnter 切换
1324
+ * 会与接管设计(apply 重新钉死)打架。
1325
+ *
1326
+ * 数据面:`configForms.get('ui-conversation')` 的快照(跨 entry 读,宿主
1327
+ * describe 镜像对该 entry 本就服务——官方 Enter 行就是它的编辑器)。
1328
+ */
1329
+ const row = {
1330
+ display: "flex",
1331
+ alignItems: "center",
1332
+ justifyContent: "space-between",
1333
+ gap: 12,
1334
+ padding: "8px 0",
1335
+ fontSize: 13
1336
+ };
1337
+ const label = { color: "var(--dsw-alias-label-primary, inherit)" };
1338
+ const dim = {
1339
+ color: "var(--dsw-alias-label-tertiary, rgba(127,127,127,.8))",
1340
+ fontSize: 12,
1341
+ lineHeight: 1.5
1342
+ };
1343
+ function InputTrafficFamilyCard({ scope, t }) {
1344
+ const pinned = (0, react.useSyncExternalStore)((listener) => scope.subscribe(listener), () => scope.getSnapshot()).value?.busyEnter ?? "queue";
1345
+ const tr = (key) => t ? t(key) : FALLBACK_ZH[key] ?? key;
1346
+ return (0, react.createElement)("div", { style: {
1347
+ display: "grid",
1348
+ gap: 6
1349
+ } }, (0, react.createElement)("div", { style: row }, (0, react.createElement)("span", { style: label }, tr("family.busyEnter")), (0, react.createElement)("span", { style: dim }, `busyEnter = ${pinned}`)), (0, react.createElement)("p", { style: {
1350
+ ...dim,
1351
+ margin: 0
1352
+ } }, tr("family.desc")));
1353
+ }
1354
+ /** 无 locale 服务时的中文兜底(与 zh 字典键一一对应)。 */
1355
+ const FALLBACK_ZH = {
1356
+ "family.busyEnter": "busy-Enter 行为(本插件接管)",
1357
+ "family.desc": "普通回车在繁忙时进入排队(queue),不会打断当前回答;打断请用输入区的冻结/转向按钮。该行为由本插件自动接管,无需配置。"
1358
+ };
1359
+ //#endregion
1360
+ //#region src/client/hide-enter-row.tsx
1361
+ /** Render nothing: the official busy-Enter row is hidden while mounted. */
1362
+ function HideEnterRow(_props) {
1363
+ return null;
1364
+ }
1365
+ //#endregion
1366
+ //#region src/client/index.ts
1367
+ /** Durable conversation settings namespace owned by ui-conversation. */
1368
+ const CONVERSATION_SETTINGS_NAMESPACE = "ui-conversation";
1369
+ /** Busy-Enter field inside that namespace; the plugin pins it to queue. */
1370
+ const BUSY_ENTER_FIELD = "busyEnter";
1371
+ /**
1372
+ * Display order of the queue strip inside `conversation.input.dock`.
1373
+ *
1374
+ * List rows render sorted by `order` ascending, so a value above every other
1375
+ * contributor keeps the strip as the bottom-most entry of the band — directly
1376
+ * on top of the composer card. Known contributors on 0.1.5-rc.2: `todo` 0,
1377
+ * `goal` 10, official `queue` 20, `dsh-perm-gate.notice` 30. DSH has no "last"
1378
+ * slot semantics, so this is a convention, not a
1379
+ * structural guarantee: a third party registering a larger value could still
1380
+ * land below us.
1381
+ */
1382
+ const QUEUE_DOCK_ORDER = 1e3;
1383
+ /**
1384
+ * Deliver one plain-text message into the session's next step. The exposed
1385
+ * conversation `send` verb only queues into the next turn, so resume steers
1386
+ * through the session face's steer-mode prompt instead (no harness change).
1387
+ * @param ctx - root context (resolves the session face behind the scope).
1388
+ * @param actx - agent-scoped context of the owning session.
1389
+ * @param text - message text to deliver.
1390
+ */
1391
+ function steerPrompt(actx, text) {
1392
+ const conversation = actx.get("conversation");
1393
+ if (conversation === void 0) return Promise.reject(/* @__PURE__ */ new Error("steer resume: conversation service unavailable"));
1394
+ return conversation.send(text);
1395
+ }
1396
+ /** Services required by the browser half. */
1397
+ const inject = [
1398
+ "slots",
1399
+ "locale",
1400
+ "sessions",
1401
+ "conversation",
1402
+ "configForms"
1403
+ ];
1404
+ /**
1405
+ * Client plugin body: dictionaries, busy-Enter pinning, and the two slot
1406
+ * shadowings.
1407
+ * @param ctx - client root context.
1408
+ */
1409
+ function apply(ctx) {
1410
+ ctx.effect(() => ctx.locale.register(NS, {
1411
+ zh,
1412
+ en
1413
+ }), "dsh-input-traffic: dictionaries");
1414
+ const tFamily = ctx.locale.bind(NS);
1415
+ const conversationSettings = ctx.configForms.get(CONVERSATION_SETTINGS_NAMESPACE);
1416
+ conversationSettings.set(BUSY_ENTER_FIELD, "queue");
1417
+ ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
1418
+ name: "conversation.input.dock",
1419
+ id: "queue",
1420
+ order: QUEUE_DOCK_ORDER,
1421
+ priority: -1,
1422
+ locale: NS,
1423
+ inject: (sessionId) => {
1424
+ const actx = ctx.sessions.scope(sessionId);
1425
+ if (actx === void 0) throw new Error(`steer dock: session "${sessionId}" resolved no scope`);
1426
+ const conversation = actx.get("conversation");
1427
+ if (conversation === void 0) throw new Error("steer dock: conversation service unavailable");
1428
+ return {
1429
+ updateQueue: (itemId, action) => conversation.updateQueue(itemId, action),
1430
+ cancel: () => conversation.cancel(),
1431
+ send: (text) => conversation.send(text),
1432
+ setDraft: (text) => {
1433
+ conversation.input.for(actx).setDraft(text);
1434
+ },
1435
+ notify: (level, text) => {
1436
+ conversation.input.for(actx).notify(level, text);
1437
+ }
1438
+ };
1439
+ }
1440
+ }, SteerQueueDock));
1441
+ ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
1442
+ name: "conversation.input.right",
1443
+ id: "steer-freeze",
1444
+ order: 30,
1445
+ locale: NS,
1446
+ inject: (sessionId) => {
1447
+ const actx = ctx.sessions.scope(sessionId);
1448
+ if (actx === void 0) throw new Error(`steer freeze: session "${sessionId}" resolved no scope`);
1449
+ const conversation = actx.get("conversation");
1450
+ if (conversation === void 0) throw new Error("steer freeze: conversation service unavailable");
1451
+ return {
1452
+ updateQueue: (itemId, action) => conversation.updateQueue(itemId, action),
1453
+ cancel: () => conversation.cancel(),
1454
+ send: (text) => conversation.send(text),
1455
+ sendSteer: (text) => steerPrompt(actx, text),
1456
+ sessionId: String(sessionId),
1457
+ setDraft: (text) => {
1458
+ conversation.input.for(actx).setDraft(text);
1459
+ },
1460
+ notify: (level, text) => {
1461
+ conversation.input.for(actx).notify(level, text);
1462
+ },
1463
+ setComposerBlock: (reason) => {
1464
+ conversation.blocks.set(sessionId, reason === void 0 ? void 0 : { reason });
1465
+ }
1466
+ };
1467
+ }
1468
+ }, FreezeButton));
1469
+ ctx.slots.inject("dsh-family.tab", () => ctx.slots.register({
1470
+ name: "dsh-family.tab",
1471
+ id: "input-traffic",
1472
+ order: 50,
1473
+ label: () => tFamily("family.title"),
1474
+ locale: NS,
1475
+ inject: () => ({ scope: conversationSettings })
1476
+ }, InputTrafficFamilyCard));
1477
+ ctx.slots.inject("settings.general.item", () => ctx.slots.register({
1478
+ name: "settings.general.item",
1479
+ id: "composer-enter",
1480
+ order: 20,
1481
+ priority: -1,
1482
+ locale: NS
1483
+ }, HideEnterRow));
1484
+ }
1485
+ //#endregion
1486
+ exports.QUEUE_DOCK_ORDER = QUEUE_DOCK_ORDER;
1487
+ exports.apply = apply;
1488
+ exports.inject = inject;
1489
+ return module.exports;
1490
+ }
1491
+ });
1492
+
1434
1493
  //# sourceMappingURL=client.js.map