@xopcai/xopc 0.0.363 → 0.0.364

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 (127) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-DkcXyPH0.js → agent-avatar-dicebear-7Ct-ZlfL.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-B-l5AaYN.js → agent-defaults-settings-panel-wYj-iyqv.js} +1 -1
  5. package/dist/gateway/static/root/assets/{agent-plugin-dialog-BY6tPYOK.js → agent-plugin-dialog-C9HYMhnJ.js} +1 -1
  6. package/dist/gateway/static/root/assets/{agents-CdSEoeje.js → agents-hPkFQ81z.js} +3 -3
  7. package/dist/gateway/static/root/assets/{app-management-settings-panel-BtD53cfe.js → app-management-settings-panel-CjfsIn9A.js} +1 -1
  8. package/dist/gateway/static/root/assets/{appearance-settings-BAweWgX_.js → appearance-settings-qqHZ15GU.js} +1 -1
  9. package/dist/gateway/static/root/assets/{apps-page-CS6-hrIB.js → apps-page-Ba2VkQk6.js} +1 -1
  10. package/dist/gateway/static/root/assets/{automations-page-CtE8r9Y2.js → automations-page-Bt8Q58ut.js} +1 -1
  11. package/dist/gateway/static/root/assets/{automations-workspace-pnMgR4Da.js → automations-workspace-Ch0ViAJW.js} +1 -1
  12. package/dist/gateway/static/root/assets/{block-editor-D5hf6WcP.js → block-editor-ClRFbHP7.js} +1 -1
  13. package/dist/gateway/static/root/assets/{browser-automations-page-PZDQlXH4.js → browser-automations-page-BjMub7JM.js} +1 -1
  14. package/dist/gateway/static/root/assets/{browser-settings-page-2YDV-ZWt.js → browser-settings-page-CYrPA-Fp.js} +1 -1
  15. package/dist/gateway/static/root/assets/{capabilities-page-CB-iX3Fy.js → capabilities-page-dW6FXtOy.js} +2 -2
  16. package/dist/gateway/static/root/assets/{capabilities-settings-panel-BCTuWNaW.js → capabilities-settings-panel-gZ8auLwa.js} +1 -1
  17. package/dist/gateway/static/root/assets/{capability-header-actions--aC5XB9E.js → capability-header-actions-L1NvoOUG.js} +1 -1
  18. package/dist/gateway/static/root/assets/{channels-JnAQP1bt.js → channels-Dx5T2G73.js} +1 -1
  19. package/dist/gateway/static/root/assets/{chat-terminal-dock-BFZEpvzn.js → chat-terminal-dock-DsCpKPBe.js} +1 -1
  20. package/dist/gateway/static/root/assets/{computer-settings-page-Dc9ooEM-.js → computer-settings-page-6FWwxYLe.js} +1 -1
  21. package/dist/gateway/static/root/assets/{connector-service-page-1_QB5D-t.js → connector-service-page-mtH9mEBf.js} +1 -1
  22. package/dist/gateway/static/root/assets/{connectors-page-Um_Jy5zh.js → connectors-page-DgDLaws0.js} +1 -1
  23. package/dist/gateway/static/root/assets/{date-picker-C9ZqTPui.js → date-picker-BDcwEaIx.js} +1 -1
  24. package/dist/gateway/static/root/assets/{dependency-picker-DBz0Pmyr.js → dependency-picker-B_fPQBTM.js} +1 -1
  25. package/dist/gateway/static/root/assets/{desktop-pet-BLtp2MmX.js → desktop-pet-CJrO9Vd_.js} +1 -1
  26. package/dist/gateway/static/root/assets/{desktop-pet-settings-BNGbqwec.js → desktop-pet-settings-DYAfri9w.js} +1 -1
  27. package/dist/gateway/static/root/assets/{device-pairing-wizard-7rdo-A7d.js → device-pairing-wizard-CqRys2R8.js} +1 -1
  28. package/dist/gateway/static/root/assets/{directory-picker-path-field-BnilGgwM.js → directory-picker-path-field-D7_qSxNZ.js} +1 -1
  29. package/dist/gateway/static/root/assets/{extension-page-DsrVktRN.js → extension-page-D1zBmd8F.js} +1 -1
  30. package/dist/gateway/static/root/assets/{extension-settings-page-jeb0ytRK.js → extension-settings-page-CTR8AjCG.js} +1 -1
  31. package/dist/gateway/static/root/assets/{fallback-models-bZcxkEDz.js → fallback-models-Chhv4Nfv.js} +1 -1
  32. package/dist/gateway/static/root/assets/{gateway-settings-A9SzLsGp.js → gateway-settings-BVfz4Tla.js} +1 -1
  33. package/dist/gateway/static/root/assets/{home-page-CnHzaH9K.js → home-page-BF_9C9yW.js} +1 -1
  34. package/dist/gateway/static/root/assets/{imports-page-CzpsV6Vz.js → imports-page-9EnECtcl.js} +1 -1
  35. package/dist/gateway/static/root/assets/{index-BYNmHn-z.js → index-BpXej9jJ.js} +5 -5
  36. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-D90Dvlwo.js → keyboard-shortcuts-settings-MC7mG2h2.js} +1 -1
  37. package/dist/gateway/static/root/assets/{local-app-workbench-page-_9IKMkV8.js → local-app-workbench-page-CytNu0-_.js} +1 -1
  38. package/dist/gateway/static/root/assets/{local-apps-page-D6gejARs.js → local-apps-page-c3HncHZ3.js} +1 -1
  39. package/dist/gateway/static/root/assets/{logs-page--t8d-oLb.js → logs-page-D4aXtitd.js} +1 -1
  40. package/dist/gateway/static/root/assets/{management-settings-DVDmXzTg.js → management-settings-CRzWi_iJ.js} +1 -1
  41. package/dist/gateway/static/root/assets/{note-detail-panel-DuZAEfU1.js → note-detail-panel-BgbByI-4.js} +3 -3
  42. package/dist/gateway/static/root/assets/{notes-page-CgWNESGp.js → notes-page-R2BX5XCE.js} +1 -1
  43. package/dist/gateway/static/root/assets/{page-context-capture-button-Z72eiQZn.js → page-context-capture-button-RBp1QK0F.js} +1 -1
  44. package/dist/gateway/static/root/assets/{personal-page-BtMatL9a.js → personal-page-F0jTBWva.js} +1 -1
  45. package/dist/gateway/static/root/assets/{product-open-page-w5Hdf0hQ.js → product-open-page-BtDK68qb.js} +1 -1
  46. package/dist/gateway/static/root/assets/{project-detail-page-CP6I3ZCV.js → project-detail-page-DmQEm0yH.js} +1 -1
  47. package/dist/gateway/static/root/assets/{projects-page-DYs6XrrK.js → projects-page-CrLQvkHX.js} +1 -1
  48. package/dist/gateway/static/root/assets/{remote-access-hub-DjYoYAEw.js → remote-access-hub-C25_hQAd.js} +1 -1
  49. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-Qar_DXLv.js → runtime-tools-settings-panel-B0c120iI.js} +1 -1
  50. package/dist/gateway/static/root/assets/{scenes-page-BSIcDCa-.js → scenes-page-DM64yMnx.js} +1 -1
  51. package/dist/gateway/static/root/assets/{schema-form-C_cJLGgF.js → schema-form-DQpQc9tH.js} +1 -1
  52. package/dist/gateway/static/root/assets/{sessions-page-BBV5Z24a.js → sessions-page-DvDO8LtA.js} +1 -1
  53. package/dist/gateway/static/root/assets/settings-advanced-gate-BNhgbmcX.js +1 -0
  54. package/dist/gateway/static/root/assets/{settings-page-F2vGXBw7.js → settings-page-GgBEp6O7.js} +1 -1
  55. package/dist/gateway/static/root/assets/{setup-status-panel-DJxbjUOg.js → setup-status-panel-10bMi6yL.js} +1 -1
  56. package/dist/gateway/static/root/assets/{shares-settings-CJa6FBpP.js → shares-settings-DJ1ME-XM.js} +1 -1
  57. package/dist/gateway/static/root/assets/{skill-api-C340XD9d.js → skill-api-1Puv6wOz.js} +1 -1
  58. package/dist/gateway/static/root/assets/{skills-page-C4lF94m5.js → skills-page-D52pEr5x.js} +1 -1
  59. package/dist/gateway/static/root/assets/{system-settings-panel-CmJUu3lb.js → system-settings-panel--yV67a8L.js} +1 -1
  60. package/dist/gateway/static/root/assets/{task-detail-page-De-Rr8W6.js → task-detail-page-D-tcS2bS.js} +1 -1
  61. package/dist/gateway/static/root/assets/{tunnel-api-Bl4nrCuE.js → tunnel-api-COLdiP_E.js} +1 -1
  62. package/dist/gateway/static/root/assets/{usage-page-QZEAaa8n.js → usage-page-CtM5IfrL.js} +1 -1
  63. package/dist/gateway/static/root/assets/{user-model-page-Crh2oNpZ.js → user-model-page-CSh61kZ5.js} +1 -1
  64. package/dist/gateway/static/root/assets/{work-discovery-overlay-9jnjEAaz.js → work-discovery-overlay-C9u7tuEc.js} +1 -1
  65. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BAcPxdcT.js → workflow-run-setup-panel-BMOBbf33.js} +1 -1
  66. package/dist/gateway/static/root/assets/{workflows-page-D8Zm-ooa.js → workflows-page-BrD9vXqO.js} +1 -1
  67. package/dist/gateway/static/root/index.html +1 -1
  68. package/dist/package.js +1 -1
  69. package/dist/src/agent/agent-manager.js +1 -1
  70. package/dist/src/agent/background-review/run-background-review.js +1 -1
  71. package/dist/src/agent/external-tools/endpoint-provider.js +2 -2
  72. package/dist/src/agent/service.js +1 -1
  73. package/dist/src/agent/skills/index.js +1 -1
  74. package/dist/src/agent/skills/managed-store.js +1 -1
  75. package/dist/src/agent/skills/skill-manage-ops.js +1 -1
  76. package/dist/src/agent/tools/xopc-use-tool.js +2 -2
  77. package/dist/src/agent-catalog/capabilities.js +2 -2
  78. package/dist/src/agent-catalog/migrations/legacy-config-cutover.js +1 -1
  79. package/dist/src/agent-catalog/repository.js +1 -1
  80. package/dist/src/automations/capabilities/drafts.js +1 -1
  81. package/dist/src/bootstrap/application-state.js +1 -1
  82. package/dist/src/capabilities/runtime/dispatcher.js +1 -1
  83. package/dist/src/capabilities/runtime/product.js +1 -1
  84. package/dist/src/cli/commands/agents.js +1 -1
  85. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  86. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  87. package/dist/src/cli/commands/models.js +1 -1
  88. package/dist/src/cli/commands/onboard/model.js +2 -2
  89. package/dist/src/cli/utils/init-workspace-core.js +1 -1
  90. package/dist/src/config/agent-profile.js +1 -1
  91. package/dist/src/extensions/agent-plugins/validation.js +1 -1
  92. package/dist/src/gateway/agents-admin.js +2 -2
  93. package/dist/src/gateway/hono/routes/agents.js +1 -1
  94. package/dist/src/gateway/hono/routes/config-patch/misc.js +1 -1
  95. package/dist/src/gateway/hono/routes/notes.js +2 -2
  96. package/dist/src/gateway/hono/routes/projects.js +2 -2
  97. package/dist/src/gateway/hono/routes/session-input-handler.js +1 -1
  98. package/dist/src/gateway/hono/routes/tasks.js +1 -1
  99. package/dist/src/gateway/image-generation-setup.js +1 -1
  100. package/dist/src/gateway/service.js +13 -5
  101. package/dist/src/gateway/xopc-cloud-capability-setup.js +1 -1
  102. package/dist/src/home-intelligence/host.js +2 -2
  103. package/dist/src/local-apps/service.js +1 -1
  104. package/dist/src/personal-agent/communication.js +1 -1
  105. package/dist/src/personal-agent/reply-composer.d.ts +22 -0
  106. package/dist/src/personal-agent/reply-composer.js +276 -0
  107. package/dist/src/personal-agent/reply-style.d.ts +2 -0
  108. package/dist/src/personal-agent/reply-style.js +14 -0
  109. package/dist/src/personal-agent/request-delivery.d.ts +1 -1
  110. package/dist/src/personal-agent/request-delivery.js +18 -4
  111. package/dist/src/personal-agent/request-service.js +1 -1
  112. package/dist/src/personal-agent/service.js +10 -2
  113. package/dist/src/storage/sqlite/migrations/234_task_result_reply.sql +14 -0
  114. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  115. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  116. package/dist/src/storage/sqlite/schema.js +1 -1
  117. package/dist/src/tasks/capabilities/read.js +2 -2
  118. package/dist/src/tasks/task-result-delivery-service.js +20 -4
  119. package/dist/src/tui/backends/embedded-backend.js +1 -1
  120. package/dist/src/voice/realtime/agentBroker.js +11 -2
  121. package/dist/src/voice/realtime/agentEngine.js +18 -10
  122. package/dist/src/voice/realtime/omniEngine.js +11 -2
  123. package/dist/src/voice/realtime/persona-context.js +1 -1
  124. package/dist/src/voice/realtime/playback-echo.d.ts +7 -0
  125. package/dist/src/voice/realtime/playback-echo.js +23 -1
  126. package/package.json +4 -4
  127. package/dist/gateway/static/root/assets/settings-advanced-gate-DRc9IDJf.js +0 -1
@@ -0,0 +1,14 @@
1
+ //#region src/personal-agent/reply-style.ts
2
+ const PERSONAL_REPLY_STYLE_RULES = [
3
+ "Understand the user’s last words and what the result means for them before replying. Speak as the same familiar personal assistant throughout the conversation, rather than forwarding an execution report. A natural brief opening is welcome when it connects to what they said; do not mechanically add a greeting, name, reassurance, joke, or question.",
4
+ "Use a conversational rhythm and concrete language. Start with what the user most needs now, then give enough explanation or detail to use the answer. Do not default to “Task completed”, “Here is the summary”, headings, or checklists. Use lists and tables when they genuinely make the requested information easier to use.",
5
+ "Synthesis means understanding and organizing, not always shortening. Preserve requested lists, exact text, code, translations, important sources, limitations, and partial failures. Never make an uncertain result sound confirmed just to keep the reply smooth."
6
+ ];
7
+ const PERSONAL_REPLY_EXAMPLES = `Examples of presentation, only when the evidence supports the facts:
8
+ Report: “Query complete. Scanned 28 emails and found 3 important items.”
9
+ Conversation: “Three emails are worth looking at first. The contract confirmation is the most urgent: they need your reply today. I’ve included the other two below.”
10
+ Report: “Task failed: connector authorization expired.”
11
+ Conversation: “I couldn’t finish checking the remaining emails because the connection expired. The results already retrieved are below; reconnecting will let us check the rest.”
12
+ These are examples of rhythm, not facts or fixed templates. Match the user’s language and preferences.`;
13
+ //#endregion
14
+ export { PERSONAL_REPLY_EXAMPLES, PERSONAL_REPLY_STYLE_RULES };
@@ -1,4 +1,4 @@
1
1
  /** Closed authorization waits retain their feedback marker for restart-safe delivery. */
2
2
  export declare function drainPersonalRequestContinuations(notify?: (conversationId: string, requestId: string) => void): void;
3
- /** The request itself is the durable outbox; append and delivery marker commit together. */
3
+ /** Stage personal results in the shared delivery outbox; legacy requests retain their own delivery marker. */
4
4
  export declare function drainPersonalRequestResults(notify?: (conversationId: string, requestId: string) => void): void;
@@ -9,7 +9,9 @@ import { getPersonalRequest, init_request_repository, updatePersonalRequest } fr
9
9
  import { appendTranscriptEntry, init_transcript_repository, loadLlmMessagesForSession } from "../storage/sqlite/transcript-repository.js";
10
10
  import { TaskConversationRepository } from "../tasks/task-conversation-repository.js";
11
11
  import { emitSessionTranscriptUpdate } from "../session/transcript-events.js";
12
+ import { getPersonalAgentByConversation } from "./repository.js";
12
13
  import { PersonalRequestResultSchema, renderPersonalRequestResult } from "./request-result.js";
14
+ import { enqueuePersonalReply } from "./reply-composer.js";
13
15
  //#region src/personal-agent/request-delivery.ts
14
16
  init_src();
15
17
  init_transaction();
@@ -66,7 +68,7 @@ function drainPersonalRequestContinuations(notify) {
66
68
  }, "Connection continuation feedback will retry");
67
69
  }
68
70
  }
69
- /** The request itself is the durable outbox; append and delivery marker commit together. */
71
+ /** Stage personal results in the shared delivery outbox; legacy requests retain their own delivery marker. */
70
72
  function drainPersonalRequestResults(notify) {
71
73
  const db = getSqliteDatabase();
72
74
  const rows = db.prepare(`SELECT request_id, delivery_attempts FROM personal_requests request WHERE task_id IS NOT NULL
@@ -75,6 +77,7 @@ function drainPersonalRequestResults(notify) {
75
77
  AND run.status IN ('succeeded','failed','cancelled')
76
78
  AND NOT EXISTS (SELECT 1 FROM task_runs newer WHERE newer.task_id = run.task_id
77
79
  AND newer.parent_run_id IS NULL AND newer.queued_at > run.queued_at))
80
+ AND NOT EXISTS (SELECT 1 FROM task_result_deliveries delivery WHERE json_extract(delivery.reply_payload_json, '$.requestId') = request.request_id)
78
81
  ORDER BY created_at LIMIT 50`).all(Date.now());
79
82
  for (const row of rows) try {
80
83
  const request = getPersonalRequest(row.request_id);
@@ -98,7 +101,7 @@ function drainPersonalRequestResults(notify) {
98
101
  const parsed = stored.result_json && stored.result_run_id === run.id ? PersonalRequestResultSchema.safeParse(JSON.parse(stored.result_json)) : void 0;
99
102
  const assistant = (run.conversationId ? loadLlmMessagesForSession(run.conversationId) : []).filter((message) => message.role === "assistant").at(-1);
100
103
  const fallback = assistant?.role === "assistant" ? assistant.content.filter((part) => part.type === "text").map((part) => part.text).join("\n").trim() : "";
101
- const content = parsed?.success ? renderPersonalRequestResult(parsed.data) : fallback && fallback !== "NO_REPLY" ? fallback.slice(0, 16e3) : receipt?.summary ?? "Request finished without a readable result.";
104
+ const content = parsed?.success ? renderPersonalRequestResult(parsed.data) : fallback && fallback !== "NO_REPLY" ? fallback.slice(0, 16e3) : receipt?.summary?.trim() || "Request finished without a readable result.";
102
105
  const failed = run.status !== "succeeded";
103
106
  const display = failed ? `查询未完成。\n\n${content}` : content;
104
107
  const now = Date.now();
@@ -125,7 +128,18 @@ function drainPersonalRequestResults(notify) {
125
128
  },
126
129
  createdAt: now
127
130
  });
128
- const entry = appendTranscriptEntry(request.conversationId, {
131
+ const personal = getPersonalAgentByConversation(request.conversationId);
132
+ if (personal) enqueuePersonalReply({
133
+ delivery,
134
+ objective: request.objective,
135
+ requestId: request.requestId,
136
+ report: parsed?.success ? parsed.data.summary : content,
137
+ ...parsed?.success ? { preservedText: renderPersonalRequestResult({
138
+ ...parsed.data,
139
+ summary: ""
140
+ }).trim() } : {}
141
+ });
142
+ const entry = personal ? void 0 : appendTranscriptEntry(request.conversationId, {
129
143
  role: "custom",
130
144
  customType: TASK_RESULT_DELIVERY_TYPE,
131
145
  content: display,
@@ -134,7 +148,7 @@ function drainPersonalRequestResults(notify) {
134
148
  timestamp: now
135
149
  });
136
150
  updatePersonalRequest(request, { state: run.status === "cancelled" ? "cancelled" : failed ? "failed" : "completed" });
137
- db.prepare("UPDATE personal_requests SET delivery_entry_id = ? WHERE request_id = ?").run(entry.entry_id, request.requestId);
151
+ if (entry) db.prepare("UPDATE personal_requests SET delivery_entry_id = ? WHERE request_id = ?").run(entry.entry_id, request.requestId);
138
152
  db.prepare(`UPDATE task_main_update_deliveries SET status = 'delivered', decision = 'silent',
139
153
  decision_reason = 'Personal request result delivered', updated_at = ? WHERE entry_id IN
140
154
  (SELECT entry_id FROM task_collaboration_entries WHERE task_run_id = ? AND kind IN ('result','failure','progress'))`).run(now, run.id);
@@ -126,7 +126,7 @@ function startPersonalRequest(request, accountId) {
126
126
  `Selected connector: ${request.connectorId}; xopcAccountId: ${accountId}.`,
127
127
  `Preserved query parameters: ${JSON.stringify(request.parameters)}.`,
128
128
  "Search and describe the external tools before executing read-only operations. Deliver results to the originating chat through the Task result. Do not claim no important mail if the query was incomplete."
129
- ].join("\n\n")}\n\n${PERSONAL_WORKER_RESULT_GUIDANCE}\n\nBefore finishing, call personal_request_result with the verified summary and, for mail, message items and coverage. The host delivers it directly to the main chat.`;
129
+ ].join("\n\n")}\n\n${PERSONAL_WORKER_RESULT_GUIDANCE}\n\nBefore finishing, call personal_request_result with the verified summary and, for mail, message items and coverage. The host composes the final explanation and preserves structured items in the main chat.`;
130
130
  const result = new TaskApplicationService().create(TaskCreateRequestSchema.parse({
131
131
  idempotencyKey: `personal-request:${request.requestId}`,
132
132
  title: request.objective.slice(0, 60),
@@ -4,13 +4,14 @@ import { init_config_repository, setSessionConfig } from "../storage/sqlite/conf
4
4
  import { ResponsePreferencesSchema } from "../agent-config/schema.js";
5
5
  import { init_agent_config } from "../agent-config/index.js";
6
6
  import { AgentCatalogRepository, init_repository } from "../agent-catalog/repository.js";
7
+ import { AgentCatalogService } from "../agent-catalog/service.js";
7
8
  import { createConversation, init_conversation_repository } from "../storage/sqlite/conversation-repository.js";
8
9
  import { getAvailableModels, init_providers } from "../providers/index.js";
9
10
  import { DEFAULT_PERSONAL_PREFERENCES, getPersonalAgent, personalAgentId } from "./repository.js";
10
11
  import { getModelThinking } from "../providers/model-thinking.js";
11
- import { AgentCatalogService } from "../agent-catalog/service.js";
12
12
  import { PERSONAL_COMMUNICATION_RULES, PERSONAL_RELIABILITY_RULES } from "./communication.js";
13
13
  import { PERSONAL_MAIN_TOOL_IDS } from "./policy.js";
14
+ import { PERSONAL_REPLY_EXAMPLES, PERSONAL_REPLY_STYLE_RULES } from "./reply-style.js";
14
15
  import { completePersonalOnboarding } from "./onboarding.js";
15
16
  import { z } from "zod";
16
17
  //#region src/personal-agent/service.ts
@@ -67,6 +68,8 @@ function personalInstructions(preferences) {
67
68
  RESULT_DELIVERY_RULE,
68
69
  ...PERSONAL_COMMUNICATION_RULES,
69
70
  ...PERSONAL_RELIABILITY_RULES,
71
+ ...PERSONAL_REPLY_STYLE_RULES,
72
+ PERSONAL_REPLY_EXAMPLES,
70
73
  "When creating a Task, provide a short title that names the work, a one-sentence objective, and a Markdown description with all detailed instructions. Keep the title free of checklists and long background. Preserve user requirements in the description.",
71
74
  "Adapt to the user’s current words first, then their saved preferences. If the user explicitly changes how they want you to respond in future, save it with personal_preference. Do not infer a lasting emotional trait from one conversation or claim human feelings or experiences.",
72
75
  "Give the accurate answer or next step in the order this user prefers. Acknowledge feelings only when relevant, without guessing how the user feels. Avoid formulaic reassurance, praise, pet names, emojis, or jokes unless the user welcomes them and the moment fits. Be accurate about task state, evidence, and uncertainty."
@@ -117,7 +120,12 @@ async function refreshPersonalDelegationGuidance(service, ownerId) {
117
120
  if (!nextInstructions.includes(LOCAL_READ_RULE)) nextInstructions += `\n${LOCAL_READ_RULE}`;
118
121
  if (!nextInstructions.includes(RESULT_DELIVERY_RULE)) nextInstructions += `\n${RESULT_DELIVERY_RULE}`;
119
122
  if (!nextInstructions.includes(CONNECTED_APP_RULE)) nextInstructions += `\n${CONNECTED_APP_RULE}`;
120
- for (const rule of [...PERSONAL_COMMUNICATION_RULES, ...PERSONAL_RELIABILITY_RULES]) if (!nextInstructions.includes(rule)) nextInstructions += `\n${rule}`;
123
+ for (const rule of [
124
+ ...PERSONAL_COMMUNICATION_RULES,
125
+ ...PERSONAL_RELIABILITY_RULES,
126
+ ...PERSONAL_REPLY_STYLE_RULES,
127
+ PERSONAL_REPLY_EXAMPLES
128
+ ]) if (!nextInstructions.includes(rule)) nextInstructions += `\n${rule}`;
121
129
  const toolAllowlist = agent.toolAllowlist?.includes("personal_task") ? [...new Set([...agent.toolAllowlist, ...PERSONAL_MAIN_TOOL_IDS])] : agent.toolAllowlist;
122
130
  if (nextInstructions === instructions && JSON.stringify(toolAllowlist) === JSON.stringify(agent.toolAllowlist)) return;
123
131
  try {
@@ -0,0 +1,14 @@
1
+ -- Keep artifact delivery and final prose in one durable outbox with independent progress.
2
+ ALTER TABLE task_result_deliveries ADD COLUMN reply_payload_json TEXT;
3
+ ALTER TABLE task_result_deliveries ADD COLUMN reply_status TEXT CHECK(reply_status IN ('pending','generating','ready','delivered','stale'));
4
+ ALTER TABLE task_result_deliveries ADD COLUMN reply_lease_token TEXT;
5
+ ALTER TABLE task_result_deliveries ADD COLUMN reply_lease_until INTEGER NOT NULL DEFAULT 0;
6
+ ALTER TABLE task_result_deliveries ADD COLUMN reply_text TEXT;
7
+ ALTER TABLE task_result_deliveries ADD COLUMN reply_draft_transcript_id TEXT;
8
+ ALTER TABLE task_result_deliveries ADD COLUMN reply_message_entry_id TEXT;
9
+ ALTER TABLE task_result_deliveries ADD COLUMN reply_notified_at INTEGER;
10
+ ALTER TABLE task_result_deliveries ADD COLUMN reply_attempts INTEGER NOT NULL DEFAULT 0;
11
+ ALTER TABLE task_result_deliveries ADD COLUMN reply_next_attempt_at INTEGER NOT NULL DEFAULT 0;
12
+ ALTER TABLE task_result_deliveries ADD COLUMN reply_last_error TEXT;
13
+
14
+ CREATE INDEX idx_task_result_reply_pending ON task_result_deliveries(reply_status, reply_next_attempt_at, reply_lease_until);
@@ -3,7 +3,7 @@ import type { ApplyMigrationsOptions } from './types.js';
3
3
  /** Baseline schema version applied from schema.sql on first open. */
4
4
  export declare const XOPC_DB_BASELINE_SCHEMA_VERSION = 165;
5
5
  /** Latest schema version this release supports (increment when adding migrations). */
6
- export declare const XOPC_DB_SCHEMA_VERSION = 233;
6
+ export declare const XOPC_DB_SCHEMA_VERSION = 234;
7
7
  export declare function resolveMigrationsDir(override?: string): string;
8
8
  /**
9
9
  * Apply sequential SQL migrations from (currentVersion + 1) through targetVersion.
@@ -57,7 +57,7 @@ function applySingleMigration(db, migration, discardOldScenes = false) {
57
57
  */
58
58
  function applyPendingMigrations(db, options = {}) {
59
59
  db.function("xopc_operation_id", () => currentOperationId() ?? null);
60
- const targetVersion = options.targetVersion ?? 233;
60
+ const targetVersion = options.targetVersion ?? 234;
61
61
  let currentVersion = readSchemaVersion(db);
62
62
  if (currentVersion > targetVersion) throw new DatabaseSchemaTooNewError(currentVersion, targetVersion);
63
63
  if (!options.migrationsDir && currentVersion < 165) throw new DatabaseSchemaTooOldError(currentVersion, 165);
@@ -95,7 +95,7 @@ function applyPendingMigrations(db, options = {}) {
95
95
  }
96
96
  /** Inspect schema version without mutating the database. */
97
97
  function inspectSchemaMigrationStatus(db, options = {}) {
98
- const appVersion = options.targetVersion ?? 233;
98
+ const appVersion = options.targetVersion ?? 234;
99
99
  const dbVersion = readSchemaVersion(db);
100
100
  if (dbVersion > appVersion) return {
101
101
  dbVersion,
@@ -137,7 +137,7 @@ var init_runner = __esmMin((() => {
137
137
  init_errors();
138
138
  log = createLogger("Sqlite:Migrations");
139
139
  XOPC_DB_BASELINE_SCHEMA_VERSION = 165;
140
- XOPC_DB_SCHEMA_VERSION = 233;
140
+ XOPC_DB_SCHEMA_VERSION = 234;
141
141
  }));
142
142
  //#endregion
143
143
  init_runner();
@@ -28,7 +28,7 @@ function ensureXopcDatabaseSchema(db, options = {}) {
28
28
  currentVersion = 165;
29
29
  }
30
30
  applyPendingMigrations(db, {
31
- targetVersion: 233,
31
+ targetVersion: 234,
32
32
  databasePath: fresh ? void 0 : options.databasePath
33
33
  });
34
34
  }
@@ -2,10 +2,10 @@ import { ProductReadContracts } from "../../../packages/gateway-contract/src/cap
2
2
  import { init_src } from "../../../packages/gateway-contract/src/index.js";
3
3
  import { TaskRepository, init_task_repository } from "../task-repository.js";
4
4
  import { TaskRunRepository, init_task_run_repository } from "../task-run-repository.js";
5
- import { TaskConversationRepository } from "../task-conversation-repository.js";
6
- import { TaskReadModelProjector } from "../task-read-model-projector.js";
7
5
  import { CapabilityError } from "../../capabilities/runtime/errors.js";
8
6
  import { defineReadCapability } from "../../capabilities/runtime/dispatcher.js";
7
+ import { TaskConversationRepository } from "../task-conversation-repository.js";
8
+ import { TaskReadModelProjector } from "../task-read-model-projector.js";
9
9
  import { TaskContextRepository } from "../task-context-repository.js";
10
10
  import { TaskDependencyService } from "../task-dependency-service.js";
11
11
  import { TaskCriterionReviewRepository } from "../task-criterion-review-repository.js";
@@ -3,19 +3,25 @@ import { init_logger } from "../utils/logger.js";
3
3
  import { getSqliteDatabase, init_transaction, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
4
4
  import { TaskResultDeliverySchema } from "../../packages/gateway-contract/src/task-result-delivery.js";
5
5
  import { init_src } from "../../packages/gateway-contract/src/index.js";
6
+ import { TaskRepository, init_task_repository } from "./task-repository.js";
6
7
  import { TaskRunRepository, init_task_run_repository } from "./task-run-repository.js";
8
+ import { init_request_repository, personalRequestForTask } from "../personal-agent/request-repository.js";
7
9
  import { appendTranscriptEntry, init_transcript_repository } from "../storage/sqlite/transcript-repository.js";
8
10
  import { TaskConversationRepository } from "./task-conversation-repository.js";
9
11
  import { tryParseMediaUri } from "../media/uri.js";
10
12
  import { resolveMediaBufferPath } from "../media/store.js";
11
13
  import { emitSessionTranscriptUpdate } from "../session/transcript-events.js";
14
+ import { getPersonalAgentByConversation } from "../personal-agent/repository.js";
12
15
  import { TaskResultDeliveryRepository } from "./task-result-delivery-repository.js";
16
+ import { enqueuePersonalReply } from "../personal-agent/reply-composer.js";
13
17
  import { lstat } from "node:fs/promises";
14
18
  //#region src/tasks/task-result-delivery-service.ts
15
19
  init_src();
16
20
  init_transcript_repository();
17
21
  init_transaction();
18
22
  init_logger();
23
+ init_request_repository();
24
+ init_task_repository();
19
25
  init_task_run_repository();
20
26
  const log = createLogger("TaskResultDelivery");
21
27
  async function materializedArtifact(item) {
@@ -86,21 +92,31 @@ var TaskResultDeliveryService = class {
86
92
  status: deliverables.length > 0 && available === 0 ? "failed" : available < deliverables.length ? "partial" : delivery.outcome.status
87
93
  };
88
94
  const statusText = db.prepare("SELECT locale FROM tasks WHERE task_id = ?").get(delivery.taskId).locale?.startsWith("zh") === true ? `${delivery.taskTitle}:${available === deliverables.length ? "成果已生成" : available > 0 ? "部分成果已生成" : "成果暂不可用"}。` : `${delivery.taskTitle}: ${available === deliverables.length ? "Results ready" : available > 0 ? "Partial results ready" : "Results unavailable"}.`;
89
- const content = delivery.text ? `${statusText}\n\n${delivery.text}` : statusText;
95
+ const personal = getPersonalAgentByConversation(delivery.conversationId);
96
+ if (personal && !personalRequestForTask(delivery.taskId)) enqueuePersonalReply({
97
+ delivery: {
98
+ ...delivery,
99
+ outcome
100
+ },
101
+ report: delivery.text?.trim() || delivery.outcome.summary?.trim() || statusText,
102
+ objective: new TaskRepository().get(delivery.taskId)?.contract?.objective ?? delivery.taskTitle
103
+ });
104
+ const content = personal ? statusText : delivery.text ? `${statusText}\n\n${delivery.text}` : statusText;
90
105
  const details = {
91
106
  ...delivery,
92
107
  outcome
93
108
  };
94
- const entry = appendTranscriptEntry(delivery.conversationId, {
109
+ if (personal) delete details.text;
110
+ const entry = !personal || deliverables.length ? appendTranscriptEntry(delivery.conversationId, {
95
111
  role: "custom",
96
112
  customType: "task_result_delivery",
97
113
  content,
98
114
  display: true,
99
115
  details,
100
116
  timestamp: Date.now()
101
- });
117
+ }) : void 0;
102
118
  db.prepare(`UPDATE task_result_deliveries SET status = 'delivered', payload_json = ?,
103
- message_entry_id = ?, delivered_at = ? WHERE delivery_id = ?`).run(JSON.stringify(details), entry.entry_id, Date.now(), row.delivery_id);
119
+ message_entry_id = ?, delivered_at = ? WHERE delivery_id = ?`).run(JSON.stringify(details), entry?.entry_id ?? null, Date.now(), row.delivery_id);
104
120
  return true;
105
121
  })) continue;
106
122
  }
@@ -10,6 +10,7 @@ import { getSessionMetadata } from "../../storage/sqlite/session-read-repository
10
10
  import { init_sqlite } from "../../storage/sqlite/index.js";
11
11
  import { normalizeAgentId } from "../../agent-catalog/id.js";
12
12
  import { init_agent_scope, listAgentEntries } from "../../agent/agent-scope.js";
13
+ import { AgentCatalogService } from "../../agent-catalog/service.js";
13
14
  import { init_paths } from "../../config/paths.js";
14
15
  import { getAgentDefaultModelRef, init_schema } from "../../config/schema.js";
15
16
  import { init_agent_session_key, resolveAgentIdFromConversationId } from "../../routing/agent-session-key.js";
@@ -22,7 +23,6 @@ import { buildSessionTimeline } from "../../session/transcript-outline.js";
22
23
  import { transcriptRowsToClientHistory } from "../../session/client-history.js";
23
24
  import { createCreateShareTool, isShareToolAvailable } from "../../agent/tools/create-share-tool.js";
24
25
  import { evictEmbeddedSessionRunner } from "../../agent/embedded/session-runner.js";
25
- import { AgentCatalogService } from "../../agent-catalog/service.js";
26
26
  import { SessionIndex } from "../../session/manager.js";
27
27
  import "../../session/index.js";
28
28
  import { parseModelRef } from "../../agent/models/selection.js";
@@ -70,7 +70,12 @@ var DurableVoiceAgentBroker = class {
70
70
  return {
71
71
  taskId: row.id,
72
72
  runId,
73
- events: this.events(row.id, runId, afterSeq, input.signal)
73
+ events: this.events(row.id, runId, afterSeq, input.signal, [
74
+ "completed",
75
+ "cancelled",
76
+ "failed",
77
+ "interrupted"
78
+ ].includes(row.status))
74
79
  };
75
80
  }
76
81
  async cancel(taskId) {
@@ -80,7 +85,7 @@ var DurableVoiceAgentBroker = class {
80
85
  this.runByTask.delete(taskId);
81
86
  return true;
82
87
  }
83
- async *events(taskId, runId, afterSeq, signal) {
88
+ async *events(taskId, runId, afterSeq, signal, ended) {
84
89
  const queued = [];
85
90
  let wake;
86
91
  const push = (event) => {
@@ -96,6 +101,10 @@ var DurableVoiceAgentBroker = class {
96
101
  };
97
102
  signal.addEventListener("abort", abort, { once: true });
98
103
  try {
104
+ if (ended && !subscription.initial.some((delivery) => {
105
+ const event = eventData(delivery);
106
+ return event?.type === "run_end" || event?.type === "stream_end";
107
+ })) throw new Error("Completed voice task event history is unavailable");
99
108
  while (!signal.aborted) {
100
109
  if (!queued.length) await new Promise((resolve) => {
101
110
  wake = resolve;
@@ -3,7 +3,7 @@ import { init_logger } from "../../utils/logger.js";
3
3
  import { speakStream } from "../tts/speak-core.js";
4
4
  import { AudioPlaybackWindow } from "./audio-playback-window.js";
5
5
  import { TurnCoordinator } from "./turnPolicy.js";
6
- import { isLikelyPlaybackEcho } from "./playback-echo.js";
6
+ import { PlaybackEchoCandidates } from "./playback-echo.js";
7
7
  import { PcmFrameBuffer } from "./pcmFrameBuffer.js";
8
8
  import { SpeakableSegmenter } from "./speakable-segmenter.js";
9
9
  import crypto from "node:crypto";
@@ -36,6 +36,7 @@ function createAgentVoiceEngine(options) {
36
36
  let finalCount = 0;
37
37
  let committing = false;
38
38
  let recentPlayback;
39
+ const echoCandidates = new PlaybackEchoCandidates();
39
40
  const finalizedUtterances = /* @__PURE__ */ new Set();
40
41
  let pendingTurn;
41
42
  let taskUpdateQueued = false;
@@ -89,6 +90,11 @@ function createAgentVoiceEngine(options) {
89
90
  function cancelActiveResponse(reason) {
90
91
  const response = activeResponse;
91
92
  if (!response) return false;
93
+ if (response.audioStarted && response.audibleText) recentPlayback = {
94
+ responseId: response.id,
95
+ text: response.audibleText,
96
+ expiresAt: Date.now() + PLAYBACK_ECHO_TAIL_MS
97
+ };
92
98
  activeResponse = void 0;
93
99
  response.abortController.abort(reason);
94
100
  send("response.cancelled", {
@@ -267,13 +273,11 @@ function createAgentVoiceEngine(options) {
267
273
  conversationId: claim.request.conversationId,
268
274
  expectedTranscriptId: claim.conversationSessionId,
269
275
  signal: response.abortController.signal,
270
- ...updateClientMessageId ? {
271
- clientMessageId: updateClientMessageId,
272
- origin: {
273
- type: "system",
274
- source: "task_update"
275
- }
276
- } : {}
276
+ clientMessageId: updateClientMessageId ?? `voice:${claim.conversationSessionId}:${claim.sessionId}:${inputGeneration}:${turnId}`,
277
+ ...updateClientMessageId ? { origin: {
278
+ type: "system",
279
+ source: "task_update"
280
+ } } : {}
277
281
  });
278
282
  response.taskId = task.taskId;
279
283
  send("task.created", {
@@ -438,6 +442,7 @@ function createAgentVoiceEngine(options) {
438
442
  if (event.type === "ready" || event.type === "usage") return;
439
443
  if (event.type === "error") {
440
444
  turn.reset();
445
+ echoCandidates.clear();
441
446
  log.warn({
442
447
  err: event.error,
443
448
  sessionId: claim.sessionId,
@@ -454,6 +459,7 @@ function createAgentVoiceEngine(options) {
454
459
  if (muted || finalizedUtterances.has(event.utteranceId)) return;
455
460
  if (event.type === "speech_started") {
456
461
  lastUserSpeechAt = Date.now();
462
+ echoCandidates.remember(event.utteranceId, activeResponse?.audioStarted ? activeResponse.audibleText : recentPlayback && recentPlayback.expiresAt >= Date.now() ? recentPlayback.text : void 0);
457
463
  if (claim.request.purpose === "conversation") {
458
464
  if (pendingTurn) {
459
465
  pendingTurn.cancelled = true;
@@ -490,11 +496,11 @@ function createAgentVoiceEngine(options) {
490
496
  responseId: activeResponse.id,
491
497
  text: activeResponse.audibleText
492
498
  } : recentPlayback && recentPlayback.expiresAt >= Date.now() ? recentPlayback : void 0;
493
- if (claim.request.purpose === "conversation" && playback && isLikelyPlaybackEcho(text, playback.text)) {
499
+ if (claim.request.purpose === "conversation" && echoCandidates.matches(event.utteranceId, text, playback?.text)) {
494
500
  bufferFinal(event.utteranceId, "");
495
501
  log.debug({
496
502
  sessionId: claim.sessionId,
497
- responseId: playback.responseId,
503
+ responseId: playback?.responseId,
498
504
  transcriptCharacters: text.length
499
505
  }, "Ignored finalized transcription matching recent voice playback");
500
506
  return;
@@ -517,6 +523,7 @@ function createAgentVoiceEngine(options) {
517
523
  }
518
524
  function discardInput() {
519
525
  turn.reset();
526
+ echoCandidates.clear();
520
527
  pendingTurn = void 0;
521
528
  inputGeneration += 1;
522
529
  queuedTurns = 0;
@@ -623,6 +630,7 @@ function createAgentVoiceEngine(options) {
623
630
  if (closing) return closing;
624
631
  closed = true;
625
632
  turn.reset();
633
+ echoCandidates.clear();
626
634
  cancelActiveResponse("session_closed");
627
635
  sttAbort.abort("session_closed");
628
636
  sttSession?.abort("session_closed");
@@ -2,7 +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
+ import { PlaybackEchoCandidates } from "./playback-echo.js";
6
6
  import { PcmFrameBuffer } from "./pcmFrameBuffer.js";
7
7
  import crypto from "node:crypto";
8
8
  import WebSocket from "ws";
@@ -45,6 +45,7 @@ function createOmniVoiceEngine(options) {
45
45
  let failed = false;
46
46
  let platformRequestId;
47
47
  let active;
48
+ const echoCandidates = new PlaybackEchoCandidates();
48
49
  let recentPlayback;
49
50
  let waitingForCancellation;
50
51
  let responseCreateQueued = false;
@@ -147,6 +148,10 @@ function createOmniVoiceEngine(options) {
147
148
  const cancel = (reason) => {
148
149
  const response = active;
149
150
  if (!response) return false;
151
+ if (response.audio && response.text) recentPlayback = {
152
+ text: response.text,
153
+ expiresAt: Date.now() + PLAYBACK_ECHO_TAIL_MS
154
+ };
150
155
  active = void 0;
151
156
  response.abort.abort(reason);
152
157
  response.release();
@@ -234,6 +239,7 @@ function createOmniVoiceEngine(options) {
234
239
  failed = true;
235
240
  clearTimeout(responseStartTimer);
236
241
  turn.reset();
242
+ echoCandidates.clear();
237
243
  responseCreateQueued = false;
238
244
  log.warn({
239
245
  sessionId: options.callId,
@@ -259,6 +265,7 @@ function createOmniVoiceEngine(options) {
259
265
  function discardInput() {
260
266
  clearTimeout(responseStartTimer);
261
267
  turn.reset();
268
+ echoCandidates.clear();
262
269
  responseCreateQueued = false;
263
270
  speaking.clear();
264
271
  turnSettled = false;
@@ -358,6 +365,7 @@ function createOmniVoiceEngine(options) {
358
365
  return;
359
366
  }
360
367
  if (recorded.has(String(event.item_id)) || pendingInputs.has(String(event.item_id))) return;
368
+ echoCandidates.remember(String(event.item_id), active?.audio ? active.text : recentPlayback && recentPlayback.expiresAt >= Date.now() ? recentPlayback.text : void 0);
361
369
  inputBlocked = false;
362
370
  pendingInputs.add(String(event.item_id));
363
371
  clearTimeout(responseStartTimer);
@@ -381,7 +389,7 @@ function createOmniVoiceEngine(options) {
381
389
  speaking.delete(event.item_id);
382
390
  const text = event.transcript.trim();
383
391
  const playbackText = active?.audio ? active.text : recentPlayback && recentPlayback.expiresAt >= Date.now() ? recentPlayback.text : void 0;
384
- if (playbackText && isLikelyPlaybackEcho(text, playbackText)) {
392
+ if (echoCandidates.matches(event.item_id, text, playbackText)) {
385
393
  turn.final(event.item_id, "");
386
394
  log.debug({
387
395
  sessionId: options.callId,
@@ -582,6 +590,7 @@ function createOmniVoiceEngine(options) {
582
590
  clearTimeout(uploadTimer);
583
591
  clearTimeout(responseStartTimer);
584
592
  turn.reset();
593
+ echoCandidates.clear();
585
594
  speaking.clear();
586
595
  inputQueue = [];
587
596
  queuedInputBytes = 0;
@@ -1,6 +1,6 @@
1
1
  import { init_agent_scope, resolveAgentProfileDir } from "../../agent/agent-scope.js";
2
- import { resolveEffectiveAgentProfileForSession } from "../../config/agent-profile.js";
3
2
  import { DEFAULT_SOUL_FILENAME } from "../../agent/context/workspace.js";
3
+ import { resolveEffectiveAgentProfileForSession } from "../../config/agent-profile.js";
4
4
  import { loadProfileBootstrapFiles } from "../../agent/bootstrap/load-bootstrap-files.js";
5
5
  import { join } from "node:path";
6
6
  import { statSync } from "node:fs";
@@ -1,2 +1,9 @@
1
1
  /** Conservatively identifies finalized ASR that is already in audible assistant output. */
2
2
  export declare function isLikelyPlaybackEcho(transcript: string, assistantText: string): boolean;
3
+ /** Retains the playback at speech onset while a provider finishes delayed ASR. */
4
+ export declare class PlaybackEchoCandidates {
5
+ private readonly candidates;
6
+ remember(utteranceId: string, text: string | undefined): void;
7
+ matches(utteranceId: string, transcript: string, currentPlayback?: string): boolean;
8
+ clear(): void;
9
+ }
@@ -28,5 +28,27 @@ function isLikelyPlaybackEcho(transcript, assistantText) {
28
28
  if (heard.length < (containsCjk ? 6 : 10)) return false;
29
29
  return closestSubstringEditDistance(heard, spoken) / Array.from(heard).length <= .22;
30
30
  }
31
+ /** Retains the playback at speech onset while a provider finishes delayed ASR. */
32
+ var PlaybackEchoCandidates = class {
33
+ candidates = /* @__PURE__ */ new Map();
34
+ remember(utteranceId, text) {
35
+ if (!text || this.candidates.has(utteranceId)) return;
36
+ const now = Date.now();
37
+ for (const [id, candidate] of this.candidates) if (candidate.expiresAt < now) this.candidates.delete(id);
38
+ if (this.candidates.size >= 256) this.candidates.delete(this.candidates.keys().next().value);
39
+ this.candidates.set(utteranceId, {
40
+ text: text.slice(-8e3),
41
+ expiresAt: now + 3e4
42
+ });
43
+ }
44
+ matches(utteranceId, transcript, currentPlayback) {
45
+ const candidate = this.candidates.get(utteranceId);
46
+ this.candidates.delete(utteranceId);
47
+ return Boolean(currentPlayback && isLikelyPlaybackEcho(transcript, currentPlayback) || candidate && candidate.expiresAt >= Date.now() && isLikelyPlaybackEcho(transcript, candidate.text));
48
+ }
49
+ clear() {
50
+ this.candidates.clear();
51
+ }
52
+ };
31
53
  //#endregion
32
- export { isLikelyPlaybackEcho };
54
+ export { PlaybackEchoCandidates, isLikelyPlaybackEcho };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xopcai/xopc",
3
- "version": "0.0.363",
3
+ "version": "0.0.364",
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",
@@ -161,12 +161,12 @@
161
161
  "vitepress": "^1.6.4",
162
162
  "vitest": "4.1.5",
163
163
  "vue": "^3.5.35",
164
- "@xopcai/computer-control-contract": "0.1.0",
164
+ "@xopcai/browser-control-contract": "0.1.0",
165
165
  "@xopcai/endpoint-tools-client": "0.0.0",
166
166
  "@xopcai/endpoint-tools-protocol": "0.0.0",
167
- "@xopcai/browser-control-contract": "0.1.0",
168
- "@xopcai/gateway-contract": "0.0.0",
167
+ "@xopcai/computer-control-contract": "0.1.0",
169
168
  "@xopcai/realtime-client": "0.0.0",
169
+ "@xopcai/gateway-contract": "0.0.0",
170
170
  "@xopcai/realtime-protocol": "0.0.0"
171
171
  },
172
172
  "scripts": {
@@ -1 +0,0 @@
1
- import{hn as e}from"./index-BYNmHn-z.js";function t(t){let{children:n,fallback:r}=t,i=r===void 0?null:r;return e()?n:i}export{t};