@xopcai/xopc 0.0.221 → 0.0.223
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/{activity-store-NItGQmvR.js → activity-store--GBciH10.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-B8SBTGg3.js → agent-avatar-dicebear-C93T3FLD.js} +1 -1
- package/dist/gateway/static/root/assets/agents-Ba4XF0Im.js +68 -0
- package/dist/gateway/static/root/assets/{api-53ZXsuyr.js → api-HoIz_ija.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-BtmPXaqu.js → app-management-settings-panel-Bhl8Xluk.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-t7HoJyNN.js → appearance-settings-CywEN-kG.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-BZY09nuJ.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-D8o8CP9Y.js → archive-plugin-CteCVsBP.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-CsLXzas8.js → attachment-load-CbNE_EPd.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-D-zgoAKz.js → automations-page-Bcqk6tOx.js} +1 -1
- package/dist/gateway/static/root/assets/automations-workspace-DtI6vsMf.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-B-MjP_22.js → binary-plugins-M_i3WS5M.js} +1 -1
- package/dist/gateway/static/root/assets/block-editor-eg9LEH2o.js +184 -0
- package/dist/gateway/static/root/assets/browser-settings-page-kKwbvzjj.js +8 -0
- package/dist/gateway/static/root/assets/{browser-workflow-inputs-Bi6RSSpD.js → browser-workflow-inputs-1UZ0vnIy.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-workflows-page-EEFHUm1i.js → browser-workflows-page-D8ATQqZ5.js} +2 -2
- package/dist/gateway/static/root/assets/capabilities-settings-panel-CZRcis8B.js +7 -0
- package/dist/gateway/static/root/assets/{capability-presets-api-pC2fno7r.js → capability-presets-api-yNqTIuyd.js} +1 -1
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-CgdRkwnk.js +3 -0
- package/dist/gateway/static/root/assets/{channel-recipient-api-B5Szgu_z.js → channel-recipient-api-krezGsKc.js} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-DETCY0Ku.js +1 -0
- package/dist/gateway/static/root/assets/channels-status-swr-DMxB7slL.js +1 -0
- package/dist/gateway/static/root/assets/{chat-terminal-dock-DT-OFl4r.js → chat-terminal-dock-DQ5bB4Su.js} +2 -2
- package/dist/gateway/static/root/assets/connectors-page-D9D92u28.js +2 -0
- package/dist/gateway/static/root/assets/{cron-expression-MZ_AJqrm.js → cron-expression-CEwPkdhh.js} +1 -1
- package/dist/gateway/static/root/assets/{date-picker-Bv9hZLuD.js → date-picker-DDAxTir8.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-D-6oO_Vk.js → dependency-picker-CYVoSZwI.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-CiI5caKj.js → desktop-pet-CrNKfW_c.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-DhkSOy4K.js → desktop-pet-settings-C06PaB46.js} +1 -1
- package/dist/gateway/static/root/assets/{directory-picker-path-field-C4s4Pz8I.js → directory-picker-path-field--D7laeLF.js} +1 -1
- package/dist/gateway/static/root/assets/dist-DaxPZj9e.js +1 -0
- package/dist/gateway/static/root/assets/{dist-DY4Af0fY.js → dist-J_ySZvz0.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-DE7J1-6W.js → extension-debug-page-Dn4Px9KQ.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-DSnACD7E.js → extension-page-DzLf3fMQ.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-Dk7k2Pok.js → extension-settings-page-DIchwtvB.js} +1 -1
- package/dist/gateway/static/root/assets/floating-ui.dom-B7uQOZZW.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-config-api-IOA1OlHx.js → gateway-config-api-SmTGLhqK.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-swr-BzTbnu4O.js → gateway-config-swr-yUxMMVjK.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-FX7QsgSQ.js → gateway-settings-yCDkOYzA.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-startup-retry-B2zpkNvi.js → gateway-startup-retry-9mrsL84j.js} +1 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-CquIULHX.js +1 -0
- package/dist/gateway/static/root/assets/home-page-COSGBh4c.js +1 -0
- package/dist/gateway/static/root/assets/{image-generation-api-BXRjz-iY.js → image-generation-api-CgFAGDi8.js} +1 -1
- package/dist/gateway/static/root/assets/index-5XkDWH_Q.css +2 -0
- package/dist/gateway/static/root/assets/index-wnDPGRTi.js +74 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BjYiM4xq.js → keyboard-shortcuts-settings-DI2To7yl.js} +1 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-CAF5SZXv.js +2 -0
- package/dist/gateway/static/root/assets/{local-apps-page-7DHvwSgA.js → local-apps-page-CSEJsQ1F.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-iKjqemWG.js → locale-store-CPylQjH9.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-D1KFPHL1.js +2 -0
- package/dist/gateway/static/root/assets/{management-settings-BSxppo9Z.js → management-settings-CgyaIyuk.js} +1 -1
- package/dist/gateway/static/root/assets/{markdown-split-D4oDjZtl.js → markdown-split-paG9qap9.js} +1 -1
- package/dist/gateway/static/root/assets/markdown-view-CUsob4Pz.js +69 -0
- package/dist/gateway/static/root/assets/{media-plugins-BPNTGROA.js → media-plugins-0Q-c_QIy.js} +1 -1
- package/dist/gateway/static/root/assets/messages-B82nsi9k.js +3 -0
- package/dist/gateway/static/root/assets/notes-api-CfKZYrBC.js +1 -0
- package/dist/gateway/static/root/assets/{notes-page-DU5tKPTq.js → notes-page-Bvb-ycX_.js} +1 -1
- package/dist/gateway/static/root/assets/notes-workbench-DelxGBof.js +4 -0
- package/dist/gateway/static/root/assets/oauth-authorization-window-Cdolr2wE.js +1 -0
- package/dist/gateway/static/root/assets/{popover-select-BzdnTy3h.js → popover-select-CccGri5Y.js} +1 -1
- package/dist/gateway/static/root/assets/{product-open-page-CPoQvD6P.js → product-open-page-DrvBwAns.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-C2EnYXEQ.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-BiMPU6Oa.js +1 -0
- package/dist/gateway/static/root/assets/{registry-api-Cx3JxXSL.js → registry-api-B_5D8epF.js} +1 -1
- package/dist/gateway/static/root/assets/remote-access-hub-CpbNivVs.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-xqnWcvIv.js → runtime-tools-settings-panel-DKjdN4Lr.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-CuIDPYu6.js → schema-form-DskqaNqC.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-BxKtzsKI.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-BfBKMMUx.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-DkAv9ncE.js → settings-form-section-CZlMvHA9.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-loading-skeleton-BJUQiEKb.js → settings-loading-skeleton-DS6lcAg9.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-KHRbLwmw.js → settings-page-oE5J9tsV.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-C8jd20Db.js → setup-status-panel-C7oFCIqD.js} +1 -1
- package/dist/gateway/static/root/assets/share-preview-page-CPFAj8NE.js +2 -0
- package/dist/gateway/static/root/assets/shares-settings-B46DWZK4.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-CyKolUZ-.js +1 -0
- package/dist/gateway/static/root/assets/{skills-page-CKhR9J81.js → skills-page-Bh4gFi8e.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-AYX-e5sD.js → skills-page.utils-BQaUWP9z.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-IIF-K9ML.js → system-settings-panel-COAZoh_O.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-CXkhprf-.js +1 -0
- package/dist/gateway/static/root/assets/{theme-store-BOD2D0dJ.js → theme-store-BN-32Fz5.js} +1 -1
- package/dist/gateway/static/root/assets/{use-autosave-DcktF1j1.js → use-autosave-BAkToU15.js} +1 -1
- package/dist/gateway/static/root/assets/user-context-page-Br5wgARM.js +1 -0
- package/dist/gateway/static/root/assets/{voice-api-key-field-BMkNTHHE.js → voice-api-key-field-CC4z9-ux.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-DVoiXekA.js → work-discovery-overlay-dLVUYSSE.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-Cw4LQjuM.js → workflow-run-setup-panel-BYU5-0KF.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-CJ24a1HM.js +5 -0
- package/dist/gateway/static/root/index.html +15 -14
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-instance-gateway.d.ts +3 -0
- package/dist/src/agent/agent-manager.d.ts +3 -0
- package/dist/src/agent/agent-manager.js +34 -28
- package/dist/src/agent/embedded/index.d.ts +1 -1
- package/dist/src/agent/embedded/index.js +2 -2
- package/dist/src/agent/embedded/model-runtime.js +1 -1
- package/dist/src/agent/embedded/run-for-session.js +102 -63
- package/dist/src/agent/embedded/run-turn.js +56 -28
- package/dist/src/agent/embedded/runs.d.ts +16 -29
- package/dist/src/agent/embedded/runs.js +44 -65
- package/dist/src/agent/embedded/types.d.ts +4 -0
- package/dist/src/agent/image/tool-model-config.js +1 -1
- package/dist/src/agent/ipc/inbox.js +1 -1
- package/dist/src/agent/memory/create-memory-manager.js +1 -1
- package/dist/src/agent/orchestration/agent-run-supervisor.d.ts +28 -0
- package/dist/src/agent/orchestration/agent-run-supervisor.js +49 -0
- package/dist/src/agent/orchestration/agent-turn-policy.d.ts +18 -0
- package/dist/src/agent/orchestration/agent-turn-policy.js +36 -0
- package/dist/src/agent/orchestration/llm-turn-retry.d.ts +5 -0
- package/dist/src/agent/orchestration/llm-turn-retry.js +34 -4
- package/dist/src/agent/orchestration/run-agent-turn-with-timeout.d.ts +10 -1
- package/dist/src/agent/orchestration/run-agent-turn-with-timeout.js +25 -4
- package/dist/src/agent/service/async-queue.d.ts +16 -3
- package/dist/src/agent/service/async-queue.js +29 -11
- package/dist/src/agent/service/process-direct-streaming.js +27 -4
- package/dist/src/agent/service.js +3 -2
- package/dist/src/agent/session/session-inspector.js +1 -1
- package/dist/src/agent/skills/hub-lock.js +1 -1
- package/dist/src/agent/tasks/task-judge-service.js +3 -3
- package/dist/src/agent/tools/exec-command.d.ts +2 -0
- package/dist/src/agent/tools/exec-command.js +70 -7
- package/dist/src/agent-runtime/index.js +1 -1
- package/dist/src/agent-runtime/runtime-profile.js +1 -1
- package/dist/src/ai-assist/text-assist.js +1 -1
- package/dist/src/auth/credentials.js +2 -2
- package/dist/src/auth/oauth/index.js +3 -3
- package/dist/src/auth/oauth/registry.d.ts +2 -0
- package/dist/src/auth/oauth/registry.js +20 -12
- package/dist/src/auth/oauth/xopc-cloud.d.ts +1 -0
- package/dist/src/auth/oauth/xopc-cloud.js +32 -16
- package/dist/src/auth/profiles/store.js +1 -1
- package/dist/src/auth/sync-provider-auth.js +2 -2
- package/dist/src/browser/providers/browser-ext-install.js +1 -1
- package/dist/src/channels/attachments/voice-stt-webchat.js +20 -3
- 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/tunnel.js +4 -5
- package/dist/src/config/index.js +4 -4
- package/dist/src/config/loader.js +1 -1
- package/dist/src/config/models-json.js +1 -1
- package/dist/src/config/schema.d.ts +18 -0
- package/dist/src/config/schema.js +19 -1
- package/dist/src/connectors/learning-coordinator.js +3 -19
- package/dist/src/extensions/lockfile.js +1 -1
- package/dist/src/gateway/agents-admin.js +1 -1
- package/dist/src/gateway/file-path-classifier.js +1 -1
- package/dist/src/gateway/hono/app.js +2 -1
- package/dist/src/gateway/hono/lib/config-payload.js +1 -1
- package/dist/src/gateway/hono/oauth-async.d.ts +1 -0
- package/dist/src/gateway/hono/oauth-async.js +11 -4
- package/dist/src/gateway/hono/routes/config.js +10 -0
- package/dist/src/gateway/hono/routes/host-fs.js +40 -1
- package/dist/src/gateway/hono/routes/media.js +33 -1
- package/dist/src/gateway/hono/routes/models.js +1 -1
- package/dist/src/gateway/hono/routes/notes.js +162 -7
- package/dist/src/gateway/hono/routes/shares.js +141 -6
- package/dist/src/gateway/hono/routes/tunnel.js +47 -10
- package/dist/src/gateway/hono/routes/voice.d.ts +2 -6
- package/dist/src/gateway/hono/routes/voice.js +28 -5
- package/dist/src/gateway/service/config-coordinator.js +1 -1
- package/dist/src/gateway/service.js +3 -3
- package/dist/src/notes/service.d.ts +6 -2
- package/dist/src/notes/service.js +36 -4
- package/dist/src/providers/auth-runtime/resolve-auth.js +1 -1
- package/dist/src/providers/index.js +3 -3
- package/dist/src/providers/model-registry.js +1 -1
- package/dist/src/providers/register-runtime-providers.js +1 -1
- package/dist/src/share/note-share-service.d.ts +67 -0
- package/dist/src/share/note-share-service.js +283 -0
- package/dist/src/share/share-config.js +32 -3
- package/dist/src/share/share-landing.d.ts +5 -1
- package/dist/src/share/share-landing.js +28 -1
- package/dist/src/share/share-store.d.ts +34 -5
- package/dist/src/share/share-store.js +125 -19
- package/dist/src/share/share-thumbnail.js +4 -2
- package/dist/src/share/share-types.d.ts +52 -14
- package/dist/src/share/share-types.js +9 -0
- package/dist/src/share/share-zip.d.ts +2 -2
- package/dist/src/tui/tui-startup-resources.js +1 -1
- package/dist/src/tui/tui.js +1 -1
- package/dist/src/tunnel/env.d.ts +12 -6
- package/dist/src/tunnel/env.js +34 -26
- package/dist/src/tunnel/gateway-lifecycle.js +2 -13
- package/dist/src/tunnel/index.d.ts +1 -1
- package/dist/src/tunnel/index.js +2 -2
- package/dist/src/tunnel/tunnel-audit.d.ts +1 -1
- package/dist/src/tunnel/tunnel-config.d.ts +1 -1
- package/dist/src/tunnel/tunnel-config.js +9 -4
- package/dist/src/tunnel/tunnel-service.d.ts +1 -1
- package/dist/src/tunnel/tunnel-service.js +3 -0
- package/dist/src/tunnel/well-known.d.ts +1 -1
- package/dist/src/tunnel/well-known.js +1 -1
- package/dist/src/tunnel/xopc-cloud-registration.d.ts +9 -0
- package/dist/src/tunnel/xopc-cloud-registration.js +42 -0
- package/dist/src/voice/audio/normalize.d.ts +7 -0
- package/dist/src/voice/audio/normalize.js +30 -4
- package/dist/src/voice/local/runtime-worker.js +26 -6
- package/dist/src/workflows/service/workflow-run-service.js +1 -1
- package/package.json +8 -14
- package/dist/gateway/static/root/assets/agents-Brs2-m6t.js +0 -68
- package/dist/gateway/static/root/assets/apps-page-DJ_Dsb1j.js +0 -1
- package/dist/gateway/static/root/assets/automations-workspace-DehLRXfd.js +0 -6
- package/dist/gateway/static/root/assets/block-editor-i4CfDqLG.js +0 -184
- package/dist/gateway/static/root/assets/browser-settings-page-C3QIO4Ti.js +0 -8
- package/dist/gateway/static/root/assets/capabilities-settings-panel-BHhUp7FW.js +0 -7
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-DoClNVLz.js +0 -3
- package/dist/gateway/static/root/assets/channels-settings-Je-9a5ZC.js +0 -1
- package/dist/gateway/static/root/assets/channels-status-swr-ByRbxC6z.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-D8z9Trw4.js +0 -2
- package/dist/gateway/static/root/assets/dist-C230izr8.js +0 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-D5CGVyci.js +0 -1
- package/dist/gateway/static/root/assets/home-page-DzfZFZx5.js +0 -1
- package/dist/gateway/static/root/assets/index-Dcj5KJRg.css +0 -2
- package/dist/gateway/static/root/assets/index-EvA8c7fo.js +0 -74
- package/dist/gateway/static/root/assets/local-app-workbench-page-hPyTaVYU.js +0 -2
- package/dist/gateway/static/root/assets/logs-page-DW0xMKWP.js +0 -2
- package/dist/gateway/static/root/assets/markdown-view-DYkxMtJA.js +0 -69
- package/dist/gateway/static/root/assets/messages-kx5W39dO.js +0 -3
- package/dist/gateway/static/root/assets/notes-api-DJizatxh.js +0 -1
- package/dist/gateway/static/root/assets/notes-workbench-D669d98r.js +0 -4
- package/dist/gateway/static/root/assets/project-detail-page-CpsQ9PTA.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-CitO1LMc.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-C9-bL4V6.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-CPSARi4y.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-BxT80Fmr.js +0 -1
- package/dist/gateway/static/root/assets/share-preview-page-WCVtJ3sX.js +0 -2
- package/dist/gateway/static/root/assets/shares-settings-DrQ_cOXh.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-C_4cG30x.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-CYjsecEr.js +0 -1
- package/dist/gateway/static/root/assets/user-context-page-Cy1D2hkt.js +0 -1
- package/dist/gateway/static/root/assets/workflows-page-DKKH1_Yt.js +0 -5
|
@@ -5,6 +5,7 @@ import { isTTSAvailable, resolveSpeechProvider } from "../../../voice/tts/factor
|
|
|
5
5
|
import { speak } from "../../../voice/tts/speak-core.js";
|
|
6
6
|
import { mergeTtsConfigFromAppConfig } from "../../../voice/tts/merge-config.js";
|
|
7
7
|
import { getLocalVoiceRuntimeClient } from "../../../voice/local/runtime-client.js";
|
|
8
|
+
import { getAudioDecoderStatus } from "../../../voice/audio/normalize.js";
|
|
8
9
|
import { resolveSttProviderConfigSlice } from "../../../voice/stt/config-slice.js";
|
|
9
10
|
import { STTTranscriptionError, transcribe } from "../../../voice/stt/transcribe-core.js";
|
|
10
11
|
import { isSTTAvailable } from "../../../voice/stt/availability.js";
|
|
@@ -200,9 +201,13 @@ function registerVoiceRoutes(authenticated, deps) {
|
|
|
200
201
|
});
|
|
201
202
|
});
|
|
202
203
|
authenticated.get("/api/voice/local/status", async (c) => {
|
|
204
|
+
const config = service.currentConfig;
|
|
205
|
+
const sttConfig = mergeSttConfigFromAppConfig(config.tools?.media?.audio, config.tools?.media);
|
|
206
|
+
const modelId = typeof sttConfig.providers?.["xopc-local"]?.model === "string" ? sttConfig.providers["xopc-local"].model : "sensevoice-small";
|
|
203
207
|
const models = await listLocalVoiceModelStatuses();
|
|
208
|
+
const decoder = getAudioDecoderStatus();
|
|
204
209
|
try {
|
|
205
|
-
const runtime = await getLocalVoiceRuntimeClient().request("health", {}, {
|
|
210
|
+
const runtime = await getLocalVoiceRuntimeClient().request("health", { modelId }, {
|
|
206
211
|
timeoutMs: 1e4,
|
|
207
212
|
stopOnTimeout: false
|
|
208
213
|
});
|
|
@@ -213,6 +218,7 @@ function registerVoiceRoutes(authenticated, deps) {
|
|
|
213
218
|
...runtime,
|
|
214
219
|
ready: true
|
|
215
220
|
},
|
|
221
|
+
decoder,
|
|
216
222
|
models
|
|
217
223
|
}
|
|
218
224
|
});
|
|
@@ -225,6 +231,7 @@ function registerVoiceRoutes(authenticated, deps) {
|
|
|
225
231
|
ready: false,
|
|
226
232
|
error: message
|
|
227
233
|
},
|
|
234
|
+
decoder,
|
|
228
235
|
models
|
|
229
236
|
}
|
|
230
237
|
});
|
|
@@ -486,14 +493,13 @@ function registerVoiceRoutes(authenticated, deps) {
|
|
|
486
493
|
fileName: input.fileName,
|
|
487
494
|
signal: c.req.raw.signal
|
|
488
495
|
});
|
|
489
|
-
const raw = result.text;
|
|
490
496
|
const detectedLanguage = result.language ?? input.language;
|
|
491
|
-
const
|
|
497
|
+
const refinementAvailable = (config.voice?.input?.refinement?.mode ?? "off") !== "off";
|
|
492
498
|
return c.json({
|
|
493
499
|
ok: true,
|
|
494
500
|
payload: {
|
|
495
|
-
|
|
496
|
-
|
|
501
|
+
text: result.text,
|
|
502
|
+
refinementAvailable,
|
|
497
503
|
...detectedLanguage ? { language: detectedLanguage } : {},
|
|
498
504
|
provider: result.provider,
|
|
499
505
|
attempts: result.attempts,
|
|
@@ -527,6 +533,23 @@ function registerVoiceRoutes(authenticated, deps) {
|
|
|
527
533
|
}, 502);
|
|
528
534
|
}
|
|
529
535
|
};
|
|
536
|
+
authenticated.post("/api/voice/transcriptions/refine", strictRateLimitMiddleware, async (c) => {
|
|
537
|
+
const body = await c.req.json().catch(() => null);
|
|
538
|
+
const text = typeof body?.text === "string" ? body.text.trim() : "";
|
|
539
|
+
if (!text) return c.json({
|
|
540
|
+
ok: false,
|
|
541
|
+
error: { message: "Missing transcript text" }
|
|
542
|
+
}, 400);
|
|
543
|
+
if (text.length > 2e4) return c.json({
|
|
544
|
+
ok: false,
|
|
545
|
+
error: { message: "Transcript text exceeds 20000 character limit" }
|
|
546
|
+
}, 413);
|
|
547
|
+
const refined = await refineTranscript(text, service.currentConfig, c.req.raw.signal);
|
|
548
|
+
return c.json({
|
|
549
|
+
ok: true,
|
|
550
|
+
payload: { text: refined ?? text }
|
|
551
|
+
});
|
|
552
|
+
});
|
|
530
553
|
/** Multipart endpoint used by browser and future native clients. */
|
|
531
554
|
authenticated.post("/api/voice/transcriptions", strictRateLimitMiddleware, async (c) => {
|
|
532
555
|
const contentLength = Number(c.req.header("content-length") ?? 0);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createLogger } from "../../utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../../utils/logger.js";
|
|
3
|
-
import { getModelRegistry } from "../../providers/model-registry.js";
|
|
4
3
|
import { loadConfig, saveConfig } from "../../config/loader.js";
|
|
4
|
+
import { getModelRegistry } from "../../providers/model-registry.js";
|
|
5
5
|
import { init_providers } from "../../providers/index.js";
|
|
6
6
|
import { disposeAllSessionMcpRuntimes } from "../../agent/mcp/bundle-mcp-runtime.js";
|
|
7
7
|
import { reloadImageGenerationProviders } from "../../agent/image/generation/provider-registry.js";
|
|
@@ -9,14 +9,14 @@ import { closeXopcDatabase, openXopcDatabase } from "../storage/sqlite/connectio
|
|
|
9
9
|
import "../storage/sqlite/index.js";
|
|
10
10
|
import { getAgentDefaultModelRef, init_schema } from "../config/schema.js";
|
|
11
11
|
import { init_agent_scope, listAgentEntries, normalizeAgentId, resolveDefaultAgentId } from "../agent/agent-scope.js";
|
|
12
|
+
import { buildSessionKey, init_session_key, sanitizeSegment } from "../routing/session-key.js";
|
|
13
|
+
import { getDefaultAgentId, init_resolve_route } from "../routing/resolve-route.js";
|
|
12
14
|
import { init_paths, resolveAgentDir, resolveConfigPath, resolveExtensionsDir } from "../config/paths.js";
|
|
13
|
-
import { prewarmModelRegistry } from "../providers/model-registry.js";
|
|
14
15
|
import { areExtensionsGloballyDisabled } from "../extensions/discover-extensions.js";
|
|
15
16
|
import { buildExtensionMetadataSnapshot } from "../extensions/extension-metadata-snapshot.js";
|
|
16
17
|
import { buildChannelCatalogForConfig, buildChannelCatalogFromSnapshot, init_channel_catalog_service } from "../channels/catalog/channel-catalog-service.js";
|
|
17
18
|
import { loadConfig, saveConfig } from "../config/loader.js";
|
|
18
|
-
import {
|
|
19
|
-
import { getDefaultAgentId, init_resolve_route } from "../routing/resolve-route.js";
|
|
19
|
+
import { prewarmModelRegistry } from "../providers/model-registry.js";
|
|
20
20
|
import { init_providers, providers_exports } from "../providers/index.js";
|
|
21
21
|
import { TaskRepository } from "../tasks/task-repository.js";
|
|
22
22
|
import { TaskConversationRepository } from "../tasks/task-conversation-repository.js";
|
|
@@ -6,11 +6,14 @@ export declare class NotesService {
|
|
|
6
6
|
private readonly store;
|
|
7
7
|
private lastSnapshotAt;
|
|
8
8
|
private quickCapturesInFlight;
|
|
9
|
+
private noteCapturesInFlight;
|
|
10
|
+
private noteAttachmentsInFlight;
|
|
9
11
|
constructor(store?: NotesStore);
|
|
10
12
|
initialize(): Promise<void>;
|
|
11
13
|
quickCapture(markdown: string, source: CaptureSource, idempotencyKey?: string): Promise<Note>;
|
|
12
14
|
private createQuickCapture;
|
|
13
|
-
createNote(params: CreateNoteParams): Promise<Note>;
|
|
15
|
+
createNote(params: CreateNoteParams, idempotencyKey?: string): Promise<Note>;
|
|
16
|
+
private createNoteWithId;
|
|
14
17
|
getNote(id: string): Promise<Note | null>;
|
|
15
18
|
updateNote(id: string, patch: Partial<Note>, trigger?: SnapshotTrigger): Promise<Note | null>;
|
|
16
19
|
private reconcileAttachments;
|
|
@@ -49,7 +52,8 @@ export declare class NotesService {
|
|
|
49
52
|
mimeType: string;
|
|
50
53
|
duration?: number;
|
|
51
54
|
retainWithoutReference?: boolean;
|
|
52
|
-
}): Promise<NoteAttachment | null>;
|
|
55
|
+
}, idempotencyKey?: string): Promise<NoteAttachment | null>;
|
|
56
|
+
private addAttachmentWithId;
|
|
53
57
|
removeAttachment(noteId: string, attachmentId: string): Promise<boolean>;
|
|
54
58
|
getAttachmentPath(noteId: string, attachmentId: string): Promise<{
|
|
55
59
|
filePath: string;
|
|
@@ -19,6 +19,12 @@ const SNAPSHOT_THROTTLE_MS = 6e4;
|
|
|
19
19
|
function quickCaptureId(idempotencyKey) {
|
|
20
20
|
return `capture_${createHash("sha256").update(`xopc:quick-capture:${idempotencyKey}`).digest("hex").slice(0, 32)}`;
|
|
21
21
|
}
|
|
22
|
+
function noteCaptureId(idempotencyKey) {
|
|
23
|
+
return `note_capture_${createHash("sha256").update(`xopc:note-capture:${idempotencyKey}`).digest("hex").slice(0, 32)}`;
|
|
24
|
+
}
|
|
25
|
+
function noteAttachmentId(noteId, idempotencyKey) {
|
|
26
|
+
return `attachment_${createHash("sha256").update(`xopc:note-attachment:${noteId}:${idempotencyKey}`).digest("hex").slice(0, 32)}`;
|
|
27
|
+
}
|
|
22
28
|
function inferKind(markdown, hasAttachments, attachments) {
|
|
23
29
|
if (hasAttachments && attachments?.length && attachments.every((item) => item.type === "audio")) return "voice";
|
|
24
30
|
if (hasAttachments) return "media";
|
|
@@ -146,6 +152,8 @@ async function buildAiCatalysisReport(note, config) {
|
|
|
146
152
|
var NotesService = class {
|
|
147
153
|
lastSnapshotAt = /* @__PURE__ */ new Map();
|
|
148
154
|
quickCapturesInFlight = /* @__PURE__ */ new Map();
|
|
155
|
+
noteCapturesInFlight = /* @__PURE__ */ new Map();
|
|
156
|
+
noteAttachmentsInFlight = /* @__PURE__ */ new Map();
|
|
149
157
|
constructor(store = new NotesStore()) {
|
|
150
158
|
this.store = store;
|
|
151
159
|
}
|
|
@@ -204,8 +212,20 @@ var NotesService = class {
|
|
|
204
212
|
}, "Quick capture");
|
|
205
213
|
return note;
|
|
206
214
|
}
|
|
207
|
-
async createNote(params) {
|
|
208
|
-
const id = randomUUID();
|
|
215
|
+
async createNote(params, idempotencyKey) {
|
|
216
|
+
const id = idempotencyKey ? noteCaptureId(idempotencyKey) : randomUUID();
|
|
217
|
+
if (idempotencyKey) {
|
|
218
|
+
const existing = await this.store.getNote(id);
|
|
219
|
+
if (existing) return existing;
|
|
220
|
+
const inFlight = this.noteCapturesInFlight.get(id);
|
|
221
|
+
if (inFlight) return inFlight;
|
|
222
|
+
const capture = this.createNoteWithId(id, params).finally(() => this.noteCapturesInFlight.delete(id));
|
|
223
|
+
this.noteCapturesInFlight.set(id, capture);
|
|
224
|
+
return capture;
|
|
225
|
+
}
|
|
226
|
+
return this.createNoteWithId(id, params);
|
|
227
|
+
}
|
|
228
|
+
async createNoteWithId(id, params) {
|
|
209
229
|
const now = Date.now();
|
|
210
230
|
const markdown = params.markdown ?? "";
|
|
211
231
|
const note = {
|
|
@@ -463,12 +483,24 @@ var NotesService = class {
|
|
|
463
483
|
async listNotes(query = {}) {
|
|
464
484
|
return this.store.listNotes(query);
|
|
465
485
|
}
|
|
466
|
-
async addAttachment(noteId, file) {
|
|
486
|
+
async addAttachment(noteId, file, idempotencyKey) {
|
|
487
|
+
if (!idempotencyKey) return this.addAttachmentWithId(noteId, file, randomUUID());
|
|
488
|
+
const attachmentId = noteAttachmentId(noteId, idempotencyKey);
|
|
489
|
+
const existing = (await this.store.getNote(noteId))?.attachments?.find((attachment) => attachment.id === attachmentId);
|
|
490
|
+
if (existing) return existing;
|
|
491
|
+
const inFlightKey = `${noteId}:${attachmentId}`;
|
|
492
|
+
const inFlight = this.noteAttachmentsInFlight.get(inFlightKey);
|
|
493
|
+
if (inFlight) return inFlight;
|
|
494
|
+
const capture = this.addAttachmentWithId(noteId, file, attachmentId).finally(() => this.noteAttachmentsInFlight.delete(inFlightKey));
|
|
495
|
+
this.noteAttachmentsInFlight.set(inFlightKey, capture);
|
|
496
|
+
return capture;
|
|
497
|
+
}
|
|
498
|
+
async addAttachmentWithId(noteId, file, attachmentId) {
|
|
467
499
|
const note = await this.store.getNote(noteId);
|
|
468
500
|
if (!note) return null;
|
|
469
501
|
const { relativePath, size } = await this.store.saveAttachment(noteId, file.name, file.buffer);
|
|
470
502
|
const attachment = {
|
|
471
|
-
id:
|
|
503
|
+
id: attachmentId,
|
|
472
504
|
type: file.mimeType.startsWith("image/") ? "image" : file.mimeType.startsWith("audio/") ? "audio" : file.mimeType.startsWith("video/") ? "video" : "file",
|
|
473
505
|
mimeType: file.mimeType,
|
|
474
506
|
fileName: file.name,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getApiKeyFromEnv, init_env_keys } from "../env-keys.js";
|
|
2
1
|
import { init_sync_provider_auth, resolveProviderApiKeyForAgentSync } from "../../auth/sync-provider-auth.js";
|
|
2
|
+
import { getApiKeyFromEnv, init_env_keys } from "../env-keys.js";
|
|
3
3
|
import { getDefaultAuthProfileStore } from "./auth-profile-store.js";
|
|
4
4
|
//#region src/providers/auth-runtime/resolve-auth.ts
|
|
5
5
|
init_env_keys();
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { __esmMin, __exportAll } from "../../_virtual/_rolldown/runtime.js";
|
|
2
2
|
import { getAgentDefaultModelRef, init_schema } from "../config/schema.js";
|
|
3
|
+
import { getModelOAuthProviderIds, init_registry } from "../auth/oauth/registry.js";
|
|
4
|
+
import { hasProviderAuthOnDiskSync, init_sync_provider_auth } from "../auth/sync-provider-auth.js";
|
|
3
5
|
import { PROVIDER_ENV_MAP, getApiKeyFromEnv, init_env_keys } from "./env-keys.js";
|
|
4
6
|
import { init_model_ref, splitProviderModelRef } from "./model-ref.js";
|
|
5
7
|
import { ModelRegistry, getModelRegistry, init_model_registry, prewarmModelRegistry, resetModelRegistry } from "./model-registry.js";
|
|
6
|
-
import { getOAuthProviderIds, init_registry } from "../auth/oauth/registry.js";
|
|
7
8
|
import { CredentialResolver, hasCredentials, init_credentials } from "../auth/credentials.js";
|
|
8
|
-
import { hasProviderAuthOnDiskSync, init_sync_provider_auth } from "../auth/sync-provider-auth.js";
|
|
9
9
|
import { EXTENSION_PROVIDER_BASE_URL, init_constants } from "./constants.js";
|
|
10
10
|
import { applyOfficialModelMetadataCorrections, getSupplementalModels, init_model_supplements } from "./model-supplements.js";
|
|
11
11
|
import { getProviderRegistry, init_plugin_registry } from "./plugin-registry.js";
|
|
@@ -141,7 +141,7 @@ function getAllProviders() {
|
|
|
141
141
|
for (const p of getProviders()) providers.add(p);
|
|
142
142
|
for (const m of registry.getAll()) providers.add(m.provider);
|
|
143
143
|
for (const plugin of getProviderRegistry().listAll()) providers.add(plugin.id);
|
|
144
|
-
for (const provider of
|
|
144
|
+
for (const provider of getModelOAuthProviderIds()) providers.add(provider);
|
|
145
145
|
return Array.from(providers);
|
|
146
146
|
}
|
|
147
147
|
async function getApiKey(provider, credentialOptions) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { __esmMin } from "../../_virtual/_rolldown/runtime.js";
|
|
2
2
|
import { createLogger } from "../utils/logger/index.js";
|
|
3
3
|
import { init_logger } from "../utils/logger.js";
|
|
4
|
-
import { init_resolve_config_value, resolveConfigValue, resolveHeaders } from "../config/resolve-config-value.js";
|
|
5
4
|
import { resolveModelsJsonPath } from "../config/paths.js";
|
|
5
|
+
import { init_resolve_config_value, resolveConfigValue, resolveHeaders } from "../config/resolve-config-value.js";
|
|
6
6
|
import { getDefaultModelValues, init_models_json, validateModelsConfig } from "../config/models-json.js";
|
|
7
7
|
import { getApiKeyFromEnv, init_env_keys } from "./env-keys.js";
|
|
8
8
|
import { getModelCatalogStore, init_model_catalog_store } from "./model-catalog-store.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { __esmMin } from "../../_virtual/_rolldown/runtime.js";
|
|
2
|
-
import { getModelCatalogStore, init_model_catalog_store } from "./model-catalog-store.js";
|
|
3
2
|
import { getOAuthProviderDefinition, init_registry } from "../auth/oauth/registry.js";
|
|
3
|
+
import { getModelCatalogStore, init_model_catalog_store } from "./model-catalog-store.js";
|
|
4
4
|
import { XOPC_CLOUD_PROVIDER_ID, init_xopc_cloud_config, resolveXopcModelRouterUrl } from "./xopc-cloud-config.js";
|
|
5
5
|
//#region src/providers/register-runtime-providers.ts
|
|
6
6
|
function registerRuntimeProvider(modelRuntime, source) {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Note, NoteAttachment } from '../notes/types.js';
|
|
2
|
+
import type { NoteShareRecord } from './share-types.js';
|
|
3
|
+
import type { ShareStore } from './share-store.js';
|
|
4
|
+
export interface NoteShareManifestAttachment {
|
|
5
|
+
id: string;
|
|
6
|
+
type: NoteAttachment['type'];
|
|
7
|
+
mimeType: string;
|
|
8
|
+
fileName: string;
|
|
9
|
+
size: number;
|
|
10
|
+
artifactFileName: string;
|
|
11
|
+
checksum: string;
|
|
12
|
+
duration?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface NoteShareManifest {
|
|
15
|
+
schemaVersion: 1;
|
|
16
|
+
shareId: string;
|
|
17
|
+
source: {
|
|
18
|
+
noteId: string;
|
|
19
|
+
noteVersion: number;
|
|
20
|
+
};
|
|
21
|
+
title: string;
|
|
22
|
+
markdown: string;
|
|
23
|
+
snapshotAt: string;
|
|
24
|
+
attachments: NoteShareManifestAttachment[];
|
|
25
|
+
}
|
|
26
|
+
export interface NoteShareSource {
|
|
27
|
+
getNote(id: string): Promise<Note | null>;
|
|
28
|
+
getAttachmentPath(noteId: string, attachmentId: string): Promise<{
|
|
29
|
+
filePath: string;
|
|
30
|
+
mimeType: string;
|
|
31
|
+
fileName: string;
|
|
32
|
+
} | null>;
|
|
33
|
+
}
|
|
34
|
+
export interface CreateNoteShareInput {
|
|
35
|
+
expectedNoteVersion?: number;
|
|
36
|
+
attachmentIds?: string[];
|
|
37
|
+
ttlMs?: number;
|
|
38
|
+
maxViews?: number | null;
|
|
39
|
+
description?: string;
|
|
40
|
+
gatewayTokenHash: string;
|
|
41
|
+
}
|
|
42
|
+
export declare class NoteShareVersionConflictError extends Error {
|
|
43
|
+
readonly currentVersion: number;
|
|
44
|
+
constructor(currentVersion: number);
|
|
45
|
+
}
|
|
46
|
+
export declare class NoteShareService {
|
|
47
|
+
private readonly store;
|
|
48
|
+
private readonly notes;
|
|
49
|
+
constructor(store: ShareStore, notes: NoteShareSource);
|
|
50
|
+
create(noteId: string, input: CreateNoteShareInput): Promise<NoteShareRecord>;
|
|
51
|
+
refresh(noteId: string, shareId: string, input: Pick<CreateNoteShareInput, 'expectedNoteVersion' | 'attachmentIds'>): Promise<NoteShareRecord>;
|
|
52
|
+
list(noteId: string): NoteShareRecord[];
|
|
53
|
+
revokeForNote(noteId: string): Promise<number>;
|
|
54
|
+
removeArtifact(shareId: string): Promise<void>;
|
|
55
|
+
readManifest(record: NoteShareRecord): Promise<NoteShareManifest>;
|
|
56
|
+
issueAssetTicket(record: NoteShareRecord): string;
|
|
57
|
+
verifyAssetTicket(record: NoteShareRecord, ticket: string): boolean;
|
|
58
|
+
resolveAsset(record: NoteShareRecord, attachmentId: string): Promise<{
|
|
59
|
+
path: string;
|
|
60
|
+
attachment: NoteShareManifestAttachment;
|
|
61
|
+
} | null>;
|
|
62
|
+
publicMarkdown(record: NoteShareRecord, manifest: NoteShareManifest, ticket: string): string;
|
|
63
|
+
private requireNote;
|
|
64
|
+
private buildArtifact;
|
|
65
|
+
private artifactRelativePath;
|
|
66
|
+
private artifactDir;
|
|
67
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { resolveStateDir } from "../config/paths-state.js";
|
|
2
|
+
import { createLogger } from "../utils/logger/index.js";
|
|
3
|
+
import { init_logger } from "../utils/logger.js";
|
|
4
|
+
import { init_paths } from "../config/paths.js";
|
|
5
|
+
import { collectReferencedAttachmentIds } from "../notes/note-attachment-sync.js";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { createHash, createHmac, randomUUID, timingSafeEqual } from "node:crypto";
|
|
8
|
+
import { copyFile, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
9
|
+
//#region src/share/note-share-service.ts
|
|
10
|
+
init_paths();
|
|
11
|
+
init_logger();
|
|
12
|
+
const log = createLogger("NoteShareService");
|
|
13
|
+
const MANIFEST_SCHEMA_VERSION = 1;
|
|
14
|
+
var NoteShareVersionConflictError = class extends Error {
|
|
15
|
+
constructor(currentVersion) {
|
|
16
|
+
super("Note changed after the share preview was reviewed");
|
|
17
|
+
this.currentVersion = currentVersion;
|
|
18
|
+
this.name = "NoteShareVersionConflictError";
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var NoteShareService = class {
|
|
22
|
+
constructor(store, notes) {
|
|
23
|
+
this.store = store;
|
|
24
|
+
this.notes = notes;
|
|
25
|
+
}
|
|
26
|
+
async create(noteId, input) {
|
|
27
|
+
const note = await this.requireNote(noteId, input.expectedNoteVersion);
|
|
28
|
+
const id = randomUUID();
|
|
29
|
+
const staged = await this.buildArtifact(id, note, input.attachmentIds);
|
|
30
|
+
try {
|
|
31
|
+
return this.store.createNoteShare({
|
|
32
|
+
id,
|
|
33
|
+
sourceNoteId: note.id,
|
|
34
|
+
sourceVersion: note.updatedAt,
|
|
35
|
+
artifactRelativePath: this.artifactRelativePath(id),
|
|
36
|
+
artifactSize: staged.totalSize,
|
|
37
|
+
attachmentCount: staged.manifest.attachments.length,
|
|
38
|
+
fileName: staged.manifest.title,
|
|
39
|
+
ttlMs: input.ttlMs,
|
|
40
|
+
maxViews: input.maxViews,
|
|
41
|
+
description: input.description,
|
|
42
|
+
gatewayTokenHash: input.gatewayTokenHash
|
|
43
|
+
});
|
|
44
|
+
} catch (error) {
|
|
45
|
+
await this.removeArtifact(id);
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async refresh(noteId, shareId, input) {
|
|
50
|
+
const record = this.store.getById(shareId);
|
|
51
|
+
if (!record || record.kind !== "note" || record.sourceNoteId !== noteId) throw new Error("Note share not found");
|
|
52
|
+
const note = await this.requireNote(noteId, input.expectedNoteVersion);
|
|
53
|
+
const finalDir = this.artifactDir(shareId);
|
|
54
|
+
const stagedDir = `${finalDir}.refresh-${randomUUID()}`;
|
|
55
|
+
const backupDir = `${finalDir}.backup-${randomUUID()}`;
|
|
56
|
+
const staged = await this.buildArtifact(shareId, note, input.attachmentIds, stagedDir);
|
|
57
|
+
let movedOld = false;
|
|
58
|
+
try {
|
|
59
|
+
await rename(finalDir, backupDir);
|
|
60
|
+
movedOld = true;
|
|
61
|
+
await rename(stagedDir, finalDir);
|
|
62
|
+
const updated = this.store.updateNoteSnapshot(shareId, {
|
|
63
|
+
sourceVersion: note.updatedAt,
|
|
64
|
+
artifactSize: staged.totalSize,
|
|
65
|
+
attachmentCount: staged.manifest.attachments.length,
|
|
66
|
+
fileName: staged.manifest.title
|
|
67
|
+
});
|
|
68
|
+
if (!updated) throw new Error("Note share not found");
|
|
69
|
+
await rm(backupDir, {
|
|
70
|
+
recursive: true,
|
|
71
|
+
force: true
|
|
72
|
+
});
|
|
73
|
+
return updated;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
await rm(stagedDir, {
|
|
76
|
+
recursive: true,
|
|
77
|
+
force: true
|
|
78
|
+
});
|
|
79
|
+
if (movedOld) {
|
|
80
|
+
await rm(finalDir, {
|
|
81
|
+
recursive: true,
|
|
82
|
+
force: true
|
|
83
|
+
});
|
|
84
|
+
await rename(backupDir, finalDir).catch(() => void 0);
|
|
85
|
+
}
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
list(noteId) {
|
|
90
|
+
return this.store.getNoteShares(noteId);
|
|
91
|
+
}
|
|
92
|
+
async revokeForNote(noteId) {
|
|
93
|
+
const active = this.store.getNoteShares(noteId).filter((record) => !record.revoked);
|
|
94
|
+
const count = this.store.revokeMany(active.map((record) => record.id));
|
|
95
|
+
await Promise.all(active.map((record) => this.removeArtifact(record.id)));
|
|
96
|
+
return count;
|
|
97
|
+
}
|
|
98
|
+
async removeArtifact(shareId) {
|
|
99
|
+
await rm(this.artifactDir(shareId), {
|
|
100
|
+
recursive: true,
|
|
101
|
+
force: true
|
|
102
|
+
}).catch((err) => {
|
|
103
|
+
log.warn({
|
|
104
|
+
err,
|
|
105
|
+
shareId
|
|
106
|
+
}, "Failed to remove Note share artifact");
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async readManifest(record) {
|
|
110
|
+
const raw = await readFile(join(resolveStateDir(), record.artifactRelativePath, "manifest.json"), "utf8");
|
|
111
|
+
const manifest = JSON.parse(raw);
|
|
112
|
+
if (manifest.schemaVersion !== MANIFEST_SCHEMA_VERSION || manifest.shareId !== record.id) throw new Error("Invalid Note share artifact");
|
|
113
|
+
return manifest;
|
|
114
|
+
}
|
|
115
|
+
issueAssetTicket(record) {
|
|
116
|
+
const expiresAt = Date.now() + this.store.getConfig().note.assetTicketTtlMs;
|
|
117
|
+
const payload = Buffer.from(JSON.stringify({
|
|
118
|
+
shareId: record.id,
|
|
119
|
+
revision: record.snapshotRevision,
|
|
120
|
+
expiresAt
|
|
121
|
+
})).toString("base64url");
|
|
122
|
+
return `${payload}.${createHmac("sha256", record.assetTicketSecret).update(payload).digest("base64url")}`;
|
|
123
|
+
}
|
|
124
|
+
verifyAssetTicket(record, ticket) {
|
|
125
|
+
const [payload, signature] = ticket.split(".");
|
|
126
|
+
if (!payload || !signature) return false;
|
|
127
|
+
const expected = createHmac("sha256", record.assetTicketSecret).update(payload).digest();
|
|
128
|
+
let actual;
|
|
129
|
+
try {
|
|
130
|
+
actual = Buffer.from(signature, "base64url");
|
|
131
|
+
} catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
if (actual.length !== expected.length || !timingSafeEqual(actual, expected)) return false;
|
|
135
|
+
try {
|
|
136
|
+
const value = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
137
|
+
return value.shareId === record.id && value.revision === record.snapshotRevision && typeof value.expiresAt === "number" && Date.now() < value.expiresAt;
|
|
138
|
+
} catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async resolveAsset(record, attachmentId) {
|
|
143
|
+
const attachment = (await this.readManifest(record)).attachments.find((item) => item.id === attachmentId);
|
|
144
|
+
if (!attachment || attachment.artifactFileName !== attachment.id) return null;
|
|
145
|
+
return {
|
|
146
|
+
path: join(resolveStateDir(), record.artifactRelativePath, "assets", attachment.artifactFileName),
|
|
147
|
+
attachment
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
publicMarkdown(record, manifest, ticket) {
|
|
151
|
+
return manifest.markdown.replace(/xopc-attachment:\/\/notes\/([^/]+)\/([^\s)]+)/gi, (_match, noteId, attachmentId) => {
|
|
152
|
+
let decodedNoteId = noteId;
|
|
153
|
+
let decodedAttachmentId = attachmentId;
|
|
154
|
+
try {
|
|
155
|
+
decodedNoteId = decodeURIComponent(noteId);
|
|
156
|
+
decodedAttachmentId = decodeURIComponent(attachmentId);
|
|
157
|
+
} catch {
|
|
158
|
+
return "#invalid-attachment";
|
|
159
|
+
}
|
|
160
|
+
if (decodedNoteId !== record.sourceNoteId || !manifest.attachments.some((item) => item.id === decodedAttachmentId)) return "#attachment-not-shared";
|
|
161
|
+
return `/s/${encodeURIComponent(record.token)}/assets/${encodeURIComponent(decodedAttachmentId)}?ticket=${encodeURIComponent(ticket)}`;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
async requireNote(noteId, expectedVersion) {
|
|
165
|
+
const note = await this.notes.getNote(noteId);
|
|
166
|
+
if (!note) throw new Error("Note not found");
|
|
167
|
+
if (expectedVersion !== void 0 && note.updatedAt !== expectedVersion) throw new NoteShareVersionConflictError(note.updatedAt);
|
|
168
|
+
return note;
|
|
169
|
+
}
|
|
170
|
+
async buildArtifact(shareId, note, requestedAttachmentIds, targetDir = this.artifactDir(shareId)) {
|
|
171
|
+
const cfg = this.store.getConfig().note;
|
|
172
|
+
if (!cfg.enabled) throw new Error("Note sharing is disabled");
|
|
173
|
+
const referenced = collectReferencedAttachmentIds(note);
|
|
174
|
+
const known = new Map((note.attachments ?? []).map((attachment) => [attachment.id, attachment]));
|
|
175
|
+
for (const id of referenced) if (!known.has(id)) throw new Error(`Referenced attachment is missing: ${id}`);
|
|
176
|
+
const selected = requestedAttachmentIds === void 0 ? [...referenced] : Array.from(new Set(requestedAttachmentIds));
|
|
177
|
+
for (const id of selected) if (!referenced.has(id)) throw new Error(`Attachment is not referenced by this Note: ${id}`);
|
|
178
|
+
const projectedMarkdown = projectPublicMarkdown(note, new Set(selected));
|
|
179
|
+
const markdownBytes = Buffer.byteLength(projectedMarkdown, "utf8");
|
|
180
|
+
if (markdownBytes > cfg.maxMarkdownBytes) throw new Error("Note Markdown exceeds sharing limit");
|
|
181
|
+
if (selected.length > cfg.maxAttachmentCount) throw new Error("Note has too many attachments to share");
|
|
182
|
+
const tempDir = targetDir === this.artifactDir(shareId) ? `${targetDir}.tmp-${randomUUID()}` : targetDir;
|
|
183
|
+
const assetsDir = join(tempDir, "assets");
|
|
184
|
+
await rm(tempDir, {
|
|
185
|
+
recursive: true,
|
|
186
|
+
force: true
|
|
187
|
+
});
|
|
188
|
+
await mkdir(assetsDir, {
|
|
189
|
+
recursive: true,
|
|
190
|
+
mode: 448
|
|
191
|
+
});
|
|
192
|
+
let totalSize = markdownBytes;
|
|
193
|
+
const attachments = [];
|
|
194
|
+
try {
|
|
195
|
+
for (const id of selected) {
|
|
196
|
+
const source = known.get(id);
|
|
197
|
+
if (source.size > cfg.maxAttachmentSize) throw new Error(`Attachment exceeds sharing limit: ${source.fileName}`);
|
|
198
|
+
totalSize += source.size;
|
|
199
|
+
if (totalSize > cfg.maxTotalSize) throw new Error("Note share exceeds total size limit");
|
|
200
|
+
const resolved = await this.notes.getAttachmentPath(note.id, id);
|
|
201
|
+
if (!resolved) throw new Error(`Attachment is missing: ${source.fileName}`);
|
|
202
|
+
const fileStat = await stat(resolved.filePath);
|
|
203
|
+
if (!fileStat.isFile() || fileStat.size !== source.size) throw new Error(`Attachment changed or is missing: ${source.fileName}`);
|
|
204
|
+
const bytes = await readFile(resolved.filePath);
|
|
205
|
+
const checksum = createHash("sha256").update(bytes).digest("hex");
|
|
206
|
+
await copyFile(resolved.filePath, join(assetsDir, id));
|
|
207
|
+
attachments.push({
|
|
208
|
+
id,
|
|
209
|
+
type: source.type,
|
|
210
|
+
mimeType: source.mimeType,
|
|
211
|
+
fileName: source.fileName,
|
|
212
|
+
size: source.size,
|
|
213
|
+
artifactFileName: id,
|
|
214
|
+
checksum,
|
|
215
|
+
duration: source.duration
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
const title = note.title?.trim() || "Untitled Note";
|
|
219
|
+
const manifest = {
|
|
220
|
+
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
221
|
+
shareId,
|
|
222
|
+
source: {
|
|
223
|
+
noteId: note.id,
|
|
224
|
+
noteVersion: note.updatedAt
|
|
225
|
+
},
|
|
226
|
+
title,
|
|
227
|
+
markdown: projectedMarkdown,
|
|
228
|
+
snapshotAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
229
|
+
attachments
|
|
230
|
+
};
|
|
231
|
+
await writeFile(join(tempDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, { mode: 384 });
|
|
232
|
+
if (tempDir !== targetDir) {
|
|
233
|
+
await mkdir(dirname(targetDir), {
|
|
234
|
+
recursive: true,
|
|
235
|
+
mode: 448
|
|
236
|
+
});
|
|
237
|
+
await rename(tempDir, targetDir);
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
manifest,
|
|
241
|
+
totalSize
|
|
242
|
+
};
|
|
243
|
+
} catch (error) {
|
|
244
|
+
await rm(tempDir, {
|
|
245
|
+
recursive: true,
|
|
246
|
+
force: true
|
|
247
|
+
});
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
artifactRelativePath(shareId) {
|
|
252
|
+
return join("share-artifacts", shareId);
|
|
253
|
+
}
|
|
254
|
+
artifactDir(shareId) {
|
|
255
|
+
return join(resolveStateDir(), this.artifactRelativePath(shareId));
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
function projectPublicMarkdown(note, selected) {
|
|
259
|
+
const canonicalTarget = "xopc-attachment://notes/([^/\\s)]+)/([^\\s)]+)";
|
|
260
|
+
const assertOwned = new RegExp(canonicalTarget, "gi");
|
|
261
|
+
note.markdown.replace(assertOwned, (_match, rawNoteId) => {
|
|
262
|
+
let noteId;
|
|
263
|
+
try {
|
|
264
|
+
noteId = decodeURIComponent(rawNoteId);
|
|
265
|
+
} catch {
|
|
266
|
+
throw new Error("Invalid Note attachment reference");
|
|
267
|
+
}
|
|
268
|
+
if (noteId !== note.id) throw new Error("Cross-Note attachment references cannot be shared");
|
|
269
|
+
return _match;
|
|
270
|
+
});
|
|
271
|
+
const projectTarget = (rawNoteId, rawAttachmentId) => {
|
|
272
|
+
try {
|
|
273
|
+
return decodeURIComponent(rawNoteId) === note.id && selected.has(decodeURIComponent(rawAttachmentId));
|
|
274
|
+
} catch {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
let markdown = note.markdown.replace(new RegExp(`!\\[([^\\]]*)\\]\\(${canonicalTarget}\\)`, "gi"), (_match, alt, rawNoteId, rawAttachmentId) => projectTarget(rawNoteId, rawAttachmentId) ? _match : `_[Attachment not shared${alt ? `: ${alt}` : ""}]_`);
|
|
279
|
+
markdown = markdown.replace(new RegExp(`\\[([^\\]]+)\\]\\(${canonicalTarget}\\)`, "gi"), (_match, label, rawNoteId, rawAttachmentId) => projectTarget(rawNoteId, rawAttachmentId) ? _match : label);
|
|
280
|
+
return markdown;
|
|
281
|
+
}
|
|
282
|
+
//#endregion
|
|
283
|
+
export { NoteShareService, NoteShareVersionConflictError };
|