@xopcai/xopc 0.0.344 → 0.0.346

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/dist/browser-ext/dist/assets/{auth-BsvblhQz.js → auth-CFiOJ_rf.js} +1 -1
  2. package/dist/browser-ext/dist/background.js +1 -1
  3. package/dist/browser-ext/dist/sidepanel.html +1 -1
  4. package/dist/browser-ext/dist/sidepanel.js +1 -1
  5. package/dist/browser-ext/manifest.json +1 -1
  6. package/dist/extensions/telegram/xopc.extension.json +1 -1
  7. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-DRFs0Jjv.js → agent-avatar-dicebear-M2vuj6W3.js} +1 -1
  8. package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-C9J-ILd0.js → agent-defaults-settings-panel-B_d0WGFm.js} +1 -1
  9. package/dist/gateway/static/root/assets/{agent-plugin-dialog-CGstiK5S.js → agent-plugin-dialog-egTQDEeV.js} +1 -1
  10. package/dist/gateway/static/root/assets/{agents-DS-CV-xB.js → agents-BbwmG8Kl.js} +6 -6
  11. package/dist/gateway/static/root/assets/{agents-admin-api-BTYHoqlW.js → agents-admin-api-Dlw9XxYa.js} +1 -1
  12. package/dist/gateway/static/root/assets/{app-management-settings-panel-znCRRKVF.js → app-management-settings-panel-IxWZDAeT.js} +1 -1
  13. package/dist/gateway/static/root/assets/{appearance-settings-DSUujN-S.js → appearance-settings-C56szwjn.js} +1 -1
  14. package/dist/gateway/static/root/assets/{apps-page-7uOjQp30.js → apps-page-Cqoc4xoi.js} +1 -1
  15. package/dist/gateway/static/root/assets/{archive-plugin-DV01jcWu.js → archive-plugin-Ce3TNO-R.js} +1 -1
  16. package/dist/gateway/static/root/assets/{automations-page-D_Ts5PDI.js → automations-page-4so9_Bya.js} +1 -1
  17. package/dist/gateway/static/root/assets/{automations-workspace-Dg7IuWeB.js → automations-workspace-CP3GH1iA.js} +1 -1
  18. package/dist/gateway/static/root/assets/{binary-plugins-CEoIySYJ.js → binary-plugins-l6tKp21u.js} +1 -1
  19. package/dist/gateway/static/root/assets/{block-editor-4bv_tjfg.js → block-editor-cj2RNjQs.js} +1 -1
  20. package/dist/gateway/static/root/assets/{browser-automation-inputs-UYRx7nE_.js → browser-automation-inputs-C9W2EklQ.js} +1 -1
  21. package/dist/gateway/static/root/assets/{browser-automations-page-EtXgro_V.js → browser-automations-page-CbTkFxIX.js} +1 -1
  22. package/dist/gateway/static/root/assets/{browser-settings-page-Bf45sjmd.js → browser-settings-page-KMfDs9dO.js} +1 -1
  23. package/dist/gateway/static/root/assets/{capabilities-B-2wxCUQ.js → capabilities-Co9ZxHHB.js} +1 -1
  24. package/dist/gateway/static/root/assets/{capabilities-page-YjIv97Sw.js → capabilities-page-cm2Q7EfF.js} +2 -2
  25. package/dist/gateway/static/root/assets/{capabilities-settings-panel-Ch2iNnfI.js → capabilities-settings-panel-CIe5qsxW.js} +1 -1
  26. package/dist/gateway/static/root/assets/{capability-header-actions-DJKC0sss.js → capability-header-actions-D2ASOs4Z.js} +1 -1
  27. package/dist/gateway/static/root/assets/{channels-BWZmepe_.js → channels-D-DnLz_c.js} +1 -1
  28. package/dist/gateway/static/root/assets/{chat-terminal-dock-DLBqdgS-.js → chat-terminal-dock-DaQrqzCz.js} +1 -1
  29. package/dist/gateway/static/root/assets/computer-settings-page-C-59NTyH.js +1 -0
  30. package/dist/gateway/static/root/assets/{connector-service-page-CYE4gX1g.js → connector-service-page-Cq7t7AJq.js} +1 -1
  31. package/dist/gateway/static/root/assets/{connectors-page-dJDZx96_.js → connectors-page-BH53KX8Y.js} +1 -1
  32. package/dist/gateway/static/root/assets/{date-picker-BM9HDlUj.js → date-picker-CcfRQKmj.js} +1 -1
  33. package/dist/gateway/static/root/assets/{dependency-picker-BybqITjG.js → dependency-picker-b9Tif8dg.js} +1 -1
  34. package/dist/gateway/static/root/assets/{desktop-pet-CYoJiTna.js → desktop-pet-BHe6Cb51.js} +1 -1
  35. package/dist/gateway/static/root/assets/{desktop-pet-settings-CXcTdFHs.js → desktop-pet-settings-wgA6yOo1.js} +1 -1
  36. package/dist/gateway/static/root/assets/{device-pairing-wizard-DnkRc3SQ.js → device-pairing-wizard-fv8c6u1o.js} +1 -1
  37. package/dist/gateway/static/root/assets/{directory-picker-path-field-BNC2UnEM.js → directory-picker-path-field-DM2YpM4N.js} +1 -1
  38. package/dist/gateway/static/root/assets/{extension-debug-page-CHU0dr2j.js → extension-debug-page-B-Y_Zk8L.js} +1 -1
  39. package/dist/gateway/static/root/assets/{extension-page-DXjPSQIh.js → extension-page-BC1m3MlB.js} +1 -1
  40. package/dist/gateway/static/root/assets/{extension-settings-page-DqFNPQsx.js → extension-settings-page-Cu3cfeJj.js} +1 -1
  41. package/dist/gateway/static/root/assets/{gateway-config-swr-DcE8n_ZS.js → gateway-config-swr-ukBQYwfH.js} +1 -1
  42. package/dist/gateway/static/root/assets/gateway-realtime-DvNfXjan.js +1 -0
  43. package/dist/gateway/static/root/assets/gateway-realtime-store-DGQMXjfI.js +1 -0
  44. package/dist/gateway/static/root/assets/{gateway-settings-BJVKTwIx.js → gateway-settings-pxmn3b8n.js} +1 -1
  45. package/dist/gateway/static/root/assets/{gateway-startup-retry-CCPlq9Lk.js → gateway-startup-retry-CVpOj205.js} +1 -1
  46. package/dist/gateway/static/root/assets/{home-page-B2imtrmi.js → home-page-DOsvv2rU.js} +1 -1
  47. package/dist/gateway/static/root/assets/{image-generation-api-D1Uh-05P.js → image-generation-api-zxx0qG9z.js} +1 -1
  48. package/dist/gateway/static/root/assets/{imports-page-CsypWC3J.js → imports-page-CWYDLeqJ.js} +1 -1
  49. package/dist/gateway/static/root/assets/index-DsIQD44S.js +106 -0
  50. package/dist/gateway/static/root/assets/index-y6kxoLV2.css +1 -0
  51. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-CSzm05SM.js → keyboard-shortcuts-settings-BEkTI6mv.js} +1 -1
  52. package/dist/gateway/static/root/assets/{local-app-workbench-page-BulzWHzC.js → local-app-workbench-page-aSXZL05r.js} +1 -1
  53. package/dist/gateway/static/root/assets/{local-apps-page-BQ2QlJNH.js → local-apps-page-BJThsekO.js} +1 -1
  54. package/dist/gateway/static/root/assets/{local-session-drafts-D9DeB55u.js → local-session-drafts-Dt_Yx7kq.js} +1 -1
  55. package/dist/gateway/static/root/assets/{locale-store-B0WmWiCo.js → locale-store-B008NR-f.js} +1 -1
  56. package/dist/gateway/static/root/assets/{logs-page-harzpOPJ.js → logs-page-WlqqTmpk.js} +1 -1
  57. package/dist/gateway/static/root/assets/{management-settings-BXKeyT_O.js → management-settings-j_npowOA.js} +1 -1
  58. package/dist/gateway/static/root/assets/{markdown-view-CSADmLmZ.js → markdown-view-BjDzimRP.js} +1 -1
  59. package/dist/gateway/static/root/assets/{media-plugins-CMe8YQ8l.js → media-plugins-DhYWGP6O.js} +1 -1
  60. package/dist/gateway/static/root/assets/messages-aVTaD9Mj.js +3 -0
  61. package/dist/gateway/static/root/assets/{note-detail-panel-DD9n9tnf.js → note-detail-panel-CYcNB2Pf.js} +3 -3
  62. package/dist/gateway/static/root/assets/{notes-page-B5NcvMau.js → notes-page-4D1-mt6Y.js} +1 -1
  63. package/dist/gateway/static/root/assets/{page-context-capture-button-BwFIuZqk.js → page-context-capture-button-BfmOqRGN.js} +1 -1
  64. package/dist/gateway/static/root/assets/{page-tabs-DH4JI3t_.js → page-tabs-aFGUXE53.js} +1 -1
  65. package/dist/gateway/static/root/assets/{preview-open-alternatives-D3Wsh5jh.js → preview-open-alternatives-BCEnqJTC.js} +1 -1
  66. package/dist/gateway/static/root/assets/{product-open-page-iigCUM2g.js → product-open-page-DHcx6kDT.js} +1 -1
  67. package/dist/gateway/static/root/assets/{project-detail-page-C19aDnSV.js → project-detail-page-ChKkovsq.js} +1 -1
  68. package/dist/gateway/static/root/assets/{projects-page-aWOL4_py.js → projects-page-Lt3HXMRm.js} +1 -1
  69. package/dist/gateway/static/root/assets/{registry-api-Bw0ZkSav.js → registry-api-EPj5O77m.js} +1 -1
  70. package/dist/gateway/static/root/assets/{remote-access-hub-CGLJFrLG.js → remote-access-hub-B94Vc-fj.js} +1 -1
  71. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-ArGdQMYT.js → runtime-tools-settings-panel-DNU19xEH.js} +1 -1
  72. package/dist/gateway/static/root/assets/{scenes-page-WB5cZttQ.js → scenes-page-BgzfnfOB.js} +1 -1
  73. package/dist/gateway/static/root/assets/{schema-form-Bzga2Tq0.js → schema-form-MKBLqh8C.js} +1 -1
  74. package/dist/gateway/static/root/assets/session-api-D5BlAIge.js +1 -0
  75. package/dist/gateway/static/root/assets/session-manager-DcFS0yfO.js +16 -0
  76. package/dist/gateway/static/root/assets/{sessions-page-DCs83Gg-.js → sessions-page-sYWhgEMH.js} +1 -1
  77. package/dist/gateway/static/root/assets/settings-advanced-gate-alf3atpI.js +1 -0
  78. package/dist/gateway/static/root/assets/{settings-form-section-DMchToFl.js → settings-form-section-C2gcfyxy.js} +1 -1
  79. package/dist/gateway/static/root/assets/{settings-page-Dh-S9_GO.js → settings-page-CYd6_yBb.js} +1 -1
  80. package/dist/gateway/static/root/assets/{setup-status-panel-kFPkdt3z.js → setup-status-panel-CUbH_YtB.js} +1 -1
  81. package/dist/gateway/static/root/assets/{share-preview-page-CrVC0BzQ.js → share-preview-page-Dxwq4fx1.js} +1 -1
  82. package/dist/gateway/static/root/assets/{shares-settings-ZesbsEGA.js → shares-settings-CqwHm0Mf.js} +1 -1
  83. package/dist/gateway/static/root/assets/{skill-api-Deb8HqtR.js → skill-api-C9k6G-tI.js} +1 -1
  84. package/dist/gateway/static/root/assets/skill-reload-api-D9bB-ANm.js +1 -0
  85. package/dist/gateway/static/root/assets/{skills-page-Bgs9jaCc.js → skills-page-CFiCgbAs.js} +1 -1
  86. package/dist/gateway/static/root/assets/{system-settings-panel-CwD47-_i.js → system-settings-panel-1tXKr0cJ.js} +1 -1
  87. package/dist/gateway/static/root/assets/task-detail-page-BBQ3sk4y.js +1 -0
  88. package/dist/gateway/static/root/assets/{tasks-QQJXsiER.js → tasks-BH8_WMUF.js} +1 -1
  89. package/dist/gateway/static/root/assets/{tunnel-api-DtL__Vgg.js → tunnel-api-COo0rSrc.js} +1 -1
  90. package/dist/gateway/static/root/assets/{usage-page-DZ01CsLl.js → usage-page-DHXG-2Rr.js} +1 -1
  91. package/dist/gateway/static/root/assets/{use-autosave-XBFnwLd0.js → use-autosave-CJfX4SWk.js} +1 -1
  92. package/dist/gateway/static/root/assets/{user-model-page-ByACaDBG.js → user-model-page-d-Gjs-EX.js} +1 -1
  93. package/dist/gateway/static/root/assets/{voice-api-key-field-NaIj9rQC.js → voice-api-key-field-D0gWz3iO.js} +1 -1
  94. package/dist/gateway/static/root/assets/{voice-config-api-st1q4lqP.js → voice-config-api-BnnUzr07.js} +1 -1
  95. package/dist/gateway/static/root/assets/{work-discovery-overlay-D418eqa5.js → work-discovery-overlay-CDAZKFVO.js} +1 -1
  96. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-CSXIidqw.js → workflow-run-setup-panel-Db0lk_9G.js} +1 -1
  97. package/dist/gateway/static/root/assets/{workflows-page-nGttfQ1r.js → workflows-page-Dj89Gagd.js} +1 -1
  98. package/dist/gateway/static/root/index.html +16 -16
  99. package/dist/package.js +1 -1
  100. package/dist/packages/endpoint-tools-protocol/src/index.js +2 -1
  101. package/dist/packages/gateway-contract/src/sessions.js +11 -0
  102. package/dist/packages/gateway-contract/src/tasks.js +1 -0
  103. package/dist/src/agent/memory/context-budget.d.ts +2 -0
  104. package/dist/src/agent/memory/context-budget.js +23 -2
  105. package/dist/src/agent/memory/context-recovery.js +2 -2
  106. package/dist/src/agent/service/direct-turn-helpers.js +2 -2
  107. package/dist/src/agent/service/process-direct-streaming.js +16 -2
  108. package/dist/src/agent/tasks/task-judge-service.d.ts +1 -0
  109. package/dist/src/agent/tasks/task-judge-service.js +55 -33
  110. package/dist/src/agent/tool-manuals/computer.d.ts +1 -1
  111. package/dist/src/agent/tool-manuals/computer.js +2 -2
  112. package/dist/src/agent/tool-manuals/xopc-use.d.ts +1 -1
  113. package/dist/src/agent/tool-manuals/xopc-use.js +28 -2
  114. package/dist/src/agent/tools/computer-use-tool.d.ts +0 -2
  115. package/dist/src/agent/tools/computer-use-tool.js +31 -19
  116. package/dist/src/agent/tools/factory.js +2 -3
  117. package/dist/src/agent/tools/xopc-use-tool.js +179 -8
  118. package/dist/src/capabilities/runtime/dispatcher.d.ts +2 -0
  119. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  120. package/dist/src/computer/broker.d.ts +6 -0
  121. package/dist/src/computer/broker.js +14 -0
  122. package/dist/src/computer/errors.js +2 -1
  123. package/dist/src/computer/model-adapter.js +3 -3
  124. package/dist/src/gateway/chat-stream/mapper.d.ts +3 -0
  125. package/dist/src/gateway/chat-stream/mapper.js +5 -1
  126. package/dist/src/gateway/chat-stream/protocol.d.ts +3 -0
  127. package/dist/src/gateway/hono/routes/session-input-handler.d.ts +2 -2
  128. package/dist/src/gateway/hono/routes/sessions.js +19 -2
  129. package/dist/src/gateway/hono/routes/tasks.js +93 -0
  130. package/dist/src/gateway/service/active-execution.js +1 -1
  131. package/dist/src/gateway/service/run-gateway-agent.js +5 -1
  132. package/dist/src/gateway/service/session-input-coordinator.d.ts +1 -1
  133. package/dist/src/gateway/service.d.ts +2 -1
  134. package/dist/src/gateway/service.js +70 -6
  135. package/dist/src/gateway/session-context-summary.js +17 -0
  136. package/dist/src/home-intelligence/host.js +2 -2
  137. package/dist/src/session/client-history.d.ts +7 -1
  138. package/dist/src/session/client-history.js +51 -1
  139. package/dist/src/session/store.js +10 -2
  140. package/dist/src/session/types.d.ts +1 -0
  141. package/dist/src/storage/sqlite/index.d.ts +1 -1
  142. package/dist/src/storage/sqlite/index.js +2 -2
  143. package/dist/src/storage/sqlite/migrations/224_task_origin_links.sql +8 -0
  144. package/dist/src/storage/sqlite/migrations/225_task_collaboration.sql +32 -0
  145. package/dist/src/storage/sqlite/migrations/226_task_main_updates.sql +9 -0
  146. package/dist/src/storage/sqlite/migrations/227_task_main_update_decisions.sql +8 -0
  147. package/dist/src/storage/sqlite/migrations/228_task_main_agent_links.sql +14 -0
  148. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  149. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  150. package/dist/src/storage/sqlite/schema.js +1 -1
  151. package/dist/src/storage/sqlite/session-input-repository.d.ts +9 -0
  152. package/dist/src/storage/sqlite/session-input-repository.js +28 -1
  153. package/dist/src/storage/sqlite/session-repository.js +11 -0
  154. package/dist/src/tasks/capabilities/write.js +2 -0
  155. package/dist/src/tasks/index.js +2 -2
  156. package/dist/src/tasks/task-application-service.js +19 -1
  157. package/dist/src/tasks/task-collaboration-delivery.d.ts +9 -0
  158. package/dist/src/tasks/task-collaboration-delivery.js +100 -0
  159. package/dist/src/tasks/task-collaboration-repository.d.ts +52 -0
  160. package/dist/src/tasks/task-collaboration-repository.js +151 -0
  161. package/dist/src/tasks/task-context-assembler.d.ts +2 -0
  162. package/dist/src/tasks/task-context-assembler.js +23 -1
  163. package/dist/src/tasks/task-main-agent-repository.d.ts +10 -0
  164. package/dist/src/tasks/task-main-agent-repository.js +30 -0
  165. package/dist/src/tasks/task-main-update-decision-service.d.ts +21 -0
  166. package/dist/src/tasks/task-main-update-decision-service.js +118 -0
  167. package/dist/src/tasks/task-main-update-delivery.d.ts +16 -0
  168. package/dist/src/tasks/task-main-update-delivery.js +64 -0
  169. package/dist/src/tasks/task-main-update-input.d.ts +20 -0
  170. package/dist/src/tasks/task-main-update-input.js +37 -0
  171. package/dist/src/tasks/task-orchestration-metrics.d.ts +17 -0
  172. package/dist/src/tasks/task-orchestration-metrics.js +61 -0
  173. package/dist/src/tasks/task-origin-repository.d.ts +14 -0
  174. package/dist/src/tasks/task-origin-repository.js +29 -0
  175. package/dist/src/tasks/task-run-dispatcher.d.ts +3 -0
  176. package/dist/src/tasks/task-run-dispatcher.js +65 -7
  177. package/dist/src/tasks/task-run-repository.d.ts +2 -0
  178. package/dist/src/tasks/task-run-repository.js +12 -1
  179. package/dist/src/tasks/task-sidebar-hierarchy.d.ts +14 -0
  180. package/dist/src/tasks/task-sidebar-hierarchy.js +49 -0
  181. package/dist/src/tui/chat-history.js +8 -0
  182. package/dist/src/tui/tui-session-snapshot.js +4 -3
  183. package/dist/src/tui/tui.js +1 -1
  184. package/dist/src/voice/realtime/agentBroker.d.ts +4 -0
  185. package/dist/src/voice/realtime/agentBroker.js +9 -3
  186. package/dist/src/voice/realtime/agentEngine.js +47 -13
  187. package/dist/src/voice/realtime/engine.d.ts +5 -0
  188. package/dist/src/voice/realtime/omniEngine.js +66 -12
  189. package/dist/src/voice/realtime/playback-echo.js +2 -0
  190. package/dist/src/voice/realtime/runtime.d.ts +6 -0
  191. package/dist/src/voice/realtime/runtime.js +12 -0
  192. package/dist/src/voice/realtime/turnPolicy.d.ts +1 -0
  193. package/dist/src/voice/realtime/turnPolicy.js +3 -0
  194. package/package.json +3 -3
  195. package/dist/gateway/static/root/assets/computer-settings-page-CciQtKpz.js +0 -1
  196. package/dist/gateway/static/root/assets/gateway-realtime-BeUJkjA3.js +0 -1
  197. package/dist/gateway/static/root/assets/gateway-realtime-store-C-8wwbt_.js +0 -1
  198. package/dist/gateway/static/root/assets/index-Ca6iqaCn.css +0 -1
  199. package/dist/gateway/static/root/assets/index-DqQgdJHd.js +0 -106
  200. package/dist/gateway/static/root/assets/messages-CTKYRqR-.js +0 -3
  201. package/dist/gateway/static/root/assets/session-api-BY3dAGBn.js +0 -1
  202. package/dist/gateway/static/root/assets/session-manager-D10sD8IN.js +0 -16
  203. package/dist/gateway/static/root/assets/settings-advanced-gate-yIwyFL-A.js +0 -1
  204. package/dist/gateway/static/root/assets/skill-reload-api-DvWOrc3C.js +0 -1
  205. package/dist/gateway/static/root/assets/task-detail-page-DFEM3Xbj.js +0 -1
@@ -0,0 +1,49 @@
1
+ import { getSqliteDatabase, init_transaction } from "../storage/sqlite/transaction.js";
2
+ //#region src/tasks/task-sidebar-hierarchy.ts
3
+ init_transaction();
4
+ /** Fetch task children for a page of conversations without one query per parent. */
5
+ function listSidebarTaskGroups(conversationIds) {
6
+ const ids = [...new Set(conversationIds.filter(Boolean))];
7
+ const groups = {};
8
+ for (const id of ids) groups[id] = {
9
+ total: 0,
10
+ activeCount: 0,
11
+ items: []
12
+ };
13
+ const db = getSqliteDatabase();
14
+ for (let offset = 0; offset < ids.length; offset += 200) {
15
+ const batch = ids.slice(offset, offset + 200);
16
+ const rows = db.prepare(`SELECT origin.conversation_id AS parent_id, task.task_id, task.title, task.phase,
17
+ state.active_conversation_id,
18
+ worker_session.name AS conversation_title,
19
+ (SELECT run.status FROM task_runs run WHERE run.task_id = task.task_id
20
+ AND run.parent_run_id IS NULL ORDER BY run.queued_at DESC LIMIT 1) AS run_status
21
+ FROM task_origin_links origin
22
+ JOIN tasks task ON task.task_id = origin.task_id
23
+ LEFT JOIN task_conversation_state state ON state.task_id = task.task_id
24
+ LEFT JOIN sessions worker_session ON worker_session.conversation_id = state.active_conversation_id
25
+ WHERE origin.conversation_id IN (${batch.map(() => "?").join(", ")})
26
+ ORDER BY CASE WHEN task.phase = 'closed' THEN 1 ELSE 0 END,
27
+ task.updated_at DESC, task.task_id DESC`).all(...batch);
28
+ for (const row of rows) {
29
+ const group = groups[row.parent_id];
30
+ group.total++;
31
+ if (row.run_status ? [
32
+ "queued",
33
+ "running",
34
+ "waiting",
35
+ "verifying"
36
+ ].includes(row.run_status) : row.phase === "active") group.activeCount++;
37
+ group.items.push({
38
+ taskId: row.task_id,
39
+ title: row.conversation_title?.trim() || row.title,
40
+ phase: row.phase,
41
+ ...row.run_status ? { runStatus: row.run_status } : {},
42
+ ...row.active_conversation_id ? { activeConversationId: row.active_conversation_id } : {}
43
+ });
44
+ }
45
+ }
46
+ return groups;
47
+ }
48
+ //#endregion
49
+ export { listSidebarTaskGroups };
@@ -93,6 +93,14 @@ function appendHistoryToChatLog(chatLog, messages, toolsExpanded, showThinking =
93
93
  });
94
94
  return;
95
95
  }
96
+ if (hm.role === "task" && hm.taskTrigger) {
97
+ chatLog.addSystem(`Task update · ${hm.taskTrigger.taskTitle} (${hm.taskTrigger.kind})`, {
98
+ displayIndex,
99
+ historyIndex,
100
+ role: "system"
101
+ });
102
+ return;
103
+ }
96
104
  chatLog.finalizeAssistant(assistantHistoryMessage(hm.content), runId, {
97
105
  displayIndex,
98
106
  historyIndex,
@@ -103,7 +103,8 @@ var TuiSessionSnapshot = class {
103
103
  }
104
104
  entryFromHistory(message, parentId) {
105
105
  const type = message.kind ?? "message";
106
- const role = message.role;
106
+ const role = message.role === "task" ? "system" : message.role;
107
+ const content = message.taskTrigger ? `Task update · ${message.taskTrigger.taskTitle} (${message.taskTrigger.kind})` : message.content;
107
108
  const base = {
108
109
  id: message.id,
109
110
  parentId,
@@ -112,7 +113,7 @@ var TuiSessionSnapshot = class {
112
113
  data: message.custom?.details,
113
114
  display: message.custom?.display,
114
115
  role,
115
- content: message.content,
116
+ content,
116
117
  timestamp: message.timestamp,
117
118
  raw: message
118
119
  };
@@ -120,7 +121,7 @@ var TuiSessionSnapshot = class {
120
121
  ...base,
121
122
  message: {
122
123
  role,
123
- content: message.content,
124
+ content,
124
125
  usage: {
125
126
  input: 0,
126
127
  output: 0,
@@ -13,7 +13,6 @@ import { renderWorkflowFinalSummary, renderWorkflowPanel } from "../agent/workfl
13
13
  import { ensureStarterAgentsInitialized } from "../agent/starter-agents.js";
14
14
  import "../config/index.js";
15
15
  import { runViewToSnapshot } from "../workflows/service/run-view-to-snapshot.js";
16
- import { resolveTuiConversationId, resolveTuiStartupConversationId } from "../routing/resolve-tui-session-key.js";
17
16
  import { applyThemeById, getBashExcludeBorderColor, getBashModeBorderColor, getCustomThemesDir, getThemeExports, getThinkingBorderColor, initTuiTheme, listAvailableThemeIds, resolveThemePalette } from "./theme-manager.js";
18
17
  import { editorTheme, theme } from "./theme.js";
19
18
  import { extensionForImageMimeType, readClipboardImage } from "./clipboard-image.js";
@@ -45,6 +44,7 @@ import { createEditorSubmitHandler, createSubmitBurstCoalescer, shouldEnableWind
45
44
  import { withTuiSuspended } from "./tui-suspend.js";
46
45
  import { createInitialState } from "./tui-types.js";
47
46
  import { TuiDraftFileStore } from "./backends/embedded-drafts.js";
47
+ import { resolveTuiConversationId, resolveTuiStartupConversationId } from "../routing/resolve-tui-session-key.js";
48
48
  import { EmbeddedBackend } from "./backends/embedded-backend.js";
49
49
  import { GatewayRealtimeBackend } from "./backends/gateway-realtime-backend.js";
50
50
  import { ChatLog } from "./components/chat-log.js";
@@ -17,6 +17,8 @@ export interface VoiceAgentBroker {
17
17
  turnId: string;
18
18
  text: string;
19
19
  signal: AbortSignal;
20
+ clientMessageId?: string;
21
+ origin?: SubmitSessionInput['origin'];
20
22
  }): Promise<VoiceAgentTask>;
21
23
  cancel(taskId: string): Promise<boolean>;
22
24
  }
@@ -47,6 +49,8 @@ export declare class DurableVoiceAgentBroker implements VoiceAgentBroker {
47
49
  turnId: string;
48
50
  text: string;
49
51
  signal: AbortSignal;
52
+ clientMessageId?: string;
53
+ origin?: SubmitSessionInput['origin'];
50
54
  }): Promise<VoiceAgentTask>;
51
55
  cancel(taskId: string): Promise<boolean>;
52
56
  private events;
@@ -29,8 +29,14 @@ var DurableVoiceAgentBroker = class {
29
29
  this.deps = deps;
30
30
  }
31
31
  async delegate(input) {
32
- const clientMessageId = `voice:${input.expectedTranscriptId}:${input.turnId}`;
33
- const activeRunId = this.deps.snapshot(input.conversationId).activeRunId;
32
+ const clientMessageId = input.clientMessageId ?? `voice:${input.expectedTranscriptId}:${input.turnId}`;
33
+ const state = this.deps.snapshot(input.conversationId);
34
+ const activeRunId = state.activeRunId;
35
+ if (input.origin?.type === "system" && input.origin.source === "task_update" && (activeRunId || state.inputs.some((item) => [
36
+ "queued",
37
+ "running",
38
+ "injecting"
39
+ ].includes(item.status)))) throw new Error("Task update cannot overtake an active or queued voice turn");
34
40
  const activeRunCursor = activeRunId ? this.deps.currentSequence(`run:${activeRunId}`) : 0;
35
41
  const result = await this.deps.submit({
36
42
  conversationId: input.conversationId,
@@ -38,7 +44,7 @@ var DurableVoiceAgentBroker = class {
38
44
  clientMessageId,
39
45
  delivery: activeRunId ? "steer" : "next",
40
46
  content: input.text,
41
- origin: {
47
+ origin: input.origin ?? {
42
48
  type: "channel",
43
49
  channel: "voice"
44
50
  }
@@ -13,6 +13,7 @@ const log = createLogger("Voice:Agent");
13
13
  const AGENT_TTS_MAX_SEGMENT_CHARACTERS = 180;
14
14
  const AGENT_TTS_MIN_SEGMENT_CHARACTERS = 24;
15
15
  const PLAYBACK_ECHO_TAIL_MS = 3e3;
16
+ const TASK_UPDATE_QUIET_MS = 1500;
16
17
  function createAgentVoiceEngine(options) {
17
18
  const claim = options.claim;
18
19
  const stt = claim.stt;
@@ -37,6 +38,8 @@ function createAgentVoiceEngine(options) {
37
38
  let recentPlayback;
38
39
  const finalizedUtterances = /* @__PURE__ */ new Set();
39
40
  let pendingTurn;
41
+ let taskUpdateQueued = false;
42
+ let lastUserSpeechAt = 0;
40
43
  const turn = new TurnCoordinator(claim.silenceDurationMs, (text, decision, turnId) => {
41
44
  if (closed || muted) return;
42
45
  log.info({
@@ -74,7 +77,7 @@ function createAgentVoiceEngine(options) {
74
77
  pendingTurn = pending;
75
78
  conversationTail = conversationTail.then(() => {
76
79
  if (pendingTurn === pending) pendingTurn = void 0;
77
- if (generation === inputGeneration && !pending.cancelled) return runAssistantTurn(text, turnId);
80
+ if (generation === inputGeneration && !pending.cancelled) return runAssistantTurn(text, turnId).then(() => {});
78
81
  }).finally(() => {
79
82
  if (generation === inputGeneration) queuedTurns -= 1;
80
83
  });
@@ -228,8 +231,8 @@ function createAgentVoiceEngine(options) {
228
231
  if (response.speechWorker) prefetchNextSpeech(response);
229
232
  else startSpeechWorker(response);
230
233
  }
231
- async function runAssistantTurn(text, turnId) {
232
- if (!claim.tts || !claim.request.conversationId || !text.trim() || closed) return;
234
+ async function runAssistantTurn(text, turnId, updateClientMessageId) {
235
+ if (!claim.tts || !claim.request.conversationId || !text.trim() || closed) return false;
233
236
  cancelActiveResponse("barge_in");
234
237
  const response = {
235
238
  id: `resp_${crypto.randomUUID()}`,
@@ -246,18 +249,26 @@ function createAgentVoiceEngine(options) {
246
249
  awaitingClarification: false,
247
250
  pcm: new PcmFrameBuffer()
248
251
  };
252
+ const taskUpdateExposed = () => Boolean(updateClientMessageId && response.text.trim());
249
253
  activeResponse = response;
250
254
  send("response.created", { responseId: response.id });
251
255
  try {
252
256
  await interruptionWrites;
253
- if (response.abortController.signal.aborted || closed) return;
257
+ if (response.abortController.signal.aborted || closed) return taskUpdateExposed();
254
258
  if (!claim.conversationSessionId) throw new Error("Conversation identity is unavailable");
255
259
  const task = await options.runtime.agentBroker.delegate({
256
260
  text,
257
261
  turnId,
258
262
  conversationId: claim.request.conversationId,
259
263
  expectedTranscriptId: claim.conversationSessionId,
260
- signal: response.abortController.signal
264
+ signal: response.abortController.signal,
265
+ ...updateClientMessageId ? {
266
+ clientMessageId: updateClientMessageId,
267
+ origin: {
268
+ type: "system",
269
+ source: "task_update"
270
+ }
271
+ } : {}
261
272
  });
262
273
  response.taskId = task.taskId;
263
274
  send("task.created", {
@@ -265,7 +276,7 @@ function createAgentVoiceEngine(options) {
265
276
  taskId: task.taskId
266
277
  });
267
278
  for await (const event of task.events) {
268
- if (activeResponse !== response || response.abortController.signal.aborted) return;
279
+ if (activeResponse !== response || response.abortController.signal.aborted) return taskUpdateExposed();
269
280
  if (event.type === "assistant_delta" || event.type === "tool_end" && event.payload?.toolName !== "clarify") response.awaitingClarification = false;
270
281
  if (event.type === "assistant_delta" && typeof event.payload?.delta === "string") {
271
282
  if (response.text.length + event.payload.delta.length > 32e3) throw new Error("Voice response text limit reached");
@@ -314,7 +325,7 @@ function createAgentVoiceEngine(options) {
314
325
  }
315
326
  if (event.type === "error") throw new Error(typeof event.payload?.message === "string" ? event.payload.message : "Agent response failed");
316
327
  }
317
- if (activeResponse !== response || response.abortController.signal.aborted || closed) return;
328
+ if (activeResponse !== response || response.abortController.signal.aborted || closed) return taskUpdateExposed();
318
329
  queuePhrases(response, response.segmenter.flush());
319
330
  send("response.text.done", { responseId: response.id });
320
331
  await waitForSpeech(response);
@@ -322,11 +333,11 @@ function createAgentVoiceEngine(options) {
322
333
  const finalFrame = response.pcm.finish();
323
334
  if (finalFrame) {
324
335
  await response.playback.reserve(finalFrame.byteLength, response.abortController.signal);
325
- if (activeResponse !== response || closed) return;
336
+ if (activeResponse !== response || closed) return taskUpdateExposed();
326
337
  options.sendAudio(response.id, finalFrame);
327
338
  }
328
339
  await response.playback.drain(response.abortController.signal);
329
- if (activeResponse !== response || response.abortController.signal.aborted) return;
340
+ if (activeResponse !== response || response.abortController.signal.aborted) return taskUpdateExposed();
330
341
  if (response.audioStarted && response.audibleText) recentPlayback = {
331
342
  responseId: response.id,
332
343
  text: response.audibleText,
@@ -339,14 +350,16 @@ function createAgentVoiceEngine(options) {
339
350
  audio: response.audioStarted
340
351
  });
341
352
  activeResponse = void 0;
353
+ return !updateClientMessageId || taskUpdateExposed();
342
354
  } catch (error) {
343
- if (response.abortController.signal.aborted || closed) return;
355
+ if (response.abortController.signal.aborted || closed) return taskUpdateExposed();
344
356
  log.warn({
345
357
  err: error,
346
358
  sessionId: claim.sessionId,
347
- responseId: response.id
348
- }, "Realtime voice response failed");
349
- send("session.error", {
359
+ responseId: response.id,
360
+ ...updateClientMessageId ? { clientMessageId: updateClientMessageId } : {}
361
+ }, updateClientMessageId ? "Proactive voice task update deferred" : "Realtime voice response failed");
362
+ if (!updateClientMessageId) send("session.error", {
350
363
  code: "RESPONSE_FAILED",
351
364
  message: "Voice response failed",
352
365
  recoverable: true
@@ -359,6 +372,7 @@ function createAgentVoiceEngine(options) {
359
372
  });
360
373
  response.abortController.abort("provider_error");
361
374
  if (activeResponse === response) activeResponse = void 0;
375
+ return taskUpdateExposed();
362
376
  } finally {
363
377
  await waitForSpeech(response);
364
378
  }
@@ -420,6 +434,7 @@ function createAgentVoiceEngine(options) {
420
434
  }
421
435
  if (muted || finalizedUtterances.has(event.utteranceId)) return;
422
436
  if (event.type === "speech_started") {
437
+ lastUserSpeechAt = Date.now();
423
438
  if (claim.request.purpose === "conversation") {
424
439
  if (pendingTurn) {
425
440
  pendingTurn.cancelled = true;
@@ -440,6 +455,7 @@ function createAgentVoiceEngine(options) {
440
455
  text: event.text
441
456
  });
442
457
  if (event.type === "transcript_final") {
458
+ lastUserSpeechAt = Date.now();
443
459
  const text = event.text.trim();
444
460
  if (!text) {
445
461
  if (claim.request.purpose === "conversation") bufferFinal(event.utteranceId, "");
@@ -506,7 +522,25 @@ function createAgentVoiceEngine(options) {
506
522
  });
507
523
  return inputReset;
508
524
  }
525
+ function canOfferTaskUpdateInput() {
526
+ return claim.request.purpose === "conversation" && Boolean(claim.tts) && !closed && Boolean(sttSession) && !committing && !activeResponse && !pendingTurn && queuedTurns === 0 && turn.isIdle() && Date.now() - lastUserSpeechAt >= TASK_UPDATE_QUIET_MS;
527
+ }
509
528
  return {
529
+ canOfferTaskUpdate() {
530
+ return !taskUpdateQueued && canOfferTaskUpdateInput();
531
+ },
532
+ offerTaskUpdate(input) {
533
+ if (taskUpdateQueued || !canOfferTaskUpdateInput()) return Promise.resolve(false);
534
+ taskUpdateQueued = true;
535
+ const attempt = conversationTail.then(() => {
536
+ if (!canOfferTaskUpdateInput()) return false;
537
+ return runAssistantTurn(input.content, input.clientMessageId, input.clientMessageId);
538
+ });
539
+ conversationTail = attempt.then(() => {}, () => {});
540
+ return attempt.finally(() => {
541
+ taskUpdateQueued = false;
542
+ });
543
+ },
510
544
  async start() {
511
545
  const opened = await openSttSession(claim);
512
546
  if (closed || options.signal.aborted) {
@@ -10,5 +10,10 @@ export interface VoiceEngine {
10
10
  cancel(responseId: string, reason: 'client_cancelled' | 'barge_in'): boolean;
11
11
  cancelTask(taskId: string): Promise<boolean>;
12
12
  acknowledge(responseId: string, playedDurationMs: number): void;
13
+ canOfferTaskUpdate?(): boolean;
14
+ offerTaskUpdate?(input: {
15
+ clientMessageId: string;
16
+ content: string;
17
+ }): Promise<boolean>;
13
18
  close(): void | Promise<void>;
14
19
  }
@@ -2,6 +2,7 @@ import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
3
  import { AudioPlaybackWindow } from "./audio-playback-window.js";
4
4
  import { TurnCoordinator } from "./turnPolicy.js";
5
+ import { isLikelyPlaybackEcho } from "./playback-echo.js";
5
6
  import { PcmFrameBuffer } from "./pcmFrameBuffer.js";
6
7
  import crypto from "node:crypto";
7
8
  import WebSocket from "ws";
@@ -13,6 +14,7 @@ const MAX_PENDING_INPUT_BYTES = 64 * 1024;
13
14
  const UPLOAD_TIMEOUT_MS = 1e4;
14
15
  const RESPONSE_START_TIMEOUT_MS = 3e4;
15
16
  const CANCELLATION_ERROR_GRACE_MS = 5e3;
17
+ const PLAYBACK_ECHO_TAIL_MS = 3e3;
16
18
  const FAILURE_MESSAGES = {
17
19
  OMNI_UPLOAD_TIMEOUT: "The connection to the voice service stopped uploading audio. Check the network to your configured endpoint and reconnect.",
18
20
  OMNI_INPUT_RESET_TIMEOUT: "The voice service did not acknowledge clearing microphone input. Reconnect to continue.",
@@ -43,6 +45,9 @@ function createOmniVoiceEngine(options) {
43
45
  let failed = false;
44
46
  let platformRequestId;
45
47
  let active;
48
+ let recentPlayback;
49
+ let waitingForCancellation;
50
+ let responseCreateQueued = false;
46
51
  let rejectStart;
47
52
  let timer;
48
53
  const recorded = /* @__PURE__ */ new Set();
@@ -55,6 +60,13 @@ function createOmniVoiceEngine(options) {
55
60
  let turnSettled = false;
56
61
  let responseStartTimer;
57
62
  const speaking = /* @__PURE__ */ new Set();
63
+ function maybeCreateResponse() {
64
+ if (!responseCreateQueued || active || waitingForCancellation || closed || failed || muted || inputBlocked) return;
65
+ responseCreateQueued = false;
66
+ clearTimeout(responseStartTimer);
67
+ responseStartTimer = setTimeout(() => fail("OMNI_RESPONSE_START_TIMEOUT"), RESPONSE_START_TIMEOUT_MS);
68
+ send("response.create");
69
+ }
58
70
  const turn = new TurnCoordinator(options.silenceDurationMs, (_text, decision, turnId) => {
59
71
  if (closed || failed || muted || inputBlocked) return;
60
72
  options.send("turn.decision", {
@@ -66,11 +78,10 @@ function createOmniVoiceEngine(options) {
66
78
  });
67
79
  options.send("turn.committed", { turnId });
68
80
  turnSettled = true;
69
- if (active) publish(active);
70
- else {
71
- clearTimeout(responseStartTimer);
72
- responseStartTimer = setTimeout(() => fail("OMNI_RESPONSE_START_TIMEOUT"), RESPONSE_START_TIMEOUT_MS);
73
- }
81
+ const preparedReply = Boolean(active && !active.published);
82
+ if (active && !active.published) publish(active);
83
+ responseCreateQueued = !preparedReply;
84
+ maybeCreateResponse();
74
85
  });
75
86
  function publish(response) {
76
87
  if (response.published || active !== response) return;
@@ -151,7 +162,8 @@ function createOmniVoiceEngine(options) {
151
162
  reason
152
163
  });
153
164
  }
154
- if ((reason === "client_cancelled" || reason === "barge_in" && (options.route.route.managed || !options.bargeIn)) && response.generating && socket?.readyState === WebSocket.OPEN) {
165
+ if (reason !== "session_closed" && response.generating && socket?.readyState === WebSocket.OPEN) {
166
+ waitingForCancellation = response.id;
155
167
  cancellationPendingUntil = Date.now() + CANCELLATION_ERROR_GRACE_MS;
156
168
  send("response.cancel");
157
169
  }
@@ -188,6 +200,10 @@ function createOmniVoiceEngine(options) {
188
200
  text: response.text,
189
201
  interrupted: false
190
202
  });
203
+ if (response.audio && response.text) recentPlayback = {
204
+ text: response.text,
205
+ expiresAt: Date.now() + PLAYBACK_ECHO_TAIL_MS
206
+ };
191
207
  options.send("response.audio.done", { responseId: response.id });
192
208
  options.send("response.done", {
193
209
  responseId: response.id,
@@ -195,6 +211,7 @@ function createOmniVoiceEngine(options) {
195
211
  finishReason: response.audio ? "completed" : "text_only"
196
212
  });
197
213
  active = void 0;
214
+ maybeCreateResponse();
198
215
  };
199
216
  const stopReply = (response, code) => {
200
217
  if (closed || active !== response) return;
@@ -217,6 +234,7 @@ function createOmniVoiceEngine(options) {
217
234
  failed = true;
218
235
  clearTimeout(responseStartTimer);
219
236
  turn.reset();
237
+ responseCreateQueued = false;
220
238
  log.warn({
221
239
  sessionId: options.callId,
222
240
  platformRequestId,
@@ -241,6 +259,7 @@ function createOmniVoiceEngine(options) {
241
259
  function discardInput() {
242
260
  clearTimeout(responseStartTimer);
243
261
  turn.reset();
262
+ responseCreateQueued = false;
244
263
  speaking.clear();
245
264
  turnSettled = false;
246
265
  if (active && !active.published) cancel("client_cancelled");
@@ -300,6 +319,8 @@ function createOmniVoiceEngine(options) {
300
319
  const managedLateCancellation = options.route.route.managed && event.error?.code === "invalid_request_error" && event.error?.message === "The voice provider rejected the request.";
301
320
  if (Date.now() <= cancellationPendingUntil && (lateCancellation || managedLateCancellation)) {
302
321
  cancellationPendingUntil = 0;
322
+ waitingForCancellation = void 0;
323
+ maybeCreateResponse();
303
324
  return;
304
325
  }
305
326
  fail("OMNI_PROVIDER_ERROR");
@@ -318,8 +339,8 @@ function createOmniVoiceEngine(options) {
318
339
  type: "server_vad",
319
340
  threshold: .5,
320
341
  silence_duration_ms: options.silenceDurationMs,
321
- create_response: true,
322
- interrupt_response: options.bargeIn
342
+ create_response: false,
343
+ interrupt_response: false
323
344
  }
324
345
  } });
325
346
  else if (event.type === "session.updated" && !ready) {
@@ -343,7 +364,7 @@ function createOmniVoiceEngine(options) {
343
364
  turnSettled = false;
344
365
  speaking.add(String(event.item_id));
345
366
  turn.start(String(event.item_id));
346
- if (options.bargeIn || active && !active.published) cancel("barge_in");
367
+ if (active && !active.published) cancel("barge_in");
347
368
  options.send("input.speech_started", { utteranceId: String(event.item_id) });
348
369
  } else if (event.type === "input_audio_buffer.speech_stopped") {
349
370
  if (muted || inputBlocked || discardedInputs.has(String(event.item_id)) || recorded.has(String(event.item_id))) return;
@@ -358,7 +379,27 @@ function createOmniVoiceEngine(options) {
358
379
  if (muted || inputBlocked || discardedInputs.has(event.item_id) || recorded.has(event.item_id)) return;
359
380
  pendingInputs.delete(event.item_id);
360
381
  speaking.delete(event.item_id);
361
- turn.final(event.item_id, event.transcript);
382
+ const text = event.transcript.trim();
383
+ const playbackText = active?.audio ? active.text : recentPlayback && recentPlayback.expiresAt >= Date.now() ? recentPlayback.text : void 0;
384
+ if (playbackText && isLikelyPlaybackEcho(text, playbackText)) {
385
+ turn.final(event.item_id, "");
386
+ log.debug({
387
+ sessionId: options.callId,
388
+ responseId: active?.id,
389
+ transcriptCharacters: text.length
390
+ }, "Ignored native voice playback echo");
391
+ return;
392
+ }
393
+ if (text && active?.published && options.bargeIn) {
394
+ log.info({
395
+ sessionId: options.callId,
396
+ responseId: active.id,
397
+ utteranceId: event.item_id,
398
+ transcriptCharacters: text.length
399
+ }, "Confirmed user speech interrupted native voice playback");
400
+ cancel("barge_in");
401
+ }
402
+ turn.final(event.item_id, text);
362
403
  save({
363
404
  itemId: event.item_id,
364
405
  role: "user",
@@ -379,15 +420,25 @@ function createOmniVoiceEngine(options) {
379
420
  recoverable: true
380
421
  });
381
422
  } else if (event.type === "response.created") {
382
- clearTimeout(responseStartTimer);
383
423
  if (muted || inputBlocked) {
384
424
  cancellationPendingUntil = Date.now() + CANCELLATION_ERROR_GRACE_MS;
385
425
  send("response.cancel");
386
426
  return;
387
427
  }
388
- if (active) cancel("barge_in");
428
+ if (active) {
429
+ log.warn({
430
+ sessionId: options.callId,
431
+ responseId: active.id
432
+ }, "Ignored overlapping native voice reply");
433
+ cancellationPendingUntil = Date.now() + CANCELLATION_ERROR_GRACE_MS;
434
+ send("response.cancel");
435
+ return;
436
+ }
437
+ clearTimeout(responseStartTimer);
389
438
  const id = event.response?.id;
390
439
  if (typeof id !== "string" || !id.length || id.length > 160) throw new Error("Invalid response ID");
440
+ responseCreateQueued = false;
441
+ waitingForCancellation = void 0;
391
442
  let release;
392
443
  const settled = new Promise((resolve) => {
393
444
  release = resolve;
@@ -467,6 +518,9 @@ function createOmniVoiceEngine(options) {
467
518
  if (!response.abort.signal.aborted) stopReply(response, "OMNI_PLAYBACK_FAILED");
468
519
  });
469
520
  }
521
+ } else if (event.type === "response.done" && event.response?.id === waitingForCancellation) {
522
+ waitingForCancellation = void 0;
523
+ maybeCreateResponse();
470
524
  }
471
525
  } catch {
472
526
  fail("OMNI_PROTOCOL_ERROR");
@@ -23,6 +23,8 @@ function isLikelyPlaybackEcho(transcript, assistantText) {
23
23
  const containsCjk = /\p{Script=Han}/u.test(heard);
24
24
  if (heard.length < (containsCjk ? 3 : 6)) return false;
25
25
  if (spoken.includes(heard)) return true;
26
+ if (heard.length > 200 || spoken.length > 8e3) return false;
27
+ if (containsCjk && heard.length >= 4 && heard.length <= 5) return closestSubstringEditDistance(heard, spoken) <= 1;
26
28
  if (heard.length < (containsCjk ? 6 : 10)) return false;
27
29
  return closestSubstringEditDistance(heard, spoken) / Array.from(heard).length <= .22;
28
30
  }
@@ -27,6 +27,12 @@ export declare class VoiceRealtimeRuntime {
27
27
  createSession(request: CreateVoiceSessionRequest, principalId: string, now?: number): Promise<CreateVoiceSessionResponse>;
28
28
  handleUpgrade(req: IncomingMessage, socket: Socket, head: Buffer): boolean;
29
29
  hasConversation(conversationId: string): boolean;
30
+ canOfferTaskUpdate(conversationId: string): boolean;
31
+ offerTaskUpdate(input: {
32
+ conversationId: string;
33
+ clientMessageId: string;
34
+ content: string;
35
+ }): Promise<boolean>;
30
36
  cancelSession(sessionId: string, ticket: string, principalId: string): Promise<boolean>;
31
37
  disconnectPrincipal(principalId: string): void;
32
38
  close(): void;
@@ -298,6 +298,16 @@ var VoiceRealtimeRuntime = class {
298
298
  this.pruneTickets(Date.now());
299
299
  return this.conversationReservations.has(conversationId);
300
300
  }
301
+ canOfferTaskUpdate(conversationId) {
302
+ const sessionId = this.conversationReservations.get(conversationId);
303
+ return (sessionId ? this.activeSessions.get(sessionId) : void 0)?.engine?.canOfferTaskUpdate?.() === true;
304
+ }
305
+ async offerTaskUpdate(input) {
306
+ const sessionId = this.conversationReservations.get(input.conversationId);
307
+ const session = sessionId ? this.activeSessions.get(sessionId) : void 0;
308
+ if (!session?.engine?.canOfferTaskUpdate?.()) return false;
309
+ return session.engine.offerTaskUpdate?.(input) ?? false;
310
+ }
301
311
  async cancelSession(sessionId, ticket, principalId) {
302
312
  const key = ticketKey(ticket);
303
313
  const claim = this.tickets.get(key);
@@ -526,6 +536,8 @@ var VoiceRealtimeRuntime = class {
526
536
  });
527
537
  await engine.start();
528
538
  if (closed) return;
539
+ const active = this.activeSessions.get(consumed.sessionId);
540
+ if (active?.socket === socket) active.engine = engine;
529
541
  ready = true;
530
542
  log.info({
531
543
  sessionId: consumed.sessionId,
@@ -29,6 +29,7 @@ export declare class TurnCoordinator {
29
29
  final(id: string, text: string): void;
30
30
  reset(): void;
31
31
  restore(id: string, text: string): void;
32
+ isIdle(): boolean;
32
33
  private text;
33
34
  private invalidatePendingDecision;
34
35
  private evaluate;
@@ -72,6 +72,9 @@ var TurnCoordinator = class {
72
72
  stopped: true
73
73
  });
74
74
  }
75
+ isIdle() {
76
+ return this.parts.size === 0 && this.timer === void 0;
77
+ }
75
78
  text() {
76
79
  return [...this.parts.values()].map((part) => part.text ?? "").filter(Boolean).join(" ");
77
80
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xopcai/xopc",
3
- "version": "0.0.344",
3
+ "version": "0.0.346",
4
4
  "description": "Local-first personal AI for the one-person company, with durable context, tasks, workflows, automations, and local or cloud models.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -159,11 +159,11 @@
159
159
  "vitest": "4.1.5",
160
160
  "vue": "^3.5.35",
161
161
  "@xopcai/browser-control-contract": "0.1.0",
162
- "@xopcai/computer-control-contract": "0.1.0",
163
162
  "@xopcai/endpoint-tools-client": "0.0.0",
163
+ "@xopcai/computer-control-contract": "0.1.0",
164
164
  "@xopcai/endpoint-tools-protocol": "0.0.0",
165
- "@xopcai/gateway-contract": "0.0.0",
166
165
  "@xopcai/realtime-client": "0.0.0",
166
+ "@xopcai/gateway-contract": "0.0.0",
167
167
  "@xopcai/realtime-protocol": "0.0.0"
168
168
  },
169
169
  "scripts": {
@@ -1 +0,0 @@
1
- import{i as e}from"./rolldown-runtime-aKtaBQYM.js";import{a as t,n,t as r}from"./vendor-react-BOUWij0V.js";import{t as i}from"./chunk-KS7C4IRE-BMYIfy9x.js";import{t as a}from"./globe-DSSGFWFn.js";import{t as o}from"./messages-CTKYRqR-.js";import{a as s,n as c,r as l}from"./locale-store-B0WmWiCo.js";import{i as u}from"./vendor-swr-BIWyz7Rc.js";import{t as d}from"./button-CS04eWY9.js";import{r as f}from"./gateway-config-swr-DcE8n_ZS.js";import{n as p,t as m}from"./registry-api-Bw0ZkSav.js";import{t as h}from"./skeleton-BwbyijEw.js";import{t as g}from"./electron-env-Dit0bHgy.js";import{Lc as _,Mi as v,Oc as y,Sn as b,gl as x,hl as S,xn as C}from"./index-DqQgdJHd.js";import{n as w,r as T}from"./settings-page-layout-X1x-2Cp9.js";var E=n(),D=e(t(),1),O=r();function k({zh:e}){let t=o(e?`zh`:`en`).computerSettings,n=u(`settings-agent-defaults`,C),r=u(m,p),a=n.data?.defaults.models.computerUse?.primary??``,[s,c]=(0,D.useState)(a),[l,f]=(0,D.useState)(!1),[g,_]=(0,D.useState)(``),[y,x]=(0,D.useState)(!1);(0,D.useEffect)(()=>{c(a)},[a]);let S=!s||r.data?.some(e=>e.id===s&&e.computerUse),w=async()=>{f(!0),_(``),x(!1);try{if(!S)throw Error(t.modelUnavailable);let e=await C(),r={...e.defaults.models};s?r.computerUse={primary:s,fallbacks:[]}:delete r.computerUse;let i=await b({...e.defaults,models:r});await n.mutate(i,!1),x(!0)}catch(e){_(e instanceof Error?e.message:String(e))}finally{f(!1)}};return(0,O.jsxs)(`section`,{"aria-label":t.model,className:`space-y-3`,children:[(0,O.jsx)(`h2`,{className:`text-sm font-semibold text-fg`,children:t.model}),!n.data&&!n.error?(0,O.jsx)(h,{className:`h-52 w-full rounded-xl`}):n.error?(0,O.jsxs)(`div`,{role:`alert`,className:`text-sm text-danger`,children:[String(n.error),(0,O.jsx)(d,{onClick:()=>{n.mutate().catch(()=>{})},children:t.retry})]}):(0,O.jsxs)(`div`,{className:`space-y-4 rounded-xl bg-surface-hover/25 p-4 sm:p-5`,children:[(0,O.jsx)(`p`,{className:`text-sm text-fg-muted`,children:t.modelDescription}),(0,O.jsx)(`p`,{className:`text-xs leading-relaxed text-fg-muted`,children:t.modelScope}),r.isLoading?(0,O.jsx)(h,{className:`h-10 w-full rounded-lg`}):(0,O.jsx)(v,{value:s,onChange:e=>{c(e),x(!1)},disabled:l,capabilitiesFilter:`computer-use`,models:r.data??[],modelsError:r.error,placeholder:t.selectModel,searchPlaceholder:t.searchModel,noMatches:t.noModels,registryEmptyHint:t.noModels,allowEmpty:!0,emptyLabel:t.noModel,ariaLabel:t.modelLabel,className:`w-full`,contentAlign:`start`}),!S&&!r.isLoading&&!r.error&&(0,O.jsx)(`p`,{role:`status`,className:`text-sm text-warning`,children:t.modelUnavailable}),r.error&&(0,O.jsxs)(`p`,{role:`alert`,className:`text-sm text-danger`,children:[String(r.error),(0,O.jsx)(d,{onClick:()=>{r.mutate().catch(()=>{})},children:t.retry})]}),(0,O.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,O.jsx)(d,{variant:`primary`,disabled:l||s===a||!S||!!r.error||r.isLoading,onClick:()=>{w()},children:t.saveModel}),(0,O.jsx)(d,{asChild:!0,variant:`ghost`,children:(0,O.jsx)(i,{to:`/settings/capabilities/models?add=1`,children:t.manageModels})}),y&&(0,O.jsx)(`span`,{role:`status`,className:`text-xs text-fg-muted`,children:t.modelSaved})]}),g&&(0,O.jsx)(`p`,{role:`alert`,className:`text-sm text-danger`,children:g})]}),(0,O.jsx)(`p`,{className:`text-xs leading-relaxed text-fg-muted`,children:t.modelPrivacy})]})}function A(e){let t=(0,E.c)(15),{icon:n,title:r,description:i,children:a}=e,o;t[0]===n?o=t[1]:(o=n&&(0,O.jsx)(`div`,{className:`flex size-10 shrink-0 items-center justify-center rounded-xl bg-surface-base/70 text-fg-muted`,"aria-hidden":!0,children:n}),t[0]=n,t[1]=o);let s;t[2]===r?s=t[3]:(s=(0,O.jsx)(`h3`,{className:`text-sm font-medium text-fg`,children:r}),t[2]=r,t[3]=s);let c;t[4]===i?c=t[5]:(c=i&&(0,O.jsx)(`div`,{className:`mt-1 text-sm leading-relaxed text-fg-muted`,children:i}),t[4]=i,t[5]=c);let l;t[6]!==s||t[7]!==c?(l=(0,O.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[s,c]}),t[6]=s,t[7]=c,t[8]=l):l=t[8];let u;t[9]===a?u=t[10]:(u=a&&(0,O.jsx)(`div`,{className:`flex shrink-0 items-center gap-3`,children:a}),t[9]=a,t[10]=u);let d;return t[11]!==o||t[12]!==l||t[13]!==u?(d=(0,O.jsxs)(`div`,{className:`flex flex-wrap items-center gap-4 p-4 sm:flex-nowrap sm:p-5`,children:[o,l,u]}),t[11]=o,t[12]=l,t[13]=u,t[14]=d):d=t[14],d}function j(){let e=(0,E.c)(2),t=c(M)===`zh`,n;return e[0]===t?n=e[1]:(n=(0,O.jsx)(N,{zh:t}),e[0]=t,e[1]=n),n}function M(e){return e.language}function N({zh:e}){let t=o(e?`zh`:`en`).computerSettings,{data:n,mutate:r,error:c}=f(!0),u=n?.payload?.config,[p,m]=(0,D.useState)(!1),[v,b]=(0,D.useState)(``),[C,E]=(0,D.useState)(!1),[j,M]=(0,D.useState)(null),[N,P]=(0,D.useState)(!1),F=window.electronAPI?.platform===`darwin`?window.electronAPI.computer:void 0,I=window.electronAPI?.platform===`darwin`?window.electronAPI.system:void 0;(0,D.useEffect)(()=>{if(!F)return;let e=!1,t=!1,n=async()=>{if(!t){t=!0;try{let t=await F.status();e||(M(t),P(!1))}catch{e||(M(null),P(!0))}finally{t=!1}}};n();let r=window.setInterval(()=>{n()},2e3);return()=>{e=!0,window.clearInterval(r)}},[F]);let L=async e=>{m(!0),b(``);try{await e()}catch(e){b(e instanceof Error?e.message:String(e))}finally{m(!1)}},R=e=>L(async()=>{g()&&(await s(l(`/api/config`),{method:`PATCH`,headers:{"Content-Type":`application/json`},body:JSON.stringify({computer:{enabled:e}})}),await r())}),z=async()=>{b(``),E(!1);try{await F?.stop(),E(!0),F&&M(await F.status())}catch(e){b(e instanceof Error?e.message:String(e))}},B=j?.session?.status??`idle`,V=t.states[B]??B,H=g()&&u?.computer?.enabled===!0;return(0,O.jsxs)(T,{gap:`gap-7`,children:[(0,O.jsx)(w,{title:t.title,subtitle:t.subtitle,meta:(0,O.jsxs)(`div`,{className:`mt-3 space-y-3`,children:[(0,O.jsxs)(`div`,{className:`flex flex-wrap gap-2`,children:[(0,O.jsx)(`span`,{className:`inline-flex rounded-md border border-edge px-2 py-1 text-xs text-fg-muted`,children:t.experimental}),(0,O.jsx)(`span`,{className:`inline-flex rounded-md border border-edge px-2 py-1 text-xs text-fg-muted`,children:t.preview})]}),(0,O.jsx)(`p`,{className:`text-sm leading-relaxed text-fg-muted`,children:t.experimentalDescription})]}),actions:F&&(0,O.jsxs)(d,{onClick:()=>{z()},children:[(0,O.jsx)(y,{className:`size-3.5`}),t.stop]})}),v&&(0,O.jsx)(`p`,{role:`alert`,className:`text-sm text-red-600`,children:v}),C&&(0,O.jsx)(`p`,{role:`status`,className:`text-sm text-fg-muted`,children:t.stoppedNotice}),j?.controlPaused&&(0,O.jsxs)(`div`,{className:`flex flex-wrap items-center justify-between gap-3 rounded-xl bg-warning-soft p-4`,children:[(0,O.jsx)(`p`,{className:`text-sm text-fg-muted`,children:t.pausedNotice}),(0,O.jsx)(d,{disabled:p,onClick:()=>{L(async()=>{F&&M(await F.resume()),E(!1)})},children:t.resume})]}),(0,O.jsx)(k,{zh:e}),(0,O.jsxs)(`section`,{"aria-labelledby":`computer-control-title`,className:`space-y-3`,children:[(0,O.jsx)(`h2`,{id:`computer-control-title`,className:`text-sm font-semibold text-fg`,children:t.control}),!u&&!c?(0,O.jsx)(h,{className:`h-40 w-full rounded-xl`}):c?(0,O.jsxs)(`div`,{role:`alert`,className:`text-sm text-fg-muted`,children:[String(c),` `,(0,O.jsx)(d,{onClick:()=>{L(()=>r())},children:t.retry})]}):(0,O.jsxs)(`div`,{className:`flex flex-col gap-1 rounded-xl bg-surface-hover/20 p-1 [&>*]:rounded-lg [&>*]:bg-surface-base/45`,children:[(0,O.jsx)(A,{icon:(0,O.jsx)(S,{className:`size-5`}),title:t.desktop,description:t.desktopDescription,children:(0,O.jsx)(`button`,{type:`button`,role:`switch`,"aria-label":t.desktop,"aria-checked":H,disabled:p||!g(),onClick:()=>{R(u?.computer?.enabled!==!0)},className:`touch-target flex items-center rounded-lg px-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent disabled:opacity-50`,children:(0,O.jsx)(`span`,{className:`flex h-6 w-11 items-center rounded-full p-0.5 transition-colors ${H?`bg-accent`:`bg-fg-muted/30`}`,children:(0,O.jsx)(`span`,{className:`size-5 rounded-full bg-white transition-transform ${H?`translate-x-5`:`translate-x-0`}`})})})}),(0,O.jsx)(A,{icon:(0,O.jsx)(_,{className:`size-5`}),title:t.approvals,description:t.approvalsDescription,children:(0,O.jsx)(`span`,{className:`rounded-md border border-edge px-2 py-1 text-xs text-fg-muted`,children:t.approvalsRequired})}),(0,O.jsx)(A,{icon:(0,O.jsx)(a,{className:`size-5`}),title:t.browser,description:(0,O.jsxs)(O.Fragment,{children:[t.browserDescription,(0,O.jsx)(`span`,{className:`mt-1 block text-xs`,children:u?.browser?.enabled?t.browserEnabled:t.browserDisabled})]}),children:(0,O.jsx)(d,{asChild:!0,children:(0,O.jsx)(i,{to:`/settings/agent-browser`,children:t.manage})})})]}),(0,O.jsxs)(`p`,{className:`text-xs leading-relaxed text-fg-muted`,children:[t.safety,` `,t.lockedDescription]})]}),(0,O.jsxs)(`section`,{"aria-labelledby":`computer-permissions-title`,className:`space-y-3`,children:[(0,O.jsx)(`h2`,{id:`computer-permissions-title`,className:`text-sm font-semibold text-fg`,children:t.permissions}),F?N?(0,O.jsx)(`p`,{role:`alert`,className:`rounded-xl bg-danger-soft p-5 text-sm text-danger`,children:t.nativeError}):j?(0,O.jsxs)(`div`,{className:`flex flex-col gap-1 rounded-xl bg-surface-hover/20 p-1 [&>*]:rounded-lg [&>*]:bg-surface-base/45`,children:[(0,O.jsx)(A,{icon:(0,O.jsx)(x,{className:`size-5`}),title:t.device,description:(0,O.jsxs)(O.Fragment,{children:[(0,O.jsxs)(`span`,{"aria-live":`polite`,children:[j.connected?t.connected:t.disconnected,` · `,V]}),j.reenrollmentRequired&&(0,O.jsx)(`p`,{className:`mt-1`,children:t.revoked})]}),children:j.reenrollmentRequired&&(0,O.jsx)(d,{disabled:p,onClick:()=>{L(async()=>{M(await F.reenroll())})},children:t.reenroll})}),I&&(0,O.jsxs)(O.Fragment,{children:[(0,O.jsxs)(A,{icon:(0,O.jsx)(_,{className:`size-5`}),title:t.accessibility,description:t.accessibilityDescription,children:[(0,O.jsx)(`span`,{className:`text-xs text-fg-muted`,children:j.permissions.accessibility?t.granted:t.notGranted}),!j.permissions.accessibility&&(0,O.jsx)(d,{disabled:p,"aria-label":`${t.openSettings}: ${t.accessibility}`,onClick:()=>{L(()=>I.requestAccessibility())},children:t.openSettings})]}),(0,O.jsxs)(A,{icon:(0,O.jsx)(x,{className:`size-5`}),title:t.screen,description:t.screenDescription,children:[(0,O.jsx)(`span`,{className:`text-xs text-fg-muted`,children:j.permissions.screenRecording===`granted`?t.granted:t.notGranted}),j.permissions.screenRecording!==`granted`&&(0,O.jsx)(d,{disabled:p,"aria-label":`${t.openSettings}: ${t.screen}`,onClick:()=>{L(()=>I.requestScreen())},children:t.openSettings})]})]})]}):(0,O.jsx)(`div`,{"aria-label":t.checking,children:(0,O.jsx)(h,{className:`h-52 w-full rounded-xl`})}):(0,O.jsx)(`p`,{className:`rounded-xl bg-surface-hover/20 p-5 text-sm leading-relaxed text-fg-muted`,children:window.electronAPI&&window.electronAPI.platform!==`darwin`?t.unsupportedPlatform:t.desktopRequired}),(j?.error||j?.session?.errorCode)&&(0,O.jsx)(`p`,{role:`alert`,className:`break-words text-sm text-red-600`,children:j.error||j.session?.errorCode})]})]})}export{j as ComputerSettingsPage,N as ComputerSettingsPanel};