@xopcai/xopc 0.0.229 → 0.0.231
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-CsFAJTIN.js → agent-avatar-dicebear-CBqUnlvL.js} +1 -1
- package/dist/gateway/static/root/assets/agents-7YO6znTT.js +68 -0
- package/dist/gateway/static/root/assets/{app-management-settings-panel-7KcMWuah.js → app-management-settings-panel-DP7YVRPn.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-Cqlw7Fif.js → appearance-settings-CVQ5n7y6.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-CsnhdGZ2.js → apps-page-DSCia0DT.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-q4pDpqkF.js → attachment-load-mmr_mHRr.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-BuTqjKGX.js → automations-page-DfY-T6Ga.js} +1 -1
- package/dist/gateway/static/root/assets/automations-workspace-BpvD54D6.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-DUce1Bsc.js → binary-plugins-CO9QMEh2.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-D8Ejp-QR.js → block-editor-CE14CVOy.js} +3 -3
- package/dist/gateway/static/root/assets/browser-settings-page-CeESA4Oj.js +8 -0
- package/dist/gateway/static/root/assets/{browser-workflows-page-BDvZbR9v.js → browser-workflows-page-1wgXyoSO.js} +2 -2
- package/dist/gateway/static/root/assets/capabilities-settings-panel-B_OXTojB.js +7 -0
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-D5nZecxV.js +3 -0
- package/dist/gateway/static/root/assets/channels-settings-BzmnWx6X.js +1 -0
- package/dist/gateway/static/root/assets/{chat-terminal-dock-h1CKexNz.js → chat-terminal-dock-CdRW6Euc.js} +2 -2
- package/dist/gateway/static/root/assets/cloud-C4-yfnnk.js +1 -0
- package/dist/gateway/static/root/assets/connectors-page-DX6OkwFQ.js +2 -0
- package/dist/gateway/static/root/assets/{cron-expression-DlaQQOId.js → cron-expression-DYSMZsms.js} +1 -1
- package/dist/gateway/static/root/assets/{date-picker-8R1sXJKb.js → date-picker-DGCfX4tK.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-CWur0821.js → dependency-picker-DHDa4ht1.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-Knl-zNDU.js → desktop-pet-CMO-J3cG.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-CAQMLVtd.js → desktop-pet-settings-MmOR4A3u.js} +1 -1
- package/dist/gateway/static/root/assets/{directory-picker-path-field-Caaz4Rw5.js → directory-picker-path-field-B9qoxDcn.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-B965Mc15.js → extension-debug-page-DdH0dG2_.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-C0UnkBi5.js → extension-page-Clg-KktN.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-BUSIAZCR.js → extension-settings-page-eP3fJ2M6.js} +1 -1
- package/dist/gateway/static/root/assets/files-Dp8_uGTO.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-config-api-DkH0eEgy.js → gateway-config-api-DEHnF4Wm.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-BBgLDm6I.js → gateway-settings-DKkEXXj9.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-BtEHVXWD.js → heartbeat-settings-DlBPNubY.js} +1 -1
- package/dist/gateway/static/root/assets/home-page-BnOrLRfs.js +1 -0
- package/dist/gateway/static/root/assets/index-0xQO5B_Q.js +70 -0
- package/dist/gateway/static/root/assets/index-CCyIdGuQ.css +2 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-CsRz8NrS.js → keyboard-shortcuts-settings-BvpEeHnm.js} +1 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-Ch1rdUo8.js +2 -0
- package/dist/gateway/static/root/assets/{local-apps-page-CTBNUG4a.js → local-apps-page-Bzvn99eH.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-zxx2ExlY.js +2 -0
- package/dist/gateway/static/root/assets/{management-settings-YqUAEwQo.js → management-settings-BQaeyOYm.js} +1 -1
- package/dist/gateway/static/root/assets/{markdown-split-dxpSCXb8.js → markdown-split-D9tmIiEm.js} +1 -1
- package/dist/gateway/static/root/assets/markdown-view-B1bGSQqt.css +1 -0
- package/dist/gateway/static/root/assets/{media-plugins-CJ00i1Lg.js → media-plugins-D8I7OGnT.js} +1 -1
- package/dist/gateway/static/root/assets/models-hub-cache-0RjEwafI.js +1 -0
- package/dist/gateway/static/root/assets/{notes-api-BFsOGYkd.js → notes-api-EpgG-OQc.js} +1 -1
- package/dist/gateway/static/root/assets/{notes-page-DEnbnkIt.js → notes-page-CCvjGMhN.js} +1 -1
- package/dist/gateway/static/root/assets/notes-workbench-9cgh77VH.js +4 -0
- package/dist/gateway/static/root/assets/oauth-api-BwNLwbI8.js +1 -0
- package/dist/gateway/static/root/assets/{product-open-page-BHxo9U95.js → product-open-page-MmMswmOU.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-hf6pmH4p.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-By2U-Wdh.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-CUcpp7CQ.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-BpQ2GI-0.js → runtime-tools-settings-panel-Dx6o7sLu.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-HE1G26Lx.js → schema-form-Cdd9c4vt.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-ByI82xS1.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-BCoI5IJ8.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-CXAn0Xhy.js → settings-form-section-D0YxMnhm.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-loading-skeleton-DsFu7Bcv.js → settings-loading-skeleton-y53gEKsF.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-C4iib9nO.js → settings-page-BoE_tToG.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-BA83Wr1d.js → setup-status-panel-BQmyb5KC.js} +1 -1
- package/dist/gateway/static/root/assets/share-preview-page-DiqBn-oE.js +2 -0
- package/dist/gateway/static/root/assets/shares-settings-Bf3VpZ-R.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-CXHlbuGS.js +1 -0
- package/dist/gateway/static/root/assets/{skills-page-CdaFtNw9.js → skills-page-CZwhWiRl.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-DYaAddUL.js → skills-page.utils-kBGr8opZ.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-Co7gilRd.js → system-settings-panel-CPBs6lWi.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-Bo0KSZ2v.js +1 -0
- package/dist/gateway/static/root/assets/theme-store-Qgvo0Ga7.js +1 -0
- package/dist/gateway/static/root/assets/{use-autosave-DnnZtMpA.js → use-autosave-C4YcEiP9.js} +1 -1
- package/dist/gateway/static/root/assets/user-context-page-Cnj7gMX2.js +1 -0
- package/dist/gateway/static/root/assets/{voice-api-key-field-wetUpi3i.js → voice-api-key-field-BML8zCwZ.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-tbTWASJJ.js → work-discovery-overlay-CKIdPEen.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-Dy0fDs8w.js → workflow-run-setup-panel-BK44Vm5W.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-CB98MTwG.js +5 -0
- package/dist/gateway/static/root/index.html +10 -6
- package/dist/package.js +1 -1
- package/dist/src/agent/inbound/turn-dispatcher.js +1 -1
- package/dist/src/agent/service/process-direct-streaming.js +4 -1
- package/dist/src/agent/service/webchat-tts.d.ts +2 -0
- package/dist/src/agent/service/webchat-tts.js +15 -2
- package/dist/src/agent/service.d.ts +1 -0
- package/dist/src/agent/service.js +5 -2
- package/dist/src/agent/skills/skill-manage-ops.js +1 -1
- package/dist/src/agent/tools/tts-tool.js +19 -13
- package/dist/src/agent/tools/write.js +9 -12
- package/dist/src/agent/tools/xopc-use-tool.js +2 -2
- package/dist/src/auth/oauth/index.js +2 -2
- package/dist/src/auth/oauth/registry.d.ts +1 -1
- package/dist/src/auth/oauth/registry.js +5 -1
- package/dist/src/auth/oauth/xopc-cloud.d.ts +1 -0
- package/dist/src/auth/oauth/xopc-cloud.js +8 -2
- package/dist/src/files/file-service.d.ts +36 -0
- package/dist/src/files/file-service.js +282 -0
- package/dist/src/gateway/chat-stream/mapper.js +11 -0
- package/dist/src/gateway/heartbeat/service.js +1 -1
- package/dist/src/gateway/hono/app.js +2 -2
- package/dist/src/gateway/hono/routes/files.d.ts +3 -0
- package/dist/src/gateway/hono/routes/files.js +172 -0
- package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
- package/dist/src/gateway/hono/routes/projects.js +0 -508
- package/dist/src/gateway/hono/routes/shares.js +410 -21
- package/dist/src/gateway/hono/routes/workspace.js +15 -974
- package/dist/src/gateway/service.js +9 -9
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/share/hosted-session-share.d.ts +104 -0
- package/dist/src/share/hosted-session-share.js +276 -0
- package/dist/src/share/note-share-service.js +4 -24
- package/dist/src/share/session-share-projector.d.ts +31 -0
- package/dist/src/share/session-share-projector.js +79 -0
- package/dist/src/share/session-share-service.d.ts +89 -0
- package/dist/src/share/session-share-service.js +325 -0
- package/dist/src/share/share-asset-ticket.d.ts +7 -0
- package/dist/src/share/share-asset-ticket.js +30 -0
- package/dist/src/share/share-landing.d.ts +4 -3
- package/dist/src/share/share-landing.js +5 -5
- package/dist/src/share/share-store.d.ts +27 -2
- package/dist/src/share/share-store.js +71 -8
- package/dist/src/share/share-types.d.ts +17 -2
- package/dist/src/tui/backends/embedded-backend.js +1 -1
- package/dist/src/tui/backends/gateway-realtime-backend.js +11 -3
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/agents-DA4b6_0C.js +0 -68
- package/dist/gateway/static/root/assets/automations-workspace-CD0E2ThS.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-CY1k3MMd.js +0 -8
- package/dist/gateway/static/root/assets/capabilities-settings-panel-D1VfotHQ.js +0 -7
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-DXHqRLpU.js +0 -3
- package/dist/gateway/static/root/assets/channels-settings-DrFL57M_.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-DUAyfFCF.js +0 -2
- package/dist/gateway/static/root/assets/home-page-5SU2TNJj.js +0 -1
- package/dist/gateway/static/root/assets/index-BNWhgmjs.js +0 -70
- package/dist/gateway/static/root/assets/index-D_hrEKXM.css +0 -2
- package/dist/gateway/static/root/assets/local-app-workbench-page-DYPEUgpP.js +0 -2
- package/dist/gateway/static/root/assets/logs-page-B0P7rG2B.js +0 -2
- package/dist/gateway/static/root/assets/markdown-view-CLzTdVLW.css +0 -1
- package/dist/gateway/static/root/assets/notes-workbench-DmVYu_Oc.js +0 -4
- package/dist/gateway/static/root/assets/project-detail-page-DV2nGmsq.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-BNIFwFfc.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-DLXqKiA_.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-DWaXf_25.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-JFCbnCL2.js +0 -1
- package/dist/gateway/static/root/assets/share-preview-page-BOGl3s6M.js +0 -2
- package/dist/gateway/static/root/assets/shares-settings-Q2MdGaBi.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-cFtfVeFU.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-5B1pswu8.js +0 -1
- package/dist/gateway/static/root/assets/theme-store-DOdCjyuw.js +0 -1
- package/dist/gateway/static/root/assets/user-context-page-Dr1FKjXv.js +0 -1
- package/dist/gateway/static/root/assets/workflows-page-IVP3GSkI.js +0 -5
- package/dist/src/gateway/file-path-classifier.d.ts +0 -30
- package/dist/src/gateway/file-path-classifier.js +0 -105
- package/dist/src/gateway/file-reference-errors.d.ts +0 -6
- package/dist/src/gateway/file-reference-errors.js +0 -21
- package/dist/src/gateway/file-reference-registry.d.ts +0 -34
- package/dist/src/gateway/file-reference-registry.js +0 -45
- /package/dist/gateway/static/root/assets/{markdown-view-DvM5PWEH.js → markdown-view-RxNHwlNo.js} +0 -0
|
@@ -21,13 +21,13 @@ 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";
|
|
23
23
|
import { disposeAllSessionMcpRuntimes } from "../agent/mcp/bundle-mcp-runtime.js";
|
|
24
|
+
import { ProjectService } from "../projects/project-service.js";
|
|
25
|
+
import { resolveProjectAgentId } from "../projects/project-agent.js";
|
|
26
|
+
import "../projects/index.js";
|
|
24
27
|
import { resolveEffectiveGatewayPort } from "./host.js";
|
|
25
28
|
import { AutomationService } from "../automations/service/automation-service.js";
|
|
26
29
|
import { onAutomationProductEvent, publishAutomationProductEvent } from "../automations/product-events.js";
|
|
27
30
|
import "../automations/index.js";
|
|
28
|
-
import { ProjectService } from "../projects/project-service.js";
|
|
29
|
-
import { resolveProjectAgentId } from "../projects/project-agent.js";
|
|
30
|
-
import "../projects/index.js";
|
|
31
31
|
import { TaskApplicationService } from "../tasks/task-application-service.js";
|
|
32
32
|
import { ProactiveScenarioService } from "../proactive/scenarios/service.js";
|
|
33
33
|
import { TaskOutboxDispatcher } from "../tasks/task-outbox-dispatcher.js";
|
|
@@ -50,12 +50,6 @@ import { registerClarifyBridge } from "./clarify-runtime.js";
|
|
|
50
50
|
import { ChannelManager } from "../channels/manager.js";
|
|
51
51
|
import { getWorkspacePath } from "../config/workspace-path-helpers.js";
|
|
52
52
|
import "../config/index.js";
|
|
53
|
-
import { ManagedComposioEventPoller } from "../connectors/composio-managed-events.js";
|
|
54
|
-
import { startConnectedKnowledgeCoordinator } from "../knowledge/coordinator.js";
|
|
55
|
-
import "../knowledge/index.js";
|
|
56
|
-
import { startConnectorLearningCoordinator } from "../connectors/learning-coordinator.js";
|
|
57
|
-
import { ConnectedSourceChangePublisher } from "../connectors/source-change-publisher.js";
|
|
58
|
-
import { startConnectorSupervisor } from "../connectors/supervisor.js";
|
|
59
53
|
import { DiscussionService } from "../discussions/service.js";
|
|
60
54
|
import { DiscussionOrganizer } from "../discussions/organizer.js";
|
|
61
55
|
import { DiscussionLiveWorker } from "../discussions/live-worker.js";
|
|
@@ -64,6 +58,12 @@ import { DiscussionSealer } from "../discussions/sealer.js";
|
|
|
64
58
|
import "../discussions/index.js";
|
|
65
59
|
import { EndpointToolRuntime } from "../endpoint-tools/runtime.js";
|
|
66
60
|
import "../endpoint-tools/index.js";
|
|
61
|
+
import { ManagedComposioEventPoller } from "../connectors/composio-managed-events.js";
|
|
62
|
+
import { startConnectedKnowledgeCoordinator } from "../knowledge/coordinator.js";
|
|
63
|
+
import "../knowledge/index.js";
|
|
64
|
+
import { startConnectorLearningCoordinator } from "../connectors/learning-coordinator.js";
|
|
65
|
+
import { ConnectedSourceChangePublisher } from "../connectors/source-change-publisher.js";
|
|
66
|
+
import { startConnectorSupervisor } from "../connectors/supervisor.js";
|
|
67
67
|
import { assertGatewayAuthConfigured, extractToken, resolveGatewayAuth, validateToken } from "./auth.js";
|
|
68
68
|
import { prepareAutomationAgentSession } from "./automation-agent-session.js";
|
|
69
69
|
import { reconcileDreamingAutomations } from "./dreaming-automation-reconciler.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_ACK_MAX_CHARS, HEARTBEAT_OK, NO_REPLY, shouldSilence, stripHeartbeatToken } from "./tokens.js";
|
|
2
|
+
import { appendCronEventLines } from "./event-prompt.js";
|
|
2
3
|
import { isWithinActiveHours } from "./active-hours.js";
|
|
3
4
|
import { isHeartbeatContentEmpty } from "./content-check.js";
|
|
4
|
-
import { appendCronEventLines } from "./event-prompt.js";
|
|
5
5
|
import { createHeartbeatWake } from "./wake.js";
|
|
6
6
|
export { DEFAULT_ACK_MAX_CHARS, HEARTBEAT_OK, NO_REPLY, appendCronEventLines, createHeartbeatWake, isHeartbeatContentEmpty, isWithinActiveHours, shouldSilence, stripHeartbeatToken };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { CredentialResolver } from '../auth/credentials.js';
|
|
2
|
+
import { type SessionShareMessage, type SessionShareToolActivity } from './session-share-projector.js';
|
|
3
|
+
import { type SessionShareSource } from './session-share-service.js';
|
|
4
|
+
export interface HostedSessionShareManifest {
|
|
5
|
+
schemaVersion: 1;
|
|
6
|
+
title: string;
|
|
7
|
+
snapshotAt: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
messages: SessionShareMessage[];
|
|
10
|
+
toolActivities: SessionShareToolActivity[];
|
|
11
|
+
attachments: Array<{
|
|
12
|
+
id: string;
|
|
13
|
+
messageId: string;
|
|
14
|
+
fileName: string;
|
|
15
|
+
mimeType: string;
|
|
16
|
+
size: number;
|
|
17
|
+
sha256: string;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
export interface HostedSessionShareSnapshot {
|
|
21
|
+
sessionId: string;
|
|
22
|
+
cutoffSeq: number;
|
|
23
|
+
manifest: HostedSessionShareManifest;
|
|
24
|
+
assets: Array<{
|
|
25
|
+
id: string;
|
|
26
|
+
path: string;
|
|
27
|
+
size: number;
|
|
28
|
+
}>;
|
|
29
|
+
}
|
|
30
|
+
export interface BuildHostedSessionShareInput {
|
|
31
|
+
expectedSessionId: string;
|
|
32
|
+
expectedCutoffSeq: number;
|
|
33
|
+
expectedMetadataUpdatedAt: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
includeToolActivities?: boolean;
|
|
36
|
+
attachmentIds?: string[];
|
|
37
|
+
}
|
|
38
|
+
export declare class HostedSessionShareBuilder {
|
|
39
|
+
private readonly source;
|
|
40
|
+
constructor(source: SessionShareSource);
|
|
41
|
+
build(sessionKey: string, input: BuildHostedSessionShareInput): Promise<HostedSessionShareSnapshot>;
|
|
42
|
+
private loadExpectedSource;
|
|
43
|
+
}
|
|
44
|
+
export interface HostedShareResult {
|
|
45
|
+
id: string;
|
|
46
|
+
shareUrl: string;
|
|
47
|
+
expiresAt: string;
|
|
48
|
+
maxViews: number | null;
|
|
49
|
+
viewCount: number;
|
|
50
|
+
snapshotRevision: number;
|
|
51
|
+
}
|
|
52
|
+
type OwnerShare = {
|
|
53
|
+
id: string;
|
|
54
|
+
title: string;
|
|
55
|
+
description: string | null;
|
|
56
|
+
status: 'staging' | 'active' | 'revoked';
|
|
57
|
+
revision: number | null;
|
|
58
|
+
expiresAt: string;
|
|
59
|
+
maxViews: number | null;
|
|
60
|
+
viewCount: number;
|
|
61
|
+
createdAt: string;
|
|
62
|
+
updatedAt: string;
|
|
63
|
+
};
|
|
64
|
+
export declare class HostedSessionSharePublisher {
|
|
65
|
+
private readonly credentials;
|
|
66
|
+
private readonly fetchImpl;
|
|
67
|
+
private readonly baseUrl;
|
|
68
|
+
constructor(baseUrl?: string, credentials?: CredentialResolver, fetchImpl?: typeof fetch);
|
|
69
|
+
create(snapshot: HostedSessionShareSnapshot, lifecycle: {
|
|
70
|
+
ttlMs: number;
|
|
71
|
+
maxViews: number | null;
|
|
72
|
+
}): Promise<HostedShareResult>;
|
|
73
|
+
refresh(shareId: string, expectedRevision: number, publicUrl: string, snapshot: HostedSessionShareSnapshot): Promise<HostedShareResult>;
|
|
74
|
+
list(): Promise<OwnerShare[]>;
|
|
75
|
+
revoke(shareId: string): Promise<void>;
|
|
76
|
+
private uploadAssets;
|
|
77
|
+
private request;
|
|
78
|
+
}
|
|
79
|
+
export declare class HostedShareAuthorizationError extends Error {
|
|
80
|
+
constructor();
|
|
81
|
+
}
|
|
82
|
+
export interface HostedShareBinding extends HostedShareResult {
|
|
83
|
+
sessionId: string;
|
|
84
|
+
cutoffSeq: number;
|
|
85
|
+
title: string;
|
|
86
|
+
description: string | null;
|
|
87
|
+
messageCount: number;
|
|
88
|
+
attachmentCount: number;
|
|
89
|
+
includeToolActivities: boolean;
|
|
90
|
+
attachmentIds: string[];
|
|
91
|
+
createdAt: string;
|
|
92
|
+
updatedAt: string;
|
|
93
|
+
revoked: boolean;
|
|
94
|
+
}
|
|
95
|
+
export declare class HostedShareBindingStore {
|
|
96
|
+
private readonly path;
|
|
97
|
+
private pending;
|
|
98
|
+
list(sessionId: string): Promise<HostedShareBinding[]>;
|
|
99
|
+
upsert(binding: HostedShareBinding): Promise<void>;
|
|
100
|
+
reconcile(remote: OwnerShare[]): Promise<void>;
|
|
101
|
+
private mutate;
|
|
102
|
+
private read;
|
|
103
|
+
}
|
|
104
|
+
export {};
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { resolveStateDir } from "../config/paths-state.js";
|
|
2
|
+
import { init_paths } from "../config/paths.js";
|
|
3
|
+
import { init_write_file_atomic, writeTextAtomic } from "../infra/write-file-atomic.js";
|
|
4
|
+
import { CredentialResolver, init_credentials } from "../auth/credentials.js";
|
|
5
|
+
import { resolveMediaReference } from "../media/media-reference.js";
|
|
6
|
+
import { projectSessionShare } from "./session-share-projector.js";
|
|
7
|
+
import { SessionShareSnapshotConflictError } from "./session-share-service.js";
|
|
8
|
+
import { basename, dirname, join } from "node:path";
|
|
9
|
+
import { createReadStream } from "node:fs";
|
|
10
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
11
|
+
import { mkdir, readFile, stat } from "node:fs/promises";
|
|
12
|
+
//#region src/share/hosted-session-share.ts
|
|
13
|
+
init_credentials();
|
|
14
|
+
init_paths();
|
|
15
|
+
init_write_file_atomic();
|
|
16
|
+
const DEFAULT_SHARE_URL = "https://share.xopc.ai";
|
|
17
|
+
const MAX_MESSAGES = 1e4;
|
|
18
|
+
const MAX_ATTACHMENTS = 20;
|
|
19
|
+
const MAX_ATTACHMENT_BYTES = 32 * 1024 * 1024;
|
|
20
|
+
const MAX_TOTAL_ATTACHMENT_BYTES = 100 * 1024 * 1024;
|
|
21
|
+
const ALLOWED_MIME_TYPES = new Set([
|
|
22
|
+
"image/png",
|
|
23
|
+
"image/jpeg",
|
|
24
|
+
"image/gif",
|
|
25
|
+
"image/webp",
|
|
26
|
+
"audio/mpeg",
|
|
27
|
+
"audio/mp4",
|
|
28
|
+
"audio/wav",
|
|
29
|
+
"audio/ogg",
|
|
30
|
+
"audio/webm",
|
|
31
|
+
"video/mp4",
|
|
32
|
+
"video/webm",
|
|
33
|
+
"video/quicktime",
|
|
34
|
+
"application/pdf",
|
|
35
|
+
"text/plain",
|
|
36
|
+
"text/markdown"
|
|
37
|
+
]);
|
|
38
|
+
var HostedSessionShareBuilder = class {
|
|
39
|
+
constructor(source) {
|
|
40
|
+
this.source = source;
|
|
41
|
+
}
|
|
42
|
+
async build(sessionKey, input) {
|
|
43
|
+
const source = await this.loadExpectedSource(sessionKey, input);
|
|
44
|
+
const projection = projectSessionShare(source.snapshot.entries);
|
|
45
|
+
if (projection.messages.length === 0) throw new Error("Session has no shareable messages");
|
|
46
|
+
if (projection.messages.length > MAX_MESSAGES) throw new Error(`Session has more than ${MAX_MESSAGES} shareable messages`);
|
|
47
|
+
const selected = [...new Set(input.attachmentIds ?? [])];
|
|
48
|
+
if (selected.length > MAX_ATTACHMENTS) throw new Error(`Select at most ${MAX_ATTACHMENTS} attachments`);
|
|
49
|
+
const candidates = new Map(projection.attachmentCandidates.map((candidate) => [candidate.id, candidate]));
|
|
50
|
+
const attachments = [];
|
|
51
|
+
const assets = [];
|
|
52
|
+
let totalSize = 0;
|
|
53
|
+
for (const id of selected) {
|
|
54
|
+
const candidate = candidates.get(id);
|
|
55
|
+
const uri = projection.attachmentUris.get(id);
|
|
56
|
+
if (!candidate || !uri) throw new Error(`Attachment is not part of this session snapshot: ${id}`);
|
|
57
|
+
const mimeType = normalizeMimeType(candidate.mimeType);
|
|
58
|
+
if (!ALLOWED_MIME_TYPES.has(mimeType)) throw new Error(`Attachment type is not supported for hosted sharing: ${candidate.fileName}`);
|
|
59
|
+
const resolved = await resolveMediaReference(uri);
|
|
60
|
+
const sourceStat = await stat(resolved.path);
|
|
61
|
+
if (!sourceStat.isFile()) throw new Error(`Attachment is not a file: ${candidate.fileName}`);
|
|
62
|
+
if (sourceStat.size > MAX_ATTACHMENT_BYTES) throw new Error(`Attachment is too large: ${candidate.fileName}`);
|
|
63
|
+
totalSize += sourceStat.size;
|
|
64
|
+
if (totalSize > MAX_TOTAL_ATTACHMENT_BYTES) throw new Error("Selected attachments are too large");
|
|
65
|
+
attachments.push({
|
|
66
|
+
id,
|
|
67
|
+
messageId: candidate.messageId,
|
|
68
|
+
fileName: publicFileName(candidate.fileName),
|
|
69
|
+
mimeType,
|
|
70
|
+
size: sourceStat.size,
|
|
71
|
+
sha256: await checksumFile(resolved.path)
|
|
72
|
+
});
|
|
73
|
+
assets.push({
|
|
74
|
+
id,
|
|
75
|
+
path: resolved.path,
|
|
76
|
+
size: sourceStat.size
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const selectedIds = new Set(selected);
|
|
80
|
+
return {
|
|
81
|
+
sessionId: source.snapshot.sessionId,
|
|
82
|
+
cutoffSeq: source.snapshot.lastSeq,
|
|
83
|
+
manifest: {
|
|
84
|
+
schemaVersion: 1,
|
|
85
|
+
title: (source.metadata.name?.trim() || "Shared conversation").slice(0, 200),
|
|
86
|
+
snapshotAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
87
|
+
...input.description?.trim() ? { description: input.description.trim().slice(0, 1e3) } : {},
|
|
88
|
+
messages: projection.messages.map((message) => ({
|
|
89
|
+
...message,
|
|
90
|
+
attachmentIds: message.attachmentIds.filter((id) => selectedIds.has(id))
|
|
91
|
+
})),
|
|
92
|
+
toolActivities: input.includeToolActivities ? projection.toolActivities : [],
|
|
93
|
+
attachments
|
|
94
|
+
},
|
|
95
|
+
assets
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
async loadExpectedSource(sessionKey, expected) {
|
|
99
|
+
const [metadata, snapshot] = await Promise.all([this.source.getMetadata(sessionKey), this.source.getSnapshot(sessionKey)]);
|
|
100
|
+
if (!metadata || !snapshot || !metadata.sessionId || metadata.sessionId !== snapshot.sessionId) throw new Error("Session not found");
|
|
101
|
+
if (snapshot.sessionId !== expected.expectedSessionId || snapshot.lastSeq !== expected.expectedCutoffSeq || metadata.updatedAt !== expected.expectedMetadataUpdatedAt) throw new SessionShareSnapshotConflictError();
|
|
102
|
+
return {
|
|
103
|
+
metadata,
|
|
104
|
+
snapshot
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
var HostedSessionSharePublisher = class {
|
|
109
|
+
baseUrl;
|
|
110
|
+
constructor(baseUrl = process.env.XOPC_SHARE_URL ?? DEFAULT_SHARE_URL, credentials = new CredentialResolver(), fetchImpl = fetch) {
|
|
111
|
+
this.credentials = credentials;
|
|
112
|
+
this.fetchImpl = fetchImpl;
|
|
113
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
114
|
+
}
|
|
115
|
+
async create(snapshot, lifecycle) {
|
|
116
|
+
const publicToken = randomBytes(32).toString("base64url");
|
|
117
|
+
const draft = await this.request("/api/v1/session-shares", {
|
|
118
|
+
method: "POST",
|
|
119
|
+
headers: { "Idempotency-Key": `xopc-${randomUUID()}` },
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
publicToken,
|
|
122
|
+
manifest: snapshot.manifest,
|
|
123
|
+
...lifecycle
|
|
124
|
+
})
|
|
125
|
+
});
|
|
126
|
+
await this.uploadAssets(draft, snapshot.assets);
|
|
127
|
+
const finalized = await this.request(`/api/v1/session-shares/${encodeURIComponent(draft.shareId)}/uploads/${encodeURIComponent(draft.uploadId)}/finalize`, { method: "POST" });
|
|
128
|
+
if (!draft.publicUrl) throw new Error("Hosted Share did not return a public URL");
|
|
129
|
+
return toResult(finalized.item, draft.publicUrl);
|
|
130
|
+
}
|
|
131
|
+
async refresh(shareId, expectedRevision, publicUrl, snapshot) {
|
|
132
|
+
const draft = await this.request(`/api/v1/session-shares/${encodeURIComponent(shareId)}/revisions`, {
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers: { "Idempotency-Key": `xopc-${randomUUID()}` },
|
|
135
|
+
body: JSON.stringify({
|
|
136
|
+
expectedRevision,
|
|
137
|
+
manifest: snapshot.manifest
|
|
138
|
+
})
|
|
139
|
+
});
|
|
140
|
+
await this.uploadAssets(draft, snapshot.assets);
|
|
141
|
+
return toResult((await this.request(`/api/v1/session-shares/${encodeURIComponent(shareId)}/uploads/${encodeURIComponent(draft.uploadId)}/finalize`, { method: "POST" })).item, publicUrl);
|
|
142
|
+
}
|
|
143
|
+
async list() {
|
|
144
|
+
return (await this.request("/api/v1/session-shares")).items;
|
|
145
|
+
}
|
|
146
|
+
async revoke(shareId) {
|
|
147
|
+
await this.request(`/api/v1/session-shares/${encodeURIComponent(shareId)}`, { method: "DELETE" });
|
|
148
|
+
}
|
|
149
|
+
async uploadAssets(draft, assets) {
|
|
150
|
+
const uploads = new Map(draft.assetUploads.map((upload) => [upload.assetId, upload.uploadUrl]));
|
|
151
|
+
for (const asset of assets) {
|
|
152
|
+
const uploadUrl = uploads.get(asset.id);
|
|
153
|
+
if (!uploadUrl) throw new Error(`Hosted Share did not accept attachment: ${asset.id}`);
|
|
154
|
+
const bytes = await readFile(asset.path);
|
|
155
|
+
if (bytes.byteLength !== asset.size) throw new Error(`Attachment changed during upload: ${asset.id}`);
|
|
156
|
+
await this.request(uploadUrl, {
|
|
157
|
+
method: "PUT",
|
|
158
|
+
headers: {
|
|
159
|
+
"Content-Length": String(bytes.byteLength),
|
|
160
|
+
"Content-Type": "application/octet-stream"
|
|
161
|
+
},
|
|
162
|
+
body: bytes
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async request(path, init = {}) {
|
|
167
|
+
const token = await this.credentials.loadOAuthToken("xopc-share");
|
|
168
|
+
if (!token) throw new HostedShareAuthorizationError();
|
|
169
|
+
const url = new URL(path, `${this.baseUrl}/`);
|
|
170
|
+
if (url.origin !== new URL(this.baseUrl).origin) throw new Error("Hosted Share returned an invalid upload URL");
|
|
171
|
+
const response = await this.fetchImpl(url, {
|
|
172
|
+
...init,
|
|
173
|
+
headers: {
|
|
174
|
+
Accept: "application/json",
|
|
175
|
+
...init.body && !init.headers?.["Content-Type"] ? { "Content-Type": "application/json" } : {},
|
|
176
|
+
...init.headers,
|
|
177
|
+
Authorization: `Bearer ${token.access}`
|
|
178
|
+
},
|
|
179
|
+
signal: init.signal ?? AbortSignal.timeout(12e4)
|
|
180
|
+
});
|
|
181
|
+
const body = await response.json().catch(() => ({}));
|
|
182
|
+
if (response.status === 401) throw new HostedShareAuthorizationError();
|
|
183
|
+
if (!response.ok) throw new Error(body.error?.message || `Hosted Share request failed (${response.status})`);
|
|
184
|
+
return body;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
var HostedShareAuthorizationError = class extends Error {
|
|
188
|
+
constructor() {
|
|
189
|
+
super("Connect XOPC Hosted Share before publishing");
|
|
190
|
+
this.name = "HostedShareAuthorizationError";
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
var HostedShareBindingStore = class {
|
|
194
|
+
path = join(resolveStateDir(), "hosted-share-bindings.json");
|
|
195
|
+
pending = Promise.resolve();
|
|
196
|
+
async list(sessionId) {
|
|
197
|
+
return (await this.read()).items.filter((item) => item.sessionId === sessionId).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
198
|
+
}
|
|
199
|
+
async upsert(binding) {
|
|
200
|
+
await this.mutate((file) => {
|
|
201
|
+
const index = file.items.findIndex((item) => item.id === binding.id);
|
|
202
|
+
if (index === -1) file.items.push(binding);
|
|
203
|
+
else file.items[index] = binding;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
async reconcile(remote) {
|
|
207
|
+
const byId = new Map(remote.map((item) => [item.id, item]));
|
|
208
|
+
await this.mutate((file) => {
|
|
209
|
+
file.items = file.items.map((binding) => {
|
|
210
|
+
const item = byId.get(binding.id);
|
|
211
|
+
return item ? {
|
|
212
|
+
...binding,
|
|
213
|
+
expiresAt: item.expiresAt,
|
|
214
|
+
maxViews: item.maxViews,
|
|
215
|
+
viewCount: item.viewCount,
|
|
216
|
+
snapshotRevision: item.revision ?? binding.snapshotRevision,
|
|
217
|
+
title: item.title,
|
|
218
|
+
description: item.description,
|
|
219
|
+
updatedAt: item.updatedAt,
|
|
220
|
+
revoked: item.status === "revoked"
|
|
221
|
+
} : binding;
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
async mutate(update) {
|
|
226
|
+
const operation = this.pending.then(async () => {
|
|
227
|
+
const file = await this.read();
|
|
228
|
+
update(file);
|
|
229
|
+
await mkdir(dirname(this.path), { recursive: true });
|
|
230
|
+
await writeTextAtomic(this.path, `${JSON.stringify(file, null, 2)}\n`);
|
|
231
|
+
});
|
|
232
|
+
this.pending = operation.catch(() => void 0);
|
|
233
|
+
await operation;
|
|
234
|
+
}
|
|
235
|
+
async read() {
|
|
236
|
+
try {
|
|
237
|
+
const parsed = JSON.parse(await readFile(this.path, "utf8"));
|
|
238
|
+
return parsed.version === 1 && Array.isArray(parsed.items) ? {
|
|
239
|
+
version: 1,
|
|
240
|
+
items: parsed.items
|
|
241
|
+
} : {
|
|
242
|
+
version: 1,
|
|
243
|
+
items: []
|
|
244
|
+
};
|
|
245
|
+
} catch {
|
|
246
|
+
return {
|
|
247
|
+
version: 1,
|
|
248
|
+
items: []
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
function toResult(item, shareUrl) {
|
|
254
|
+
if (!item.revision) throw new Error("Hosted Share revision was not activated");
|
|
255
|
+
return {
|
|
256
|
+
id: item.id,
|
|
257
|
+
shareUrl,
|
|
258
|
+
expiresAt: item.expiresAt,
|
|
259
|
+
maxViews: item.maxViews,
|
|
260
|
+
viewCount: item.viewCount,
|
|
261
|
+
snapshotRevision: item.revision
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function normalizeMimeType(value) {
|
|
265
|
+
return value.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
266
|
+
}
|
|
267
|
+
function publicFileName(value) {
|
|
268
|
+
return basename(value.replaceAll("\\", "/")).replace(/[\u0000-\u001f\u007f]/g, "_").trim().slice(0, 200) || "attachment";
|
|
269
|
+
}
|
|
270
|
+
async function checksumFile(path) {
|
|
271
|
+
const hash = createHash("sha256");
|
|
272
|
+
for await (const chunk of createReadStream(path)) hash.update(chunk);
|
|
273
|
+
return hash.digest("hex");
|
|
274
|
+
}
|
|
275
|
+
//#endregion
|
|
276
|
+
export { HostedSessionShareBuilder, HostedSessionSharePublisher, HostedShareAuthorizationError, HostedShareBindingStore };
|
|
@@ -3,8 +3,9 @@ import { createLogger } from "../utils/logger/index.js";
|
|
|
3
3
|
import { init_logger } from "../utils/logger.js";
|
|
4
4
|
import { init_paths } from "../config/paths.js";
|
|
5
5
|
import { collectReferencedAttachmentIds } from "../notes/note-attachment-sync.js";
|
|
6
|
+
import { issueShareAssetTicket, verifyShareAssetTicket } from "./share-asset-ticket.js";
|
|
6
7
|
import { dirname, join } from "node:path";
|
|
7
|
-
import { createHash,
|
|
8
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
8
9
|
import { copyFile, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
9
10
|
//#region src/share/note-share-service.ts
|
|
10
11
|
init_paths();
|
|
@@ -113,31 +114,10 @@ var NoteShareService = class {
|
|
|
113
114
|
return manifest;
|
|
114
115
|
}
|
|
115
116
|
issueAssetTicket(record) {
|
|
116
|
-
|
|
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")}`;
|
|
117
|
+
return issueShareAssetTicket(record, this.store.getConfig().note.assetTicketTtlMs);
|
|
123
118
|
}
|
|
124
119
|
verifyAssetTicket(record, ticket) {
|
|
125
|
-
|
|
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
|
-
}
|
|
120
|
+
return verifyShareAssetTicket(record, ticket);
|
|
141
121
|
}
|
|
142
122
|
async resolveAsset(record, attachmentId) {
|
|
143
123
|
const attachment = (await this.readManifest(record)).attachments.find((item) => item.id === attachmentId);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { TranscriptSourceEntry } from '../storage/sqlite/index.js';
|
|
2
|
+
export interface SessionShareMessage {
|
|
3
|
+
id: string;
|
|
4
|
+
role: 'user' | 'assistant';
|
|
5
|
+
markdown: string;
|
|
6
|
+
createdAt: string;
|
|
7
|
+
attachmentIds: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface SessionShareToolActivity {
|
|
10
|
+
id: string;
|
|
11
|
+
messageId?: string;
|
|
12
|
+
toolName: string;
|
|
13
|
+
status: 'completed' | 'failed';
|
|
14
|
+
createdAt: string;
|
|
15
|
+
}
|
|
16
|
+
export interface SessionShareAttachmentCandidate {
|
|
17
|
+
id: string;
|
|
18
|
+
messageId: string;
|
|
19
|
+
fileName: string;
|
|
20
|
+
mimeType: string;
|
|
21
|
+
size: number;
|
|
22
|
+
}
|
|
23
|
+
export interface SessionShareProjection {
|
|
24
|
+
messages: SessionShareMessage[];
|
|
25
|
+
toolActivities: SessionShareToolActivity[];
|
|
26
|
+
attachmentCandidates: SessionShareAttachmentCandidate[];
|
|
27
|
+
/** Internal lookup used only while copying explicitly selected media. */
|
|
28
|
+
attachmentUris: Map<string, string>;
|
|
29
|
+
}
|
|
30
|
+
/** Closed public projection. Tool arguments/results and raw media references never leave this boundary. */
|
|
31
|
+
export declare function projectSessionShare(entries: TranscriptSourceEntry[]): SessionShareProjection;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { transcriptRowsToClientHistory } from "../session/client-history.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
//#region src/share/session-share-projector.ts
|
|
4
|
+
/** Closed public projection. Tool arguments/results and raw media references never leave this boundary. */
|
|
5
|
+
function projectSessionShare(entries) {
|
|
6
|
+
const history = transcriptRowsToClientHistory(entries.map((entry) => entry.row));
|
|
7
|
+
const messages = [];
|
|
8
|
+
const toolActivities = [];
|
|
9
|
+
const attachmentCandidates = [];
|
|
10
|
+
const attachmentUris = /* @__PURE__ */ new Map();
|
|
11
|
+
for (const item of history) {
|
|
12
|
+
const rowNumber = parseRowNumber(item.id);
|
|
13
|
+
const source = rowNumber === null ? void 0 : entries[rowNumber - 1];
|
|
14
|
+
const createdAt = new Date(source?.createdAt ?? item.timestamp ?? Date.now()).toISOString();
|
|
15
|
+
if (item.kind === "bash") {
|
|
16
|
+
if (item.bash?.exitCode === void 0) continue;
|
|
17
|
+
toolActivities.push({
|
|
18
|
+
id: `activity-${toolActivities.length + 1}`,
|
|
19
|
+
toolName: "shell",
|
|
20
|
+
status: item.bash?.exitCode === 0 ? "completed" : "failed",
|
|
21
|
+
createdAt
|
|
22
|
+
});
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (item.kind !== "message" || item.role !== "user" && item.role !== "assistant") continue;
|
|
26
|
+
const messageId = `message-${messages.length + 1}`;
|
|
27
|
+
const messageAttachmentIds = [];
|
|
28
|
+
for (const media of item.media ?? []) {
|
|
29
|
+
const uri = typeof media.uri === "string" ? media.uri.trim() : "";
|
|
30
|
+
if (!uri) continue;
|
|
31
|
+
const id = attachmentId(messageId, uri);
|
|
32
|
+
messageAttachmentIds.push(id);
|
|
33
|
+
if (attachmentUris.has(id)) continue;
|
|
34
|
+
attachmentUris.set(id, uri);
|
|
35
|
+
attachmentCandidates.push({
|
|
36
|
+
id,
|
|
37
|
+
messageId,
|
|
38
|
+
fileName: media.name?.trim() || "attachment",
|
|
39
|
+
mimeType: media.mimeType?.trim() || "application/octet-stream",
|
|
40
|
+
size: Number.isFinite(media.size) && media.size >= 0 ? media.size : 0
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const hasMessage = Boolean(item.content.trim()) || messageAttachmentIds.length > 0;
|
|
44
|
+
if (hasMessage) messages.push({
|
|
45
|
+
id: messageId,
|
|
46
|
+
role: item.role,
|
|
47
|
+
markdown: item.content,
|
|
48
|
+
createdAt,
|
|
49
|
+
attachmentIds: messageAttachmentIds
|
|
50
|
+
});
|
|
51
|
+
if (item.role === "assistant") for (const tool of item.toolCalls ?? []) {
|
|
52
|
+
if (tool.result === void 0 && tool.isError === void 0) continue;
|
|
53
|
+
toolActivities.push({
|
|
54
|
+
id: `activity-${toolActivities.length + 1}`,
|
|
55
|
+
...hasMessage ? { messageId } : {},
|
|
56
|
+
toolName: tool.name.trim().slice(0, 100) || "tool",
|
|
57
|
+
status: tool.isError ? "failed" : "completed",
|
|
58
|
+
createdAt
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
messages,
|
|
64
|
+
toolActivities,
|
|
65
|
+
attachmentCandidates,
|
|
66
|
+
attachmentUris
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function attachmentId(messageId, uri) {
|
|
70
|
+
return `attachment-${createHash("sha256").update(messageId).update("\0").update(uri).digest("hex").slice(0, 24)}`;
|
|
71
|
+
}
|
|
72
|
+
function parseRowNumber(id) {
|
|
73
|
+
const match = /^row-(\d+)$/.exec(id ?? "");
|
|
74
|
+
if (!match) return null;
|
|
75
|
+
const value = Number(match[1]);
|
|
76
|
+
return Number.isSafeInteger(value) && value > 0 ? value : null;
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
export { projectSessionShare };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { SessionMetadata } from '../session/types.js';
|
|
2
|
+
import type { CompactionSourceSnapshot } from '../storage/sqlite/index.js';
|
|
3
|
+
import { type SessionShareAttachmentCandidate, type SessionShareMessage, type SessionShareToolActivity } from './session-share-projector.js';
|
|
4
|
+
import type { ShareStore } from './share-store.js';
|
|
5
|
+
import type { SessionShareRecord } from './share-types.js';
|
|
6
|
+
export interface SessionShareManifestAttachment {
|
|
7
|
+
id: string;
|
|
8
|
+
messageId: string;
|
|
9
|
+
mimeType: string;
|
|
10
|
+
fileName: string;
|
|
11
|
+
size: number;
|
|
12
|
+
artifactFileName: string;
|
|
13
|
+
checksum: string;
|
|
14
|
+
}
|
|
15
|
+
export interface SessionShareManifest {
|
|
16
|
+
schemaVersion: 1;
|
|
17
|
+
shareId: string;
|
|
18
|
+
source: {
|
|
19
|
+
sessionId: string;
|
|
20
|
+
cutoffSeq: number;
|
|
21
|
+
};
|
|
22
|
+
title: string;
|
|
23
|
+
snapshotAt: string;
|
|
24
|
+
messages: SessionShareMessage[];
|
|
25
|
+
toolActivities: SessionShareToolActivity[];
|
|
26
|
+
attachments: SessionShareManifestAttachment[];
|
|
27
|
+
}
|
|
28
|
+
export interface SessionShareSource {
|
|
29
|
+
getMetadata(sessionKey: string): Promise<SessionMetadata | null>;
|
|
30
|
+
getSnapshot(sessionKey: string): Promise<CompactionSourceSnapshot | null>;
|
|
31
|
+
}
|
|
32
|
+
export interface SessionSharePreview {
|
|
33
|
+
sessionId: string;
|
|
34
|
+
cutoffSeq: number;
|
|
35
|
+
metadataUpdatedAt: string;
|
|
36
|
+
title: string;
|
|
37
|
+
snapshotAt: string;
|
|
38
|
+
messageCount: number;
|
|
39
|
+
messages: SessionShareMessage[];
|
|
40
|
+
toolActivities: SessionShareToolActivity[];
|
|
41
|
+
attachmentCandidates: SessionShareAttachmentCandidate[];
|
|
42
|
+
}
|
|
43
|
+
export interface CreateSessionShareInput {
|
|
44
|
+
expectedSessionId: string;
|
|
45
|
+
expectedCutoffSeq: number;
|
|
46
|
+
expectedMetadataUpdatedAt: string;
|
|
47
|
+
includeToolActivities?: boolean;
|
|
48
|
+
attachmentIds?: string[];
|
|
49
|
+
ttlMs?: number;
|
|
50
|
+
maxViews?: number | null;
|
|
51
|
+
description?: string;
|
|
52
|
+
gatewayTokenHash: string;
|
|
53
|
+
}
|
|
54
|
+
export interface RefreshSessionShareInput {
|
|
55
|
+
expectedSessionId: string;
|
|
56
|
+
expectedCutoffSeq: number;
|
|
57
|
+
expectedMetadataUpdatedAt: string;
|
|
58
|
+
includeToolActivities?: boolean;
|
|
59
|
+
attachmentIds?: string[];
|
|
60
|
+
}
|
|
61
|
+
export declare class SessionShareSnapshotConflictError extends Error {
|
|
62
|
+
constructor();
|
|
63
|
+
}
|
|
64
|
+
export declare class SessionShareService {
|
|
65
|
+
private readonly store;
|
|
66
|
+
private readonly source;
|
|
67
|
+
constructor(store: ShareStore, source: SessionShareSource);
|
|
68
|
+
preview(sessionKey: string): Promise<SessionSharePreview>;
|
|
69
|
+
create(sessionKey: string, input: CreateSessionShareInput): Promise<SessionShareRecord>;
|
|
70
|
+
refresh(sessionKey: string, shareId: string, input: RefreshSessionShareInput): Promise<SessionShareRecord>;
|
|
71
|
+
list(sessionId: string): SessionShareRecord[];
|
|
72
|
+
readManifest(record: SessionShareRecord): Promise<SessionShareManifest>;
|
|
73
|
+
issueAssetTicket(record: SessionShareRecord): string;
|
|
74
|
+
verifyAssetTicket(record: SessionShareRecord, ticket: string): boolean;
|
|
75
|
+
resolveAsset(record: SessionShareRecord, attachmentId: string): Promise<{
|
|
76
|
+
path: string;
|
|
77
|
+
attachment: SessionShareManifestAttachment;
|
|
78
|
+
} | null>;
|
|
79
|
+
removeArtifact(shareId: string): Promise<void>;
|
|
80
|
+
private loadExpectedSource;
|
|
81
|
+
private loadSource;
|
|
82
|
+
private buildManifestBase;
|
|
83
|
+
private createArtifact;
|
|
84
|
+
private writeArtifact;
|
|
85
|
+
private artifactRelativePath;
|
|
86
|
+
private artifactDir;
|
|
87
|
+
private validateMessages;
|
|
88
|
+
private validateManifestRelations;
|
|
89
|
+
}
|