@flowingspring/dsh-voco 0.3.2 → 0.3.4
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/cordis.patch.yml +6 -3
- package/lib/client.js +631 -40
- package/lib/client.js.map +1 -1
- package/lib/plugins/voice-assistant.js +5135 -32
- package/package.json +4 -1
package/lib/client.js
CHANGED
|
@@ -6,6 +6,8 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
8
|
let react = require("react");
|
|
9
|
+
let react_dom_client = require("react-dom/client");
|
|
10
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
11
|
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
10
12
|
//#region \0dsh-css:D:\QUAN\DevCode\Python\Project\dsh\dsh-voco\packages\ui-voice\src\client\VoiceControl.module.css.mjs
|
|
11
13
|
const css$3 = ".TFZsua_controls{align-items:center;gap:2px;display:inline-flex}.TFZsua_button{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:8px;justify-content:center;align-items:center;padding:0;display:inline-flex}.TFZsua_button:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.TFZsua_active{color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-interactive-bg-hover-solid)}.TFZsua_button[data-muted=true]{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, currentColor 12%, transparent)}.TFZsua_interruptButton,.TFZsua_interruptButton:hover{color:var(--dsw-alias-state-error-primary)}.TFZsua_button[data-muted=true]:hover{background:color-mix(in srgb, currentColor 18%, transparent)}.TFZsua_button[data-state=speaking][data-muted=false]{animation:1.2s ease-in-out infinite TFZsua_voice-pulse}@keyframes TFZsua_voice-pulse{50%{opacity:.55;transform:scale(.94)}}@media (prefers-reduced-motion:reduce){.TFZsua_button[data-state=speaking][data-muted=false]{animation:none}}";
|
|
@@ -18,10 +20,10 @@ window.__ModuleLoader__.load({
|
|
|
18
20
|
document.head.appendChild(tag);
|
|
19
21
|
}
|
|
20
22
|
var VoiceControl_module_css_default = {
|
|
21
|
-
"controls": "TFZsua_controls",
|
|
22
23
|
"button": "TFZsua_button",
|
|
23
|
-
"interruptButton": "TFZsua_interruptButton",
|
|
24
24
|
"voice-pulse": "TFZsua_voice-pulse",
|
|
25
|
+
"interruptButton": "TFZsua_interruptButton",
|
|
26
|
+
"controls": "TFZsua_controls",
|
|
25
27
|
"active": "TFZsua_active"
|
|
26
28
|
};
|
|
27
29
|
//#endregion
|
|
@@ -130,6 +132,26 @@ window.__ModuleLoader__.load({
|
|
|
130
132
|
});
|
|
131
133
|
}
|
|
132
134
|
//#endregion
|
|
135
|
+
//#region ../ui-voice/src/client/session-archive.ts
|
|
136
|
+
/**
|
|
137
|
+
* Returns whether a session is archived itself or inherits archive state from
|
|
138
|
+
* any known ancestor. The optional fallback resolves a parent when the
|
|
139
|
+
* summary is not present yet (for example while a subagent route is loading).
|
|
140
|
+
* Cycles terminate the walk without throwing in render paths.
|
|
141
|
+
*/
|
|
142
|
+
function isSessionEffectivelyArchived(sessionId, archivedSessionIds, summaries, fallbackParent) {
|
|
143
|
+
const archived = archivedSessionIds instanceof Set ? archivedSessionIds : new Set(archivedSessionIds);
|
|
144
|
+
const visited = /* @__PURE__ */ new Set();
|
|
145
|
+
let current = sessionId;
|
|
146
|
+
while (current !== void 0 && !visited.has(current)) {
|
|
147
|
+
if (archived.has(current)) return true;
|
|
148
|
+
visited.add(current);
|
|
149
|
+
const summary = summaries[current];
|
|
150
|
+
current = (summary !== null && typeof summary === "object" && "parentId" in summary ? summary.parentId : void 0) ?? fallbackParent?.(current);
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
//#endregion
|
|
133
155
|
//#region \0dsh-css:D:\QUAN\DevCode\Python\Project\dsh\dsh-voco\packages\ui-voice\src\client\VoiceNodeViews.module.css.mjs
|
|
134
156
|
const css$2 = ".Kt2smW_utterance{flex-direction:column;align-items:flex-start;gap:6px;min-width:0;display:flex}.Kt2smW_utterance[data-role=user]{align-items:flex-end}.Kt2smW_bubble{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);max-width:min(525px,82%);color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;white-space:pre-wrap;border-radius:18px;padding:10px 16px;font-size:16px;line-height:24px}.Kt2smW_utterance[data-role=user] .Kt2smW_bubble{background:var(--dsw-specific-bubble);border-color:#0000}.Kt2smW_utterance[data-state=streaming] .Kt2smW_bubble{color:var(--dsw-alias-label-secondary)}.Kt2smW_meta,.Kt2smW_voiceBadge{align-items:center;display:inline-flex}.Kt2smW_meta{color:var(--dsw-alias-label-tertiary);gap:8px;padding:0 5px;font-size:10px;line-height:14px}.Kt2smW_voiceBadge{color:var(--dsw-alias-state-business-primary);letter-spacing:.04em;gap:4px;font-weight:600}.Kt2smW_miniWave{align-items:center;gap:1px;height:10px;display:inline-flex}.Kt2smW_miniWave i{background:currentColor;border-radius:1px;width:1.5px;height:4px}.Kt2smW_miniWave i:nth-child(2){height:8px}.Kt2smW_miniWave i:nth-child(3){height:6px}.Kt2smW_taskCard{box-sizing:border-box;width:fit-content;min-width:0;max-width:min(525px,100%)}.Kt2smW_taskSummary{min-width:0;max-width:100%;color:inherit;cursor:pointer;font:inherit;background:0 0;border:0;border-radius:6px;align-items:center;gap:7px;margin:-3px -5px;padding:3px 5px;display:inline-flex}.Kt2smW_taskSummary:hover{background:var(--dsw-alias-bg-layer-1)}.Kt2smW_taskDot{background:var(--dsw-alias-state-business-primary);border-radius:50%;flex:none;width:7px;height:7px}.Kt2smW_taskCard[data-status=completed] .Kt2smW_taskDot{background:var(--dsw-alias-state-success-primary)}.Kt2smW_taskCard[data-status=failed] .Kt2smW_taskDot{background:var(--dsw-alias-state-error-primary)}.Kt2smW_taskCard[data-status=cancelled] .Kt2smW_taskDot,.Kt2smW_taskCard[data-status=interrupted] .Kt2smW_taskDot{background:var(--dsw-alias-state-warn-primary)}.Kt2smW_taskTitle{min-width:0;color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:600;line-height:20px;overflow:hidden}.Kt2smW_taskStatus{color:var(--dsw-alias-label-tertiary);flex:none;font-size:11px;line-height:16px}.Kt2smW_taskStatus:before{content:\"·\";margin-right:7px}.Kt2smW_taskChevron{border-right:1px solid var(--dsw-alias-label-tertiary);border-bottom:1px solid var(--dsw-alias-label-tertiary);width:6px;height:6px;margin:0 2px 0 1px;transition:transform .12s;transform:rotate(-45deg)}.Kt2smW_taskCard[data-expanded=true] .Kt2smW_taskChevron{transform:rotate(45deg)}.Kt2smW_taskInput,.Kt2smW_taskUpdate{overflow-wrap:anywhere;margin:0;font-size:13px;line-height:20px}.Kt2smW_taskInput{color:var(--dsw-alias-label-primary)}.Kt2smW_taskUpdate{color:var(--dsw-alias-label-secondary);margin-top:4px}.Kt2smW_taskDetails{border-left:1px solid var(--dsw-alias-border-l2);margin:9px 0 0 3px;padding:2px 0 2px 16px}.Kt2smW_taskActions{align-items:center;gap:14px;margin-top:7px;display:flex}.Kt2smW_taskLink{color:var(--dsw-alias-state-business-primary);cursor:pointer;font:inherit;background:0 0;border:0;padding:0;font-size:12px;line-height:18px}.Kt2smW_taskLink:hover:not(:disabled){text-underline-position:from-font;text-decoration:underline}.Kt2smW_taskLink:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.Kt2smW_taskCancel{color:var(--dsw-alias-state-error-primary)}";
|
|
135
157
|
const tagId$2 = "@flowingspring/dsh-voco/VoiceNodeViews.module.css";
|
|
@@ -141,23 +163,23 @@ window.__ModuleLoader__.load({
|
|
|
141
163
|
document.head.appendChild(tag);
|
|
142
164
|
}
|
|
143
165
|
var VoiceNodeViews_module_css_default = {
|
|
166
|
+
"taskDot": "Kt2smW_taskDot",
|
|
167
|
+
"bubble": "Kt2smW_bubble",
|
|
168
|
+
"taskDetails": "Kt2smW_taskDetails",
|
|
169
|
+
"taskLink": "Kt2smW_taskLink",
|
|
144
170
|
"taskCancel": "Kt2smW_taskCancel",
|
|
171
|
+
"taskTitle": "Kt2smW_taskTitle",
|
|
145
172
|
"taskSummary": "Kt2smW_taskSummary",
|
|
146
173
|
"meta": "Kt2smW_meta",
|
|
147
|
-
"
|
|
148
|
-
"taskStatus": "Kt2smW_taskStatus",
|
|
149
|
-
"taskInput": "Kt2smW_taskInput",
|
|
150
|
-
"taskTitle": "Kt2smW_taskTitle",
|
|
151
|
-
"taskActions": "Kt2smW_taskActions",
|
|
152
|
-
"taskLink": "Kt2smW_taskLink",
|
|
174
|
+
"utterance": "Kt2smW_utterance",
|
|
153
175
|
"voiceBadge": "Kt2smW_voiceBadge",
|
|
154
|
-
"bubble": "Kt2smW_bubble",
|
|
155
|
-
"taskChevron": "Kt2smW_taskChevron",
|
|
156
|
-
"taskUpdate": "Kt2smW_taskUpdate",
|
|
157
176
|
"taskCard": "Kt2smW_taskCard",
|
|
158
|
-
"
|
|
177
|
+
"taskChevron": "Kt2smW_taskChevron",
|
|
159
178
|
"miniWave": "Kt2smW_miniWave",
|
|
160
|
-
"
|
|
179
|
+
"taskActions": "Kt2smW_taskActions",
|
|
180
|
+
"taskStatus": "Kt2smW_taskStatus",
|
|
181
|
+
"taskUpdate": "Kt2smW_taskUpdate",
|
|
182
|
+
"taskInput": "Kt2smW_taskInput"
|
|
161
183
|
};
|
|
162
184
|
//#endregion
|
|
163
185
|
//#region ../ui-voice/src/client/VoiceNodeViews.tsx
|
|
@@ -201,9 +223,19 @@ window.__ModuleLoader__.load({
|
|
|
201
223
|
interrupted: "task.status.interrupted"
|
|
202
224
|
};
|
|
203
225
|
/** Render the task link from a Voice Session to its bound background Agent Session. */
|
|
204
|
-
function VoiceDelegationView({ node, sessionId, useSessions, useVoice, openSession, cancelTask, t }) {
|
|
226
|
+
function VoiceDelegationView({ node, sessionId, useSessions, useWorkspaces, useVoice, openSession, cancelTask, recordTaskActivity, t }) {
|
|
205
227
|
const [expanded, setExpanded] = (0, react.useState)(false);
|
|
206
|
-
|
|
228
|
+
(0, react.useEffect)(() => {
|
|
229
|
+
const at = node.data.lastActivityAt;
|
|
230
|
+
if (recordTaskActivity !== void 0 && at !== void 0) recordTaskActivity(node.data.taskSessionId, at, sessionId);
|
|
231
|
+
}, [
|
|
232
|
+
node.data.lastActivityAt,
|
|
233
|
+
node.data.taskSessionId,
|
|
234
|
+
recordTaskActivity,
|
|
235
|
+
sessionId
|
|
236
|
+
]);
|
|
237
|
+
const archivedSessionIds = useWorkspaces((state) => state.archivedSessionIds);
|
|
238
|
+
const navigable = useSessions((sessions) => !isSessionEffectivelyArchived(node.data.taskSessionId, archivedSessionIds, sessions.byId, (id) => id === node.data.taskSessionId ? sessionId : void 0) && (sessions.ids.includes(node.data.taskSessionId) || Object.values(sessions.subagentsByParent).some((catalog) => catalog.entries.some((entry) => entry.kind === "child" && entry.id === node.data.taskSessionId))));
|
|
207
239
|
const cancellable = useVoice((voice) => voice.sessionId === sessionId && voice.state !== "off" && voice.state !== "error") && !isTerminalTaskStatus(node.data.status);
|
|
208
240
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
209
241
|
className: VoiceNodeViews_module_css_default.taskCard,
|
|
@@ -285,10 +317,10 @@ window.__ModuleLoader__.load({
|
|
|
285
317
|
document.head.appendChild(tag);
|
|
286
318
|
}
|
|
287
319
|
var VoiceOverlay_module_css_default = {
|
|
288
|
-
"
|
|
320
|
+
"stop": "GPPB2G_stop",
|
|
289
321
|
"wave": "GPPB2G_wave",
|
|
322
|
+
"voice-wave": "GPPB2G_voice-wave",
|
|
290
323
|
"root": "GPPB2G_root",
|
|
291
|
-
"stop": "GPPB2G_stop",
|
|
292
324
|
"status": "GPPB2G_status"
|
|
293
325
|
};
|
|
294
326
|
//#endregion
|
|
@@ -338,7 +370,7 @@ window.__ModuleLoader__.load({
|
|
|
338
370
|
}
|
|
339
371
|
//#endregion
|
|
340
372
|
//#region \0dsh-css:D:\QUAN\DevCode\Python\Project\dsh\dsh-voco\packages\ui-voice\src\client\VoiceSessionMarkers.module.css.mjs
|
|
341
|
-
const css = "._0brRca_slot{flex:none;justify-content:center;align-items:center;width:16px;height:20px;display:inline-flex}._0brRca_marker{width:16px;height:16px;color:var(--dsw-alias-state-business-primary);pointer-events:none;justify-content:center;align-items:center;display:inline-flex}._0brRca_marker svg{width:14px;height:14px;display:block}";
|
|
373
|
+
const css = "._0brRca_slot{flex:none;justify-content:center;align-items:center;width:16px;height:20px;display:inline-flex}._0brRca_marker{width:16px;height:16px;color:var(--dsw-alias-state-business-primary);pointer-events:none;justify-content:center;align-items:center;display:inline-flex}._0brRca_marker svg{width:14px;height:14px;display:block}._0brRca_toggle{width:18px;height:18px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:4px;flex:none;justify-content:center;align-items:center;margin-left:1px;padding:0;font-size:16px;line-height:16px;display:inline-flex}._0brRca_toggle:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}._0brRca_children{flex-direction:column;gap:2px;margin:2px 0 2px 20px;display:flex}._0brRca_child{min-width:0;height:30px;color:var(--dsw-alias-label-secondary);cursor:pointer;text-align:left;font:inherit;background:0 0;border:0;border-radius:8px;align-items:center;gap:8px;padding:0 10px 0 12px;display:flex;position:relative}._0brRca_childMain{min-width:0;height:30px;color:inherit;cursor:pointer;text-align:left;font:inherit;background:0 0;border:0;flex:1;align-items:center;gap:8px;padding:0;display:flex}._0brRca_child:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}._0brRca_childMain:disabled{color:var(--dsw-alias-label-tertiary);cursor:not-allowed;opacity:.72}._0brRca_child[aria-current=page]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-selected)}._0brRca_childDot{background:var(--dsw-alias-label-tertiary);border-radius:50%;flex:none;width:6px;height:6px}._0brRca_childDot[data-activity=running]{background:var(--dsw-alias-state-success-primary)}._0brRca_childLabel{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}._0brRca_childTime{color:var(--dsw-alias-label-tertiary);flex:none;margin-left:auto;font-size:12px;line-height:20px}._0brRca_child[data-menu-open=true] ._0brRca_childTime,._0brRca_child:hover ._0brRca_childTime{display:none}._0brRca_childActions{flex:none;align-items:center;display:none;position:absolute;right:10px}._0brRca_child:hover ._0brRca_childActions,._0brRca_child[data-menu-open=true] ._0brRca_childActions{display:inline-flex}._0brRca_childMenuButton{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:4px;justify-content:center;align-items:center;padding:0;display:inline-flex}._0brRca_childMenuButton:hover{color:var(--dsw-alias-label-primary)}._0brRca_deleteAction:not(:disabled){color:var(--dsw-alias-state-error-primary)}._0brRca_deleteStatus{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}._0brRca_deleteError{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}";
|
|
342
374
|
const tagId = "@flowingspring/dsh-voco/VoiceSessionMarkers.module.css";
|
|
343
375
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
344
376
|
const tag = document.createElement("style");
|
|
@@ -348,22 +380,44 @@ window.__ModuleLoader__.load({
|
|
|
348
380
|
document.head.appendChild(tag);
|
|
349
381
|
}
|
|
350
382
|
var VoiceSessionMarkers_module_css_default = {
|
|
383
|
+
"deleteStatus": "_0brRca_deleteStatus",
|
|
384
|
+
"childMain": "_0brRca_childMain",
|
|
385
|
+
"childTime": "_0brRca_childTime",
|
|
386
|
+
"marker": "_0brRca_marker",
|
|
387
|
+
"deleteError": "_0brRca_deleteError",
|
|
351
388
|
"slot": "_0brRca_slot",
|
|
352
|
-
"
|
|
389
|
+
"toggle": "_0brRca_toggle",
|
|
390
|
+
"children": "_0brRca_children",
|
|
391
|
+
"child": "_0brRca_child",
|
|
392
|
+
"childActions": "_0brRca_childActions",
|
|
393
|
+
"childMenuButton": "_0brRca_childMenuButton",
|
|
394
|
+
"childDot": "_0brRca_childDot",
|
|
395
|
+
"childLabel": "_0brRca_childLabel",
|
|
396
|
+
"deleteAction": "_0brRca_deleteAction"
|
|
353
397
|
};
|
|
354
398
|
//#endregion
|
|
355
399
|
//#region ../ui-voice/src/client/VoiceSessionMarkers.tsx
|
|
356
400
|
/**
|
|
357
|
-
* The workspace package currently owns the session-row renderer and
|
|
358
|
-
*
|
|
359
|
-
*
|
|
401
|
+
* The workspace package currently owns the session-row renderer and filters
|
|
402
|
+
* subagent children from its top-level list. This decorator adds the voice
|
|
403
|
+
* marker and a small parent-owned child tree. Child actions are forwarded to
|
|
404
|
+
* the same runtime/registry operations used by the Host row menu.
|
|
360
405
|
*/
|
|
361
|
-
function VoiceSessionMarkers({ useSessions, useVoiceHistory }) {
|
|
362
|
-
const sessions = useSessions((state) => state
|
|
363
|
-
const
|
|
406
|
+
function VoiceSessionMarkers({ useSessions, useWorkspaces, useVoiceHistory, openSession, childActions, refreshSubagents, setSubagentCatalogOpen }) {
|
|
407
|
+
const sessions = useSessions((state) => state);
|
|
408
|
+
const archivedSessionIds = useWorkspaces((state) => state.archivedSessionIds);
|
|
409
|
+
const history = useVoiceHistory((state) => state);
|
|
410
|
+
const expandedParents = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
411
|
+
const requestedParents = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
364
412
|
(0, react.useEffect)(() => {
|
|
365
|
-
const voiceTitles = new Set(history.map((entry) => entry.sessionId));
|
|
366
|
-
const
|
|
413
|
+
const voiceTitles = new Set(history.entries.map((entry) => entry.sessionId));
|
|
414
|
+
for (const entry of history.taskActivity ?? []) if (entry.parentSessionId !== void 0) voiceTitles.add(entry.parentSessionId);
|
|
415
|
+
const taskActivity = new Map((history.taskActivity ?? []).map((entry) => [entry.sessionId, entry.lastActiveAt]));
|
|
416
|
+
const sessionIds = new Set(Object.keys(sessions.byId));
|
|
417
|
+
for (const parentId of voiceTitles) if (!requestedParents.current.has(parentId)) {
|
|
418
|
+
requestedParents.current.add(parentId);
|
|
419
|
+
refreshSubagents(parentId);
|
|
420
|
+
}
|
|
367
421
|
let frame = 0;
|
|
368
422
|
const decorate = () => {
|
|
369
423
|
frame = 0;
|
|
@@ -374,10 +428,34 @@ window.__ModuleLoader__.load({
|
|
|
374
428
|
const sessionId = findSessionKey(row, sessionIds);
|
|
375
429
|
if (!(sessionId !== void 0 && voiceTitles.has(sessionId))) {
|
|
376
430
|
marker?.remove();
|
|
431
|
+
row.querySelector("[data-voice-subagent-toggle]")?.remove();
|
|
432
|
+
removeSubagentList(row, sessionId);
|
|
377
433
|
continue;
|
|
378
434
|
}
|
|
379
|
-
|
|
380
|
-
(
|
|
435
|
+
const target = slot ?? createMarkerSlot(row);
|
|
436
|
+
if (marker === null) target.append(createMarker());
|
|
437
|
+
const parentId = sessionId;
|
|
438
|
+
const catalog = sessions.subagentsByParent[parentId];
|
|
439
|
+
const expanded = expandedParents.current.has(parentId);
|
|
440
|
+
const toggle = row.querySelector("[data-voice-subagent-toggle]") ?? createSubagentToggle(parentId, expanded, () => {
|
|
441
|
+
const next = !expandedParents.current.has(parentId);
|
|
442
|
+
if (next) expandedParents.current.add(parentId);
|
|
443
|
+
else expandedParents.current.delete(parentId);
|
|
444
|
+
setSubagentCatalogOpen(parentId, next);
|
|
445
|
+
if (next) refreshSubagents(parentId);
|
|
446
|
+
decorate();
|
|
447
|
+
});
|
|
448
|
+
updateSubagentToggle(toggle, parentId, expanded, () => {
|
|
449
|
+
const next = !expandedParents.current.has(parentId);
|
|
450
|
+
if (next) expandedParents.current.add(parentId);
|
|
451
|
+
else expandedParents.current.delete(parentId);
|
|
452
|
+
setSubagentCatalogOpen(parentId, next);
|
|
453
|
+
if (next) refreshSubagents(parentId);
|
|
454
|
+
decorate();
|
|
455
|
+
});
|
|
456
|
+
const title = row.querySelector("[class*=\"title\"]");
|
|
457
|
+
if (toggle.parentElement !== row) row.insertBefore(toggle, title ?? null);
|
|
458
|
+
renderSubagentChildren(row, parentId, catalog?.entries ?? [], expanded, sessions.current, sessions.byId, new Set(archivedSessionIds), taskActivity, openSession, childActions);
|
|
381
459
|
}
|
|
382
460
|
};
|
|
383
461
|
const schedule = () => {
|
|
@@ -385,7 +463,20 @@ window.__ModuleLoader__.load({
|
|
|
385
463
|
frame = requestAnimationFrame(decorate);
|
|
386
464
|
};
|
|
387
465
|
decorate();
|
|
466
|
+
const clock = window.setInterval(schedule, 6e4);
|
|
388
467
|
const observer = new MutationObserver(schedule);
|
|
468
|
+
const suppressChildHover = (event) => {
|
|
469
|
+
const target = event.target;
|
|
470
|
+
if (!(target instanceof Element)) return;
|
|
471
|
+
const child = target.closest("[data-voice-subagent-child]");
|
|
472
|
+
if (child === null) return;
|
|
473
|
+
event.stopPropagation();
|
|
474
|
+
suppressHostHover(child);
|
|
475
|
+
};
|
|
476
|
+
document.addEventListener("pointerover", suppressChildHover, true);
|
|
477
|
+
document.addEventListener("mouseover", suppressChildHover, true);
|
|
478
|
+
document.addEventListener("pointerenter", suppressChildHover, true);
|
|
479
|
+
document.addEventListener("mouseenter", suppressChildHover, true);
|
|
389
480
|
observer.observe(document.body, {
|
|
390
481
|
childList: true,
|
|
391
482
|
subtree: true,
|
|
@@ -393,6 +484,11 @@ window.__ModuleLoader__.load({
|
|
|
393
484
|
});
|
|
394
485
|
return () => {
|
|
395
486
|
observer.disconnect();
|
|
487
|
+
document.removeEventListener("pointerover", suppressChildHover, true);
|
|
488
|
+
document.removeEventListener("mouseover", suppressChildHover, true);
|
|
489
|
+
document.removeEventListener("pointerenter", suppressChildHover, true);
|
|
490
|
+
document.removeEventListener("mouseenter", suppressChildHover, true);
|
|
491
|
+
window.clearInterval(clock);
|
|
396
492
|
if (frame !== 0) cancelAnimationFrame(frame);
|
|
397
493
|
document.querySelectorAll("[data-voice-session-marker]").forEach((node) => {
|
|
398
494
|
node.remove();
|
|
@@ -400,10 +496,340 @@ window.__ModuleLoader__.load({
|
|
|
400
496
|
document.querySelectorAll("[data-voice-session-marker-slot]").forEach((node) => {
|
|
401
497
|
node.remove();
|
|
402
498
|
});
|
|
499
|
+
document.querySelectorAll("[data-voice-subagent-list]").forEach((node) => {
|
|
500
|
+
unmountChildMenus(node);
|
|
501
|
+
node.remove();
|
|
502
|
+
});
|
|
503
|
+
document.querySelectorAll("[data-voice-subagent-toggle]").forEach((node) => {
|
|
504
|
+
node.remove();
|
|
505
|
+
});
|
|
403
506
|
};
|
|
404
|
-
}, [
|
|
507
|
+
}, [
|
|
508
|
+
history,
|
|
509
|
+
sessions,
|
|
510
|
+
archivedSessionIds,
|
|
511
|
+
openSession,
|
|
512
|
+
childActions,
|
|
513
|
+
refreshSubagents,
|
|
514
|
+
setSubagentCatalogOpen
|
|
515
|
+
]);
|
|
405
516
|
return null;
|
|
406
517
|
}
|
|
518
|
+
function createSubagentToggle(parentId, expanded, onClick) {
|
|
519
|
+
const button = document.createElement("button");
|
|
520
|
+
button.type = "button";
|
|
521
|
+
button.dataset.voiceSubagentToggle = "";
|
|
522
|
+
button.className = VoiceSessionMarkers_module_css_default.toggle ?? "voice-subagent-toggle";
|
|
523
|
+
updateSubagentToggle(button, parentId, expanded, onClick);
|
|
524
|
+
button.onclick = (event) => {
|
|
525
|
+
event.stopPropagation();
|
|
526
|
+
onClick();
|
|
527
|
+
};
|
|
528
|
+
button.dataset.parentSessionId = parentId;
|
|
529
|
+
return button;
|
|
530
|
+
}
|
|
531
|
+
function updateSubagentToggle(button, parentId, expanded, onClick) {
|
|
532
|
+
button.dataset.parentSessionId = parentId;
|
|
533
|
+
const label = expanded ? "收起语音子会话" : "展开语音子会话";
|
|
534
|
+
if (button.getAttribute("aria-label") !== label) button.setAttribute("aria-label", label);
|
|
535
|
+
const expandedValue = String(expanded);
|
|
536
|
+
if (button.getAttribute("aria-expanded") !== expandedValue) button.setAttribute("aria-expanded", expandedValue);
|
|
537
|
+
if (button.title !== label) button.title = label;
|
|
538
|
+
const icon = expanded ? "⌄" : "›";
|
|
539
|
+
if (button.textContent !== icon) button.innerHTML = `<span aria-hidden="true">${icon}</span>`;
|
|
540
|
+
button.onclick = (event) => {
|
|
541
|
+
event.stopPropagation();
|
|
542
|
+
onClick();
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
function renderSubagentChildren(row, parentId, entries, expanded, current, summaries, archivedSessionIds, taskActivity, openSession, childActions) {
|
|
546
|
+
let list = findSubagentList(row, parentId);
|
|
547
|
+
if (!expanded) {
|
|
548
|
+
if (list !== void 0 && list !== null) unmountChildMenus(list);
|
|
549
|
+
list?.remove();
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
if (list === null || list === void 0) {
|
|
553
|
+
list = document.createElement("div");
|
|
554
|
+
list.dataset.voiceSubagentList = String(parentId);
|
|
555
|
+
list.className = VoiceSessionMarkers_module_css_default.children ?? "voice-subagent-children";
|
|
556
|
+
row.parentElement?.insertBefore(list, row.nextSibling);
|
|
557
|
+
}
|
|
558
|
+
const children = entries.filter((entry) => entry.kind !== "parent" && !isSessionEffectivelyArchived(entry.id, archivedSessionIds, summaries, (id) => id === entry.id ? parentId : void 0));
|
|
559
|
+
const signature = children.map((entry) => `${entry.id}:${entry.label ?? ""}:${entry.activity ?? ""}:${childUpdatedAt(entry.id, entry, summaries, taskActivity) ?? ""}`).join("|");
|
|
560
|
+
if (list.dataset.signature === signature && list.dataset.current === String(current ?? "")) return;
|
|
561
|
+
list.dataset.signature = signature;
|
|
562
|
+
list.dataset.current = String(current ?? "");
|
|
563
|
+
unmountChildMenus(list);
|
|
564
|
+
list.replaceChildren(...children.map((entry) => {
|
|
565
|
+
const child = document.createElement("div");
|
|
566
|
+
child.className = VoiceSessionMarkers_module_css_default.child ?? "voice-subagent-child";
|
|
567
|
+
child.dataset.voiceSubagentChild = "";
|
|
568
|
+
child.dataset.sessionId = String(entry.id);
|
|
569
|
+
child.disabled = entry.kind !== "child";
|
|
570
|
+
if (entry.kind !== "child") child.dataset.state = "unavailable";
|
|
571
|
+
child.setAttribute("role", "treeitem");
|
|
572
|
+
child.tabIndex = entry.kind === "child" ? 0 : -1;
|
|
573
|
+
child.setAttribute("aria-current", entry.id === current ? "page" : "false");
|
|
574
|
+
const unavailableLabel = entry.kind === "diagnostic" ? "会话记录损坏" : "子会话暂时不可用";
|
|
575
|
+
const displayLabel = entry.label ?? (entry.kind === "child" ? String(entry.id) : unavailableLabel);
|
|
576
|
+
child.addEventListener("mouseenter", (event) => suppressHostHover(child, event), true);
|
|
577
|
+
child.addEventListener("mouseover", (event) => suppressHostHover(child, event), true);
|
|
578
|
+
child.addEventListener("pointerenter", (event) => suppressHostHover(child, event), true);
|
|
579
|
+
child.addEventListener("click", (event) => {
|
|
580
|
+
if (event.target !== child || entry.kind !== "child") return;
|
|
581
|
+
openSession(entry.id);
|
|
582
|
+
});
|
|
583
|
+
const dot = document.createElement("span");
|
|
584
|
+
dot.className = VoiceSessionMarkers_module_css_default.childDot ?? "voice-subagent-child-dot";
|
|
585
|
+
dot.dataset.activity = entry.activity ?? "inactive";
|
|
586
|
+
const label = document.createElement("span");
|
|
587
|
+
label.className = VoiceSessionMarkers_module_css_default.childLabel ?? "voice-subagent-child-label";
|
|
588
|
+
label.textContent = displayLabel;
|
|
589
|
+
const open = document.createElement("button");
|
|
590
|
+
open.type = "button";
|
|
591
|
+
open.className = VoiceSessionMarkers_module_css_default.childMain ?? "voice-subagent-child-main";
|
|
592
|
+
open.disabled = entry.kind !== "child";
|
|
593
|
+
open.append(dot, label);
|
|
594
|
+
child.append(open);
|
|
595
|
+
const updatedAt = childUpdatedAt(entry.id, entry, summaries, taskActivity);
|
|
596
|
+
if (updatedAt !== void 0) {
|
|
597
|
+
const time = document.createElement("span");
|
|
598
|
+
time.className = VoiceSessionMarkers_module_css_default.childTime ?? "voice-subagent-child-time";
|
|
599
|
+
time.dataset.voiceSubagentChildTime = "";
|
|
600
|
+
time.dataset.updatedAt = String(updatedAt);
|
|
601
|
+
time.textContent = relativeTimeLabel(updatedAt, Date.now());
|
|
602
|
+
open.append(time);
|
|
603
|
+
}
|
|
604
|
+
open.addEventListener("click", (event) => {
|
|
605
|
+
event.stopPropagation();
|
|
606
|
+
if (entry.kind !== "child") return;
|
|
607
|
+
openSession(entry.id);
|
|
608
|
+
});
|
|
609
|
+
if (entry.kind === "child") mountChildMenu(child, entry.id, displayLabel, childActions);
|
|
610
|
+
return child;
|
|
611
|
+
}));
|
|
612
|
+
}
|
|
613
|
+
function suppressHostHover(child, event) {
|
|
614
|
+
event?.stopPropagation();
|
|
615
|
+
child.dataset.voiceChildHover = "true";
|
|
616
|
+
const parentRow = child.closest("[data-voice-subagent-list]")?.previousElementSibling;
|
|
617
|
+
if (!(parentRow instanceof HTMLElement)) return;
|
|
618
|
+
const outside = document.body;
|
|
619
|
+
if (typeof PointerEvent === "function") {
|
|
620
|
+
parentRow.dispatchEvent(new PointerEvent("pointerleave", {
|
|
621
|
+
bubbles: false,
|
|
622
|
+
relatedTarget: outside
|
|
623
|
+
}));
|
|
624
|
+
parentRow.dispatchEvent(new PointerEvent("pointerout", {
|
|
625
|
+
bubbles: true,
|
|
626
|
+
relatedTarget: outside
|
|
627
|
+
}));
|
|
628
|
+
}
|
|
629
|
+
parentRow.dispatchEvent(new MouseEvent("mouseleave", {
|
|
630
|
+
bubbles: false,
|
|
631
|
+
relatedTarget: outside
|
|
632
|
+
}));
|
|
633
|
+
parentRow.dispatchEvent(new MouseEvent("mouseout", {
|
|
634
|
+
bubbles: true,
|
|
635
|
+
relatedTarget: outside
|
|
636
|
+
}));
|
|
637
|
+
}
|
|
638
|
+
const childMenuRoots = /* @__PURE__ */ new WeakMap();
|
|
639
|
+
function mountChildMenu(child, id, title, actions) {
|
|
640
|
+
const wrapper = document.createElement("span");
|
|
641
|
+
wrapper.className = VoiceSessionMarkers_module_css_default.childActions ?? "voice-subagent-child-actions";
|
|
642
|
+
wrapper.dataset.voiceChildMenuRoot = "";
|
|
643
|
+
child.append(wrapper);
|
|
644
|
+
const root = (0, react_dom_client.createRoot)(wrapper);
|
|
645
|
+
childMenuRoots.set(wrapper, root);
|
|
646
|
+
root.render(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(OfficialChildMenu, {
|
|
647
|
+
id,
|
|
648
|
+
title,
|
|
649
|
+
actions,
|
|
650
|
+
onOpenChange: (open) => {
|
|
651
|
+
child.dataset.menuOpen = String(open);
|
|
652
|
+
}
|
|
653
|
+
}));
|
|
654
|
+
}
|
|
655
|
+
function unmountChildMenus(list) {
|
|
656
|
+
list.querySelectorAll("[data-voice-child-menu-root]").forEach((root) => {
|
|
657
|
+
childMenuRoots.get(root)?.unmount();
|
|
658
|
+
childMenuRoots.delete(root);
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
function OfficialChildMenu({ id, title, actions, onOpenChange }) {
|
|
662
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
663
|
+
const [deleteOpen, setDeleteOpen] = (0, react.useState)(false);
|
|
664
|
+
const [deleting, setDeleting] = (0, react.useState)(false);
|
|
665
|
+
const [deleteError, setDeleteError] = (0, react.useState)(null);
|
|
666
|
+
const [actionError, setActionError] = (0, react.useState)(null);
|
|
667
|
+
const items = [
|
|
668
|
+
{
|
|
669
|
+
id: "rename",
|
|
670
|
+
label: "重命名",
|
|
671
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, {})
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
id: "fork",
|
|
675
|
+
label: "分叉会话",
|
|
676
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutline16, {})
|
|
677
|
+
},
|
|
678
|
+
...actions?.delete === void 0 ? [] : [{
|
|
679
|
+
id: "delete",
|
|
680
|
+
label: "删除会话",
|
|
681
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, {}),
|
|
682
|
+
danger: true
|
|
683
|
+
}]
|
|
684
|
+
];
|
|
685
|
+
const setMenuOpen = (next) => {
|
|
686
|
+
setOpen(next);
|
|
687
|
+
onOpenChange(next);
|
|
688
|
+
};
|
|
689
|
+
const closeDelete = () => {
|
|
690
|
+
if (deleting) return;
|
|
691
|
+
setDeleteOpen(false);
|
|
692
|
+
setDeleteError(null);
|
|
693
|
+
};
|
|
694
|
+
const confirmDelete = () => {
|
|
695
|
+
if (deleting || actions?.delete === void 0) return;
|
|
696
|
+
setDeleting(true);
|
|
697
|
+
setDeleteError(null);
|
|
698
|
+
actions.delete(id, title).then(() => {
|
|
699
|
+
setDeleting(false);
|
|
700
|
+
setDeleteOpen(false);
|
|
701
|
+
}, (cause) => {
|
|
702
|
+
setDeleting(false);
|
|
703
|
+
setDeleteError(cause instanceof Error ? cause.message : String(cause));
|
|
704
|
+
});
|
|
705
|
+
};
|
|
706
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
707
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
708
|
+
open,
|
|
709
|
+
onClose: () => {
|
|
710
|
+
setMenuOpen(false);
|
|
711
|
+
},
|
|
712
|
+
items,
|
|
713
|
+
onSelect: (selected) => {
|
|
714
|
+
setMenuOpen(false);
|
|
715
|
+
setActionError(null);
|
|
716
|
+
if (selected === "delete") {
|
|
717
|
+
setDeleteError(null);
|
|
718
|
+
setDeleteOpen(true);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
(selected === "rename" ? (() => {
|
|
722
|
+
if (actions?.rename === void 0) return void 0;
|
|
723
|
+
const next = window.prompt("重命名会话", title);
|
|
724
|
+
return next === null ? void 0 : actions.rename(id, next);
|
|
725
|
+
})() : actions?.fork?.(id))?.catch((error) => {
|
|
726
|
+
console.warn("voice child action failed:", error);
|
|
727
|
+
setActionError(error instanceof Error ? error.message : String(error));
|
|
728
|
+
});
|
|
729
|
+
},
|
|
730
|
+
portal: true,
|
|
731
|
+
closeOnPointerLeave: true,
|
|
732
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
733
|
+
type: "button",
|
|
734
|
+
className: VoiceSessionMarkers_module_css_default.childMenuButton ?? "voice-subagent-child-menu-button",
|
|
735
|
+
"aria-label": `会话“${title}”的操作`,
|
|
736
|
+
onClick: (event) => {
|
|
737
|
+
event.stopPropagation();
|
|
738
|
+
setMenuOpen(!open);
|
|
739
|
+
},
|
|
740
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, {})
|
|
741
|
+
})
|
|
742
|
+
}),
|
|
743
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
744
|
+
open: deleteOpen,
|
|
745
|
+
onClose: closeDelete,
|
|
746
|
+
closeLabel: "关闭",
|
|
747
|
+
title: "删除会话",
|
|
748
|
+
description: `将永久删除会话“${title}”及其子代理(含正在运行的)和全部记录(对话内容、统计、缓存),此操作不可恢复。`,
|
|
749
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
750
|
+
variant: "outline",
|
|
751
|
+
disabled: deleting,
|
|
752
|
+
onClick: closeDelete,
|
|
753
|
+
children: "取消"
|
|
754
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
755
|
+
variant: "outline",
|
|
756
|
+
className: VoiceSessionMarkers_module_css_default.deleteAction ?? "voice-subagent-delete-action",
|
|
757
|
+
disabled: deleting,
|
|
758
|
+
onClick: confirmDelete,
|
|
759
|
+
children: "删除会话"
|
|
760
|
+
})] }),
|
|
761
|
+
children: [deleting && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
762
|
+
className: VoiceSessionMarkers_module_css_default.deleteStatus,
|
|
763
|
+
role: "status",
|
|
764
|
+
children: "正在删除会话…"
|
|
765
|
+
}), deleteError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
766
|
+
className: VoiceSessionMarkers_module_css_default.deleteError,
|
|
767
|
+
role: "alert",
|
|
768
|
+
children: ["删除会话失败:", deleteError]
|
|
769
|
+
})]
|
|
770
|
+
}),
|
|
771
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
772
|
+
open: actionError !== null,
|
|
773
|
+
onClose: () => {
|
|
774
|
+
setActionError(null);
|
|
775
|
+
},
|
|
776
|
+
closeLabel: "关闭",
|
|
777
|
+
title: "会话操作失败",
|
|
778
|
+
description: actionError ?? "",
|
|
779
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
780
|
+
variant: "outline",
|
|
781
|
+
onClick: () => {
|
|
782
|
+
setActionError(null);
|
|
783
|
+
},
|
|
784
|
+
children: "关闭"
|
|
785
|
+
})
|
|
786
|
+
})
|
|
787
|
+
] });
|
|
788
|
+
}
|
|
789
|
+
function childUpdatedAt(id, entry, summaries, taskActivity) {
|
|
790
|
+
if (typeof entry.updatedAt === "number" && entry.updatedAt > 0) return entry.updatedAt;
|
|
791
|
+
const summaryTime = summaries[id]?.updatedAt;
|
|
792
|
+
if (typeof summaryTime === "number" && summaryTime > 0) return summaryTime;
|
|
793
|
+
const taskTime = taskActivity.get(id);
|
|
794
|
+
return typeof taskTime === "number" && taskTime > 0 ? taskTime : void 0;
|
|
795
|
+
}
|
|
796
|
+
/** Match the Host workspace's compact relative-time buckets for child rows. */
|
|
797
|
+
function relativeTimeLabel(updatedAt, now, language = typeof navigator !== "undefined" ? navigator.language : "en-US") {
|
|
798
|
+
const diff = Math.max(0, now - updatedAt);
|
|
799
|
+
const minute = 6e4;
|
|
800
|
+
const hour = 36e5;
|
|
801
|
+
const day = 864e5;
|
|
802
|
+
const chinese = language.toLowerCase().startsWith("zh");
|
|
803
|
+
if (diff < minute) return chinese ? "刚刚" : "now";
|
|
804
|
+
if (diff < hour) {
|
|
805
|
+
const n = Math.floor(diff / minute);
|
|
806
|
+
return chinese ? `${n}分钟` : `${n}min`;
|
|
807
|
+
}
|
|
808
|
+
if (diff < day) {
|
|
809
|
+
const n = Math.floor(diff / hour);
|
|
810
|
+
return chinese ? `${n}小时` : `${n}h`;
|
|
811
|
+
}
|
|
812
|
+
if (diff < 30 * day) {
|
|
813
|
+
const n = Math.floor(diff / day);
|
|
814
|
+
return chinese ? `${n}天` : `${n}d`;
|
|
815
|
+
}
|
|
816
|
+
if (diff < 365 * day) {
|
|
817
|
+
const n = Math.floor(diff / (30 * day));
|
|
818
|
+
return chinese ? `${n}个月` : `${n}mo`;
|
|
819
|
+
}
|
|
820
|
+
const n = Math.floor(diff / (365 * day));
|
|
821
|
+
return chinese ? `${n}年` : `${n}y`;
|
|
822
|
+
}
|
|
823
|
+
function findSubagentList(row, parentId) {
|
|
824
|
+
return [...row.parentElement?.querySelectorAll("[data-voice-subagent-list]") ?? []].find((candidate) => candidate.dataset.voiceSubagentList === String(parentId));
|
|
825
|
+
}
|
|
826
|
+
function removeSubagentList(row, parentId) {
|
|
827
|
+
if (parentId === void 0) return;
|
|
828
|
+
const list = findSubagentList(row, parentId);
|
|
829
|
+
if (list === void 0) return;
|
|
830
|
+
unmountChildMenus(list);
|
|
831
|
+
list.remove();
|
|
832
|
+
}
|
|
407
833
|
/** React keeps the keyed SessionNodeItem identity on the row's owner fiber. */
|
|
408
834
|
function findSessionKey(row, sessionIds) {
|
|
409
835
|
const fiberKey = Object.keys(row).find((key) => key.startsWith("__reactFiber$"));
|
|
@@ -1141,14 +1567,26 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
|
|
|
1141
1567
|
};
|
|
1142
1568
|
}
|
|
1143
1569
|
function readHistory(storage) {
|
|
1144
|
-
if (storage === void 0) return {
|
|
1570
|
+
if (storage === void 0) return {
|
|
1571
|
+
entries: [],
|
|
1572
|
+
taskActivity: []
|
|
1573
|
+
};
|
|
1145
1574
|
try {
|
|
1146
1575
|
const raw = storage.getItem(STORAGE_KEY);
|
|
1147
|
-
if (raw === null) return {
|
|
1576
|
+
if (raw === null) return {
|
|
1577
|
+
entries: [],
|
|
1578
|
+
taskActivity: []
|
|
1579
|
+
};
|
|
1148
1580
|
const parsed = JSON.parse(raw);
|
|
1149
|
-
if (typeof parsed !== "object" || parsed === null) return {
|
|
1581
|
+
if (typeof parsed !== "object" || parsed === null) return {
|
|
1582
|
+
entries: [],
|
|
1583
|
+
taskActivity: []
|
|
1584
|
+
};
|
|
1150
1585
|
const value = parsed;
|
|
1151
|
-
if (value.version !== 1 || !Array.isArray(value.entries)) return {
|
|
1586
|
+
if (value.version !== 1 || !Array.isArray(value.entries)) return {
|
|
1587
|
+
entries: [],
|
|
1588
|
+
taskActivity: []
|
|
1589
|
+
};
|
|
1152
1590
|
const entries = [];
|
|
1153
1591
|
const seen = /* @__PURE__ */ new Set();
|
|
1154
1592
|
for (const candidate of value.entries) {
|
|
@@ -1158,10 +1596,35 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
|
|
|
1158
1596
|
entries.push(entry);
|
|
1159
1597
|
}
|
|
1160
1598
|
entries.sort((left, right) => right.lastActiveAt - left.lastActiveAt);
|
|
1161
|
-
|
|
1599
|
+
const taskActivity = [];
|
|
1600
|
+
const taskSeen = /* @__PURE__ */ new Set();
|
|
1601
|
+
if (Array.isArray(value.taskActivity)) {
|
|
1602
|
+
for (const candidate of value.taskActivity) {
|
|
1603
|
+
if (typeof candidate !== "object" || candidate === null) continue;
|
|
1604
|
+
const item = candidate;
|
|
1605
|
+
if (typeof item.sessionId !== "string" || item.sessionId === "" || typeof item.lastActiveAt !== "number" || !Number.isFinite(item.lastActiveAt) || item.lastActiveAt < 0) continue;
|
|
1606
|
+
const sessionId = item.sessionId;
|
|
1607
|
+
if (taskSeen.has(sessionId)) continue;
|
|
1608
|
+
taskSeen.add(sessionId);
|
|
1609
|
+
const parentSessionId = typeof item.parentSessionId === "string" && item.parentSessionId !== "" ? item.parentSessionId : void 0;
|
|
1610
|
+
taskActivity.push({
|
|
1611
|
+
sessionId,
|
|
1612
|
+
lastActiveAt: item.lastActiveAt,
|
|
1613
|
+
...parentSessionId === void 0 ? {} : { parentSessionId }
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
taskActivity.sort((left, right) => right.lastActiveAt - left.lastActiveAt);
|
|
1617
|
+
}
|
|
1618
|
+
return {
|
|
1619
|
+
entries,
|
|
1620
|
+
taskActivity
|
|
1621
|
+
};
|
|
1162
1622
|
} catch (error) {
|
|
1163
1623
|
console.error("voice history rehydration failed:", error);
|
|
1164
|
-
return {
|
|
1624
|
+
return {
|
|
1625
|
+
entries: [],
|
|
1626
|
+
taskActivity: []
|
|
1627
|
+
};
|
|
1165
1628
|
}
|
|
1166
1629
|
}
|
|
1167
1630
|
/** Own the local history index without adding Voice metadata to Host session projections. */
|
|
@@ -1183,15 +1646,36 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
|
|
|
1183
1646
|
lastActiveAt
|
|
1184
1647
|
}, ...this.snapshot.getSnapshot().entries.filter((entry) => entry.sessionId !== sessionId)] });
|
|
1185
1648
|
}
|
|
1649
|
+
/** Retain the latest timestamp seen in a delegated task's durable events. */
|
|
1650
|
+
recordTaskActivity(sessionId, lastActiveAt, parentSessionId) {
|
|
1651
|
+
if (!Number.isFinite(lastActiveAt) || lastActiveAt < 0) return;
|
|
1652
|
+
const entries = this.snapshot.getSnapshot().taskActivity ?? [];
|
|
1653
|
+
const previousEntry = entries.find((entry) => entry.sessionId === sessionId);
|
|
1654
|
+
const previous = previousEntry?.lastActiveAt ?? 0;
|
|
1655
|
+
const nextTime = Math.max(previous, lastActiveAt);
|
|
1656
|
+
const owner = parentSessionId ?? previousEntry?.parentSessionId;
|
|
1657
|
+
if (nextTime === previous && owner === previousEntry?.parentSessionId) return;
|
|
1658
|
+
const next = [{
|
|
1659
|
+
sessionId,
|
|
1660
|
+
lastActiveAt: nextTime,
|
|
1661
|
+
...owner === void 0 ? {} : { parentSessionId: owner }
|
|
1662
|
+
}, ...entries.filter((entry) => entry.sessionId !== sessionId)];
|
|
1663
|
+
this.snapshot.set({
|
|
1664
|
+
...this.snapshot.getSnapshot(),
|
|
1665
|
+
taskActivity: next
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1186
1668
|
/** Stop persistence notifications. */
|
|
1187
1669
|
dispose() {
|
|
1188
1670
|
this.unsubscribe();
|
|
1189
1671
|
}
|
|
1190
1672
|
persist() {
|
|
1191
1673
|
if (this.storage === void 0) return;
|
|
1674
|
+
const snapshot = this.snapshot.getSnapshot();
|
|
1192
1675
|
const value = {
|
|
1193
1676
|
version: 1,
|
|
1194
|
-
entries:
|
|
1677
|
+
entries: snapshot.entries,
|
|
1678
|
+
...snapshot.taskActivity === void 0 ? {} : { taskActivity: snapshot.taskActivity }
|
|
1195
1679
|
};
|
|
1196
1680
|
try {
|
|
1197
1681
|
this.storage.setItem(STORAGE_KEY, JSON.stringify(value));
|
|
@@ -1315,7 +1799,8 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
|
|
|
1315
1799
|
taskId: match.event.data.taskId,
|
|
1316
1800
|
taskSessionId: match.event.data.taskSessionId,
|
|
1317
1801
|
input: match.event.data.input,
|
|
1318
|
-
status: "accepted"
|
|
1802
|
+
status: "accepted",
|
|
1803
|
+
lastActivityAt: match.event.time
|
|
1319
1804
|
};
|
|
1320
1805
|
},
|
|
1321
1806
|
update: (context, match) => {
|
|
@@ -1325,6 +1810,7 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
|
|
|
1325
1810
|
return {
|
|
1326
1811
|
...context.state,
|
|
1327
1812
|
status: observation.status,
|
|
1813
|
+
lastActivityAt: Math.max(context.state.lastActivityAt ?? 0, match.event.time),
|
|
1328
1814
|
...update === void 0 ? {} : { update }
|
|
1329
1815
|
};
|
|
1330
1816
|
},
|
|
@@ -1416,8 +1902,43 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
|
|
|
1416
1902
|
"conversationEvents",
|
|
1417
1903
|
"slots",
|
|
1418
1904
|
"sessions",
|
|
1419
|
-
"
|
|
1905
|
+
"workspaces",
|
|
1906
|
+
"locale",
|
|
1907
|
+
"remote"
|
|
1420
1908
|
];
|
|
1909
|
+
/**
|
|
1910
|
+
* The Harness model-selection UI intentionally hides addressed children. Voice
|
|
1911
|
+
* tasks are ordinary durable Agent Sessions, so let that official UI use the
|
|
1912
|
+
* same model directory as the parent while retaining navigationAddress for
|
|
1913
|
+
* child routing and breadcrumbs.
|
|
1914
|
+
*/
|
|
1915
|
+
function enableChildModelSelection(ctx) {
|
|
1916
|
+
const sessions = ctx.sessions;
|
|
1917
|
+
const original = sessions.subagentAddress;
|
|
1918
|
+
if (typeof original !== "function") return;
|
|
1919
|
+
ctx.effect(() => {
|
|
1920
|
+
sessions.subagentAddress = () => void 0;
|
|
1921
|
+
return () => {
|
|
1922
|
+
sessions.subagentAddress = original;
|
|
1923
|
+
};
|
|
1924
|
+
}, "ui-voice: child model selection compatibility");
|
|
1925
|
+
}
|
|
1926
|
+
function archiveManagerRegistry(ctx) {
|
|
1927
|
+
try {
|
|
1928
|
+
const registry = ctx.get("remote.workspaceRegistry");
|
|
1929
|
+
if (registry === null || typeof registry !== "object") return void 0;
|
|
1930
|
+
return registry;
|
|
1931
|
+
} catch {
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
/** Call archive-manager's complete deletion path when its remote is installed. */
|
|
1936
|
+
async function deleteWithArchiveManager(ctx, sessionId) {
|
|
1937
|
+
const deleteSession = archiveManagerRegistry(ctx)?.deleteSession;
|
|
1938
|
+
if (typeof deleteSession !== "function") throw new Error("archive-manager 删除服务不可用");
|
|
1939
|
+
const result = await deleteSession(sessionId);
|
|
1940
|
+
if (!result.ok) throw new Error(result.error?.message ?? "archive-manager 删除会话失败");
|
|
1941
|
+
}
|
|
1421
1942
|
/** Refresh the Host-backed Session list when the runtime exposes its baseline pull. */
|
|
1422
1943
|
function refreshSessions(ctx) {
|
|
1423
1944
|
const service = ctx.sessions;
|
|
@@ -1463,11 +1984,65 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
|
|
|
1463
1984
|
hooks: { voice: controller },
|
|
1464
1985
|
stopVoice
|
|
1465
1986
|
});
|
|
1466
|
-
const voiceSessionMarkersInjected = () => ({
|
|
1987
|
+
const voiceSessionMarkersInjected = () => ({
|
|
1988
|
+
hooks: { voiceHistory: history.snapshot },
|
|
1989
|
+
childActions: {
|
|
1990
|
+
rename: async (id, title) => {
|
|
1991
|
+
const session = ensureChildSession(ctx, id);
|
|
1992
|
+
if (session === void 0) throw new Error(`unknown session "${id}"`);
|
|
1993
|
+
const result = await session.rename(title);
|
|
1994
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
1995
|
+
},
|
|
1996
|
+
fork: async (id) => {
|
|
1997
|
+
ensureChildSession(ctx, id);
|
|
1998
|
+
const childId = await ctx.sessions.fork({
|
|
1999
|
+
sessionId: id,
|
|
2000
|
+
increaseTitle: true
|
|
2001
|
+
});
|
|
2002
|
+
ctx.sessions.open(childId);
|
|
2003
|
+
},
|
|
2004
|
+
...archiveManagerRegistry(ctx)?.deleteSession === void 0 ? {} : { delete: async (id) => {
|
|
2005
|
+
const parentId = ctx.sessions.navigationAddress?.(id)?.parentSessionId;
|
|
2006
|
+
await deleteWithArchiveManager(ctx, id);
|
|
2007
|
+
refreshSessions(ctx);
|
|
2008
|
+
if (parentId !== void 0) await ctx.sessions.refreshSubagents?.call(ctx.sessions, parentId);
|
|
2009
|
+
} }
|
|
2010
|
+
},
|
|
2011
|
+
openSession: (id) => {
|
|
2012
|
+
if (isEffectivelyArchivedInContext(ctx, id)) return;
|
|
2013
|
+
const sessions = ctx.sessions;
|
|
2014
|
+
const address = sessions.navigationAddress?.(id) ?? sessions.subagentAddress?.(id);
|
|
2015
|
+
if (address !== void 0 && sessions.openSubagent !== void 0) {
|
|
2016
|
+
sessions.openSubagent(address);
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
ctx.sessions.open(id);
|
|
2020
|
+
},
|
|
2021
|
+
refreshSubagents: (parentSessionId) => {
|
|
2022
|
+
const refresh = ctx.sessions.refreshSubagents;
|
|
2023
|
+
if (refresh === void 0) return;
|
|
2024
|
+
refresh.call(ctx.sessions, parentSessionId).catch((error) => {
|
|
2025
|
+
console.warn("voice subagent catalog refresh failed:", error);
|
|
2026
|
+
});
|
|
2027
|
+
},
|
|
2028
|
+
setSubagentCatalogOpen: (parentSessionId, open) => {
|
|
2029
|
+
ctx.sessions.setSubagentCatalogOpen?.call(ctx.sessions, parentSessionId, open);
|
|
2030
|
+
}
|
|
2031
|
+
});
|
|
1467
2032
|
const utteranceInjected = () => ({ hooks: { voice: controller } });
|
|
1468
2033
|
const delegationInjected = () => ({
|
|
1469
2034
|
hooks: { voice: controller },
|
|
2035
|
+
recordTaskActivity: (id, at, parentSessionId) => {
|
|
2036
|
+
history.recordTaskActivity(id, at, parentSessionId);
|
|
2037
|
+
},
|
|
1470
2038
|
openSession: (id) => {
|
|
2039
|
+
if (isEffectivelyArchivedInContext(ctx, id)) return;
|
|
2040
|
+
const sessions = ctx.sessions;
|
|
2041
|
+
const address = sessions.navigationAddress?.(id) ?? sessions.subagentAddress?.(id);
|
|
2042
|
+
if (address !== void 0 && sessions.openSubagent !== void 0) {
|
|
2043
|
+
sessions.openSubagent(address);
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
1471
2046
|
ctx.sessions.open(id);
|
|
1472
2047
|
},
|
|
1473
2048
|
cancelTask: (id) => {
|
|
@@ -1487,6 +2062,7 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
|
|
|
1487
2062
|
ctx.effect(() => () => {
|
|
1488
2063
|
history.dispose();
|
|
1489
2064
|
}, "ui-voice: history persistence teardown");
|
|
2065
|
+
enableChildModelSelection(ctx);
|
|
1490
2066
|
ctx.conversationEvents.register(voiceUtteranceDefinition);
|
|
1491
2067
|
ctx.conversationEvents.register(voiceDelegationDefinition);
|
|
1492
2068
|
ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
|
|
@@ -1523,6 +2099,21 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
|
|
|
1523
2099
|
inject: delegationInjected
|
|
1524
2100
|
}, VoiceDelegationView));
|
|
1525
2101
|
}
|
|
2102
|
+
/** Archive-manager archives one requested ID; Voice treats that state as
|
|
2103
|
+
* inherited by every descendant without mutating the third-party archive set. */
|
|
2104
|
+
function isEffectivelyArchivedInContext(ctx, sessionId) {
|
|
2105
|
+
const workspaces = ctx.workspaces;
|
|
2106
|
+
if (workspaces === void 0) return false;
|
|
2107
|
+
const snapshot = ctx.sessions.list.getSnapshot();
|
|
2108
|
+
const sessions = ctx.sessions;
|
|
2109
|
+
return isSessionEffectivelyArchived(sessionId, workspaces.list.getSnapshot().archivedSessionIds, snapshot.byId, (id) => sessions.navigationAddress?.(id)?.parentSessionId);
|
|
2110
|
+
}
|
|
2111
|
+
function ensureChildSession(ctx, id) {
|
|
2112
|
+
const sessions = ctx.sessions;
|
|
2113
|
+
const address = sessions.navigationAddress?.(id);
|
|
2114
|
+
if (address !== void 0) sessions.openSubagent?.(address);
|
|
2115
|
+
return ctx.sessions.binding(id)?.session;
|
|
2116
|
+
}
|
|
1526
2117
|
//#endregion
|
|
1527
2118
|
exports.apply = apply;
|
|
1528
2119
|
exports.inject = inject;
|