@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.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-DkcXyPH0.js → agent-avatar-dicebear-7Ct-ZlfL.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-B-l5AaYN.js → agent-defaults-settings-panel-wYj-iyqv.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-plugin-dialog-BY6tPYOK.js → agent-plugin-dialog-C9HYMhnJ.js} +1 -1
- package/dist/gateway/static/root/assets/{agents-CdSEoeje.js → agents-hPkFQ81z.js} +3 -3
- package/dist/gateway/static/root/assets/{app-management-settings-panel-BtD53cfe.js → app-management-settings-panel-CjfsIn9A.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-BAweWgX_.js → appearance-settings-qqHZ15GU.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-CS6-hrIB.js → apps-page-Ba2VkQk6.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-CtE8r9Y2.js → automations-page-Bt8Q58ut.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-workspace-pnMgR4Da.js → automations-workspace-Ch0ViAJW.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-D5hf6WcP.js → block-editor-ClRFbHP7.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-PZDQlXH4.js → browser-automations-page-BjMub7JM.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-settings-page-2YDV-ZWt.js → browser-settings-page-CYrPA-Fp.js} +1 -1
- package/dist/gateway/static/root/assets/{capabilities-page-CB-iX3Fy.js → capabilities-page-dW6FXtOy.js} +2 -2
- package/dist/gateway/static/root/assets/{capabilities-settings-panel-BCTuWNaW.js → capabilities-settings-panel-gZ8auLwa.js} +1 -1
- package/dist/gateway/static/root/assets/{capability-header-actions--aC5XB9E.js → capability-header-actions-L1NvoOUG.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-JnAQP1bt.js → channels-Dx5T2G73.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-BFZEpvzn.js → chat-terminal-dock-DsCpKPBe.js} +1 -1
- package/dist/gateway/static/root/assets/{computer-settings-page-Dc9ooEM-.js → computer-settings-page-6FWwxYLe.js} +1 -1
- package/dist/gateway/static/root/assets/{connector-service-page-1_QB5D-t.js → connector-service-page-mtH9mEBf.js} +1 -1
- package/dist/gateway/static/root/assets/{connectors-page-Um_Jy5zh.js → connectors-page-DgDLaws0.js} +1 -1
- package/dist/gateway/static/root/assets/{date-picker-C9ZqTPui.js → date-picker-BDcwEaIx.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-DBz0Pmyr.js → dependency-picker-B_fPQBTM.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-BLtp2MmX.js → desktop-pet-CJrO9Vd_.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-BNGbqwec.js → desktop-pet-settings-DYAfri9w.js} +1 -1
- package/dist/gateway/static/root/assets/{device-pairing-wizard-7rdo-A7d.js → device-pairing-wizard-CqRys2R8.js} +1 -1
- package/dist/gateway/static/root/assets/{directory-picker-path-field-BnilGgwM.js → directory-picker-path-field-D7_qSxNZ.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-DsrVktRN.js → extension-page-D1zBmd8F.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-jeb0ytRK.js → extension-settings-page-CTR8AjCG.js} +1 -1
- package/dist/gateway/static/root/assets/{fallback-models-bZcxkEDz.js → fallback-models-Chhv4Nfv.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-A9SzLsGp.js → gateway-settings-BVfz4Tla.js} +1 -1
- package/dist/gateway/static/root/assets/{home-page-CnHzaH9K.js → home-page-BF_9C9yW.js} +1 -1
- package/dist/gateway/static/root/assets/{imports-page-CzpsV6Vz.js → imports-page-9EnECtcl.js} +1 -1
- package/dist/gateway/static/root/assets/{index-BYNmHn-z.js → index-BpXej9jJ.js} +5 -5
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-D90Dvlwo.js → keyboard-shortcuts-settings-MC7mG2h2.js} +1 -1
- package/dist/gateway/static/root/assets/{local-app-workbench-page-_9IKMkV8.js → local-app-workbench-page-CytNu0-_.js} +1 -1
- package/dist/gateway/static/root/assets/{local-apps-page-D6gejARs.js → local-apps-page-c3HncHZ3.js} +1 -1
- package/dist/gateway/static/root/assets/{logs-page--t8d-oLb.js → logs-page-D4aXtitd.js} +1 -1
- package/dist/gateway/static/root/assets/{management-settings-DVDmXzTg.js → management-settings-CRzWi_iJ.js} +1 -1
- package/dist/gateway/static/root/assets/{note-detail-panel-DuZAEfU1.js → note-detail-panel-BgbByI-4.js} +3 -3
- package/dist/gateway/static/root/assets/{notes-page-CgWNESGp.js → notes-page-R2BX5XCE.js} +1 -1
- package/dist/gateway/static/root/assets/{page-context-capture-button-Z72eiQZn.js → page-context-capture-button-RBp1QK0F.js} +1 -1
- package/dist/gateway/static/root/assets/{personal-page-BtMatL9a.js → personal-page-F0jTBWva.js} +1 -1
- package/dist/gateway/static/root/assets/{product-open-page-w5Hdf0hQ.js → product-open-page-BtDK68qb.js} +1 -1
- package/dist/gateway/static/root/assets/{project-detail-page-CP6I3ZCV.js → project-detail-page-DmQEm0yH.js} +1 -1
- package/dist/gateway/static/root/assets/{projects-page-DYs6XrrK.js → projects-page-CrLQvkHX.js} +1 -1
- package/dist/gateway/static/root/assets/{remote-access-hub-DjYoYAEw.js → remote-access-hub-C25_hQAd.js} +1 -1
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-Qar_DXLv.js → runtime-tools-settings-panel-B0c120iI.js} +1 -1
- package/dist/gateway/static/root/assets/{scenes-page-BSIcDCa-.js → scenes-page-DM64yMnx.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-C_cJLGgF.js → schema-form-DQpQc9tH.js} +1 -1
- package/dist/gateway/static/root/assets/{sessions-page-BBV5Z24a.js → sessions-page-DvDO8LtA.js} +1 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-BNhgbmcX.js +1 -0
- package/dist/gateway/static/root/assets/{settings-page-F2vGXBw7.js → settings-page-GgBEp6O7.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-DJxbjUOg.js → setup-status-panel-10bMi6yL.js} +1 -1
- package/dist/gateway/static/root/assets/{shares-settings-CJa6FBpP.js → shares-settings-DJ1ME-XM.js} +1 -1
- package/dist/gateway/static/root/assets/{skill-api-C340XD9d.js → skill-api-1Puv6wOz.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page-C4lF94m5.js → skills-page-D52pEr5x.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-CmJUu3lb.js → system-settings-panel--yV67a8L.js} +1 -1
- package/dist/gateway/static/root/assets/{task-detail-page-De-Rr8W6.js → task-detail-page-D-tcS2bS.js} +1 -1
- package/dist/gateway/static/root/assets/{tunnel-api-Bl4nrCuE.js → tunnel-api-COLdiP_E.js} +1 -1
- package/dist/gateway/static/root/assets/{usage-page-QZEAaa8n.js → usage-page-CtM5IfrL.js} +1 -1
- package/dist/gateway/static/root/assets/{user-model-page-Crh2oNpZ.js → user-model-page-CSh61kZ5.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-9jnjEAaz.js → work-discovery-overlay-C9u7tuEc.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BAcPxdcT.js → workflow-run-setup-panel-BMOBbf33.js} +1 -1
- package/dist/gateway/static/root/assets/{workflows-page-D8Zm-ooa.js → workflows-page-BrD9vXqO.js} +1 -1
- package/dist/gateway/static/root/index.html +1 -1
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-manager.js +1 -1
- package/dist/src/agent/background-review/run-background-review.js +1 -1
- package/dist/src/agent/external-tools/endpoint-provider.js +2 -2
- package/dist/src/agent/service.js +1 -1
- package/dist/src/agent/skills/index.js +1 -1
- package/dist/src/agent/skills/managed-store.js +1 -1
- package/dist/src/agent/skills/skill-manage-ops.js +1 -1
- package/dist/src/agent/tools/xopc-use-tool.js +2 -2
- package/dist/src/agent-catalog/capabilities.js +2 -2
- package/dist/src/agent-catalog/migrations/legacy-config-cutover.js +1 -1
- package/dist/src/agent-catalog/repository.js +1 -1
- package/dist/src/automations/capabilities/drafts.js +1 -1
- package/dist/src/bootstrap/application-state.js +1 -1
- package/dist/src/capabilities/runtime/dispatcher.js +1 -1
- package/dist/src/capabilities/runtime/product.js +1 -1
- package/dist/src/cli/commands/agents.js +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
- package/dist/src/cli/commands/models.js +1 -1
- package/dist/src/cli/commands/onboard/model.js +2 -2
- package/dist/src/cli/utils/init-workspace-core.js +1 -1
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/extensions/agent-plugins/validation.js +1 -1
- package/dist/src/gateway/agents-admin.js +2 -2
- package/dist/src/gateway/hono/routes/agents.js +1 -1
- package/dist/src/gateway/hono/routes/config-patch/misc.js +1 -1
- package/dist/src/gateway/hono/routes/notes.js +2 -2
- package/dist/src/gateway/hono/routes/projects.js +2 -2
- package/dist/src/gateway/hono/routes/session-input-handler.js +1 -1
- package/dist/src/gateway/hono/routes/tasks.js +1 -1
- package/dist/src/gateway/image-generation-setup.js +1 -1
- package/dist/src/gateway/service.js +13 -5
- package/dist/src/gateway/xopc-cloud-capability-setup.js +1 -1
- package/dist/src/home-intelligence/host.js +2 -2
- package/dist/src/local-apps/service.js +1 -1
- package/dist/src/personal-agent/communication.js +1 -1
- package/dist/src/personal-agent/reply-composer.d.ts +22 -0
- package/dist/src/personal-agent/reply-composer.js +276 -0
- package/dist/src/personal-agent/reply-style.d.ts +2 -0
- package/dist/src/personal-agent/reply-style.js +14 -0
- package/dist/src/personal-agent/request-delivery.d.ts +1 -1
- package/dist/src/personal-agent/request-delivery.js +18 -4
- package/dist/src/personal-agent/request-service.js +1 -1
- package/dist/src/personal-agent/service.js +10 -2
- package/dist/src/storage/sqlite/migrations/234_task_result_reply.sql +14 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/tasks/capabilities/read.js +2 -2
- package/dist/src/tasks/task-result-delivery-service.js +20 -4
- package/dist/src/tui/backends/embedded-backend.js +1 -1
- package/dist/src/voice/realtime/agentBroker.js +11 -2
- package/dist/src/voice/realtime/agentEngine.js +18 -10
- package/dist/src/voice/realtime/omniEngine.js +11 -2
- package/dist/src/voice/realtime/persona-context.js +1 -1
- package/dist/src/voice/realtime/playback-echo.d.ts +7 -0
- package/dist/src/voice/realtime/playback-echo.js +23 -1
- package/package.json +4 -4
- 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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
|
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 [
|
|
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 =
|
|
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 ??
|
|
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 ??
|
|
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 =
|
|
140
|
+
XOPC_DB_SCHEMA_VERSION = 234;
|
|
141
141
|
}));
|
|
142
142
|
//#endregion
|
|
143
143
|
init_runner();
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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" &&
|
|
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
|
|
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 {
|
|
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 (
|
|
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.
|
|
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/
|
|
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/
|
|
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};
|