@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/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
- "taskDot": "Kt2smW_taskDot",
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
- "taskDetails": "Kt2smW_taskDetails",
177
+ "taskChevron": "Kt2smW_taskChevron",
159
178
  "miniWave": "Kt2smW_miniWave",
160
- "utterance": "Kt2smW_utterance"
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
- const navigable = useSessions((sessions) => sessions.ids.includes(node.data.taskSessionId));
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
- "voice-wave": "GPPB2G_voice-wave",
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
- "marker": "_0brRca_marker"
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 does not
358
- * expose a row-level slot. This small, read-only decorator keeps the marker in
359
- * sync with that renderer while leaving row interactions and layout intact.
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.byId);
363
- const history = useVoiceHistory((state) => state.entries);
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 sessionIds = new Set(Object.keys(sessions));
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
- if (marker !== null) continue;
380
- (slot ?? createMarkerSlot(row)).append(createMarker());
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
- }, [history, sessions]);
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 { entries: [] };
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 { entries: [] };
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 { entries: [] };
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 { entries: [] };
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
- return { entries };
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 { entries: [] };
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: this.snapshot.getSnapshot().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
- "locale"
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 = () => ({ hooks: { voiceHistory: history.snapshot } });
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;