@xenosystem/agent-interface-ui 0.1.36 → 0.1.37
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/adapters/agentHostPlatformBridge.d.ts.map +1 -1
- package/dist/adapters/agentHostPlatformBridge.js +21 -1
- package/dist/adapters/agentHostPlatformBridge.js.map +1 -1
- package/dist/agent.css +2180 -7
- package/dist/components/agent/AgentAuthGate.d.ts +86 -0
- package/dist/components/agent/AgentAuthGate.d.ts.map +1 -0
- package/dist/components/agent/AgentAuthGate.js +189 -0
- package/dist/components/agent/AgentAuthGate.js.map +1 -0
- package/dist/components/agent/AgentInterface.d.ts +8 -1
- package/dist/components/agent/AgentInterface.d.ts.map +1 -1
- package/dist/components/agent/AgentInterface.js +27 -8
- package/dist/components/agent/AgentInterface.js.map +1 -1
- package/dist/components/agent/settings/sections/AccountWorkspacePanel.d.ts.map +1 -1
- package/dist/components/agent/settings/sections/AccountWorkspacePanel.js +37 -6
- package/dist/components/agent/settings/sections/AccountWorkspacePanel.js.map +1 -1
- package/dist/components/agent/settings/sections/WorkspaceDirectoryAccessSection.d.ts.map +1 -1
- package/dist/components/agent/settings/sections/WorkspaceDirectoryAccessSection.js +1 -0
- package/dist/components/agent/settings/sections/WorkspaceDirectoryAccessSection.js.map +1 -1
- package/dist/components/product/XenoAgentProductHeader.d.ts +2 -1
- package/dist/components/product/XenoAgentProductHeader.d.ts.map +1 -1
- package/dist/components/product/XenoAgentProductHeader.js +2 -2
- package/dist/components/product/XenoAgentProductHeader.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -4
- package/dist/index.js.map +1 -1
- package/dist/stores/workspaceStore.d.ts.map +1 -1
- package/dist/stores/workspaceStore.js +1 -0
- package/dist/stores/workspaceStore.js.map +1 -1
- package/package.json +8 -7
- package/src/agent.css +5 -0
- package/src/fonts/inter/OFL.txt +92 -0
- package/src/fonts/inter/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2 +0 -0
- package/src/fonts/inter/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2 +0 -0
- package/src/fonts/inter/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2 +0 -0
- package/src/fonts/inter/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2 +0 -0
- package/src/fonts/inter/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2 +0 -0
- package/src/fonts/inter/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2 +0 -0
- package/src/fonts/inter/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2 +0 -0
- package/src/fonts/inter.css +75 -0
- package/dist/adapters/xenoAgentCompatibilityBridge.d.ts +0 -14
- package/dist/adapters/xenoAgentCompatibilityBridge.d.ts.map +0 -1
- package/dist/adapters/xenoAgentCompatibilityBridge.js +0 -247
- package/dist/adapters/xenoAgentCompatibilityBridge.js.map +0 -1
- package/dist/adapters/xenoAgentHostCatalog.d.ts +0 -40
- package/dist/adapters/xenoAgentHostCatalog.d.ts.map +0 -1
- package/dist/adapters/xenoAgentHostCatalog.js +0 -231
- package/dist/adapters/xenoAgentHostCatalog.js.map +0 -1
- package/dist/components/agent/AIStatusIndicator.d.ts +0 -25
- package/dist/components/agent/AIStatusIndicator.d.ts.map +0 -1
- package/dist/components/agent/AIStatusIndicator.js +0 -85
- package/dist/components/agent/AIStatusIndicator.js.map +0 -1
- package/dist/components/agent/AgentNotificationBanner.d.ts +0 -10
- package/dist/components/agent/AgentNotificationBanner.d.ts.map +0 -1
- package/dist/components/agent/AgentNotificationBanner.js +0 -28
- package/dist/components/agent/AgentNotificationBanner.js.map +0 -1
- package/dist/components/agent/AgentView.d.ts +0 -185
- package/dist/components/agent/AgentView.d.ts.map +0 -1
- package/dist/components/agent/AgentView.js +0 -7850
- package/dist/components/agent/AgentView.js.map +0 -1
- package/dist/components/agent/AssistantTurnStatusNotice.d.ts +0 -16
- package/dist/components/agent/AssistantTurnStatusNotice.d.ts.map +0 -1
- package/dist/components/agent/AssistantTurnStatusNotice.js +0 -54
- package/dist/components/agent/AssistantTurnStatusNotice.js.map +0 -1
- package/dist/components/agent/CommandToolCard.d.ts +0 -11
- package/dist/components/agent/CommandToolCard.d.ts.map +0 -1
- package/dist/components/agent/CommandToolCard.js +0 -29
- package/dist/components/agent/CommandToolCard.js.map +0 -1
- package/dist/components/agent/GhostTextArea.d.ts +0 -13
- package/dist/components/agent/GhostTextArea.d.ts.map +0 -1
- package/dist/components/agent/GhostTextArea.js +0 -21
- package/dist/components/agent/GhostTextArea.js.map +0 -1
- package/dist/components/agent/LiveConversationCollaboration.d.ts +0 -27
- package/dist/components/agent/LiveConversationCollaboration.d.ts.map +0 -1
- package/dist/components/agent/LiveConversationCollaboration.js +0 -127
- package/dist/components/agent/LiveConversationCollaboration.js.map +0 -1
- package/dist/components/agent/SdkSessionPanel.d.ts +0 -25
- package/dist/components/agent/SdkSessionPanel.d.ts.map +0 -1
- package/dist/components/agent/SdkSessionPanel.js +0 -15
- package/dist/components/agent/SdkSessionPanel.js.map +0 -1
- package/dist/components/agent/SlashCommandAutocomplete.d.ts +0 -12
- package/dist/components/agent/SlashCommandAutocomplete.d.ts.map +0 -1
- package/dist/components/agent/SlashCommandAutocomplete.js +0 -33
- package/dist/components/agent/SlashCommandAutocomplete.js.map +0 -1
- package/dist/components/agent/collaborationWindow.d.ts +0 -11
- package/dist/components/agent/collaborationWindow.d.ts.map +0 -1
- package/dist/components/agent/collaborationWindow.js +0 -10
- package/dist/components/agent/collaborationWindow.js.map +0 -1
- package/dist/components/agent/conversationCollaborationAppendJournal.d.ts +0 -25
- package/dist/components/agent/conversationCollaborationAppendJournal.d.ts.map +0 -1
- package/dist/components/agent/conversationCollaborationAppendJournal.js +0 -118
- package/dist/components/agent/conversationCollaborationAppendJournal.js.map +0 -1
- package/dist/components/agent/conversationCollaborationController.d.ts +0 -96
- package/dist/components/agent/conversationCollaborationController.d.ts.map +0 -1
- package/dist/components/agent/conversationCollaborationController.js +0 -597
- package/dist/components/agent/conversationCollaborationController.js.map +0 -1
- package/dist/components/agent/destructiveConfirmation.d.ts +0 -76
- package/dist/components/agent/destructiveConfirmation.d.ts.map +0 -1
- package/dist/components/agent/destructiveConfirmation.js +0 -231
- package/dist/components/agent/destructiveConfirmation.js.map +0 -1
- package/dist/components/agent/editorOperationAuthority.d.ts +0 -29
- package/dist/components/agent/editorOperationAuthority.d.ts.map +0 -1
- package/dist/components/agent/editorOperationAuthority.js +0 -73
- package/dist/components/agent/editorOperationAuthority.js.map +0 -1
- package/dist/components/agent/mainAssistant.d.ts +0 -24
- package/dist/components/agent/mainAssistant.d.ts.map +0 -1
- package/dist/components/agent/mainAssistant.js +0 -24
- package/dist/components/agent/mainAssistant.js.map +0 -1
- package/dist/components/agent/nativeTuiComposerInput.d.ts +0 -42
- package/dist/components/agent/nativeTuiComposerInput.d.ts.map +0 -1
- package/dist/components/agent/nativeTuiComposerInput.js +0 -51
- package/dist/components/agent/nativeTuiComposerInput.js.map +0 -1
- package/dist/components/agent/settings/shared/InAppConfirmationDialog.d.ts +0 -25
- package/dist/components/agent/settings/shared/InAppConfirmationDialog.d.ts.map +0 -1
- package/dist/components/agent/settings/shared/InAppConfirmationDialog.js +0 -89
- package/dist/components/agent/settings/shared/InAppConfirmationDialog.js.map +0 -1
- package/dist/components/agent/toolCardDisplay.d.ts +0 -24
- package/dist/components/agent/toolCardDisplay.d.ts.map +0 -1
- package/dist/components/agent/toolCardDisplay.js +0 -85
- package/dist/components/agent/toolCardDisplay.js.map +0 -1
- package/dist/components/agent/webJobProgressView.d.ts +0 -90
- package/dist/components/agent/webJobProgressView.d.ts.map +0 -1
- package/dist/components/agent/webJobProgressView.js +0 -104
- package/dist/components/agent/webJobProgressView.js.map +0 -1
- package/dist/components/ui/CodeBlock.d.ts +0 -8
- package/dist/components/ui/CodeBlock.d.ts.map +0 -1
- package/dist/components/ui/CodeBlock.js +0 -89
- package/dist/components/ui/CodeBlock.js.map +0 -1
- package/dist/platformApiTypes.d.ts +0 -4176
- package/dist/platformApiTypes.d.ts.map +0 -1
- package/dist/platformApiTypes.js +0 -2
- package/dist/platformApiTypes.js.map +0 -1
- package/dist/platformBridge.d.ts +0 -43
- package/dist/platformBridge.d.ts.map +0 -1
- package/dist/platformBridge.js +0 -67
- package/dist/platformBridge.js.map +0 -1
- package/dist/providerSelector.d.ts +0 -28
- package/dist/providerSelector.d.ts.map +0 -1
- package/dist/providerSelector.js +0 -104
- package/dist/providerSelector.js.map +0 -1
- package/dist/services/connectionMode.d.ts +0 -13
- package/dist/services/connectionMode.d.ts.map +0 -1
- package/dist/services/connectionMode.js +0 -31
- package/dist/services/connectionMode.js.map +0 -1
- package/dist/sessionTabs.d.ts +0 -48
- package/dist/sessionTabs.d.ts.map +0 -1
- package/dist/sessionTabs.js +0 -106
- package/dist/sessionTabs.js.map +0 -1
- package/dist/stores/acpAgentStore.d.ts +0 -264
- package/dist/stores/acpAgentStore.d.ts.map +0 -1
- package/dist/stores/acpAgentStore.js +0 -825
- package/dist/stores/acpAgentStore.js.map +0 -1
- package/dist/stores/agentChatStore.d.ts +0 -693
- package/dist/stores/agentChatStore.d.ts.map +0 -1
- package/dist/stores/agentChatStore.js +0 -5876
- package/dist/stores/agentChatStore.js.map +0 -1
- package/dist/stores/authStore.d.ts +0 -26
- package/dist/stores/authStore.d.ts.map +0 -1
- package/dist/stores/authStore.js +0 -163
- package/dist/stores/authStore.js.map +0 -1
- package/dist/stores/suggestionStore.d.ts +0 -36
- package/dist/stores/suggestionStore.d.ts.map +0 -1
- package/dist/stores/suggestionStore.js +0 -187
- package/dist/stores/suggestionStore.js.map +0 -1
- package/dist/stores/tabStore.d.ts +0 -30
- package/dist/stores/tabStore.d.ts.map +0 -1
- package/dist/stores/tabStore.js +0 -252
- package/dist/stores/tabStore.js.map +0 -1
- package/dist/stores/xenoChatStore.d.ts +0 -88
- package/dist/stores/xenoChatStore.d.ts.map +0 -1
- package/dist/stores/xenoChatStore.js +0 -583
- package/dist/stores/xenoChatStore.js.map +0 -1
- package/dist/types/agentEditor.d.ts +0 -40
- package/dist/types/agentEditor.d.ts.map +0 -1
- package/dist/types/agentEditor.js +0 -2
- package/dist/types/agentEditor.js.map +0 -1
- package/dist/types/agentRuntimeProvider.d.ts +0 -2
- package/dist/types/agentRuntimeProvider.d.ts.map +0 -1
- package/dist/types/agentRuntimeProvider.js +0 -2
- package/dist/types/agentRuntimeProvider.js.map +0 -1
- package/dist/utils/acpExecutionAuthority.d.ts +0 -26
- package/dist/utils/acpExecutionAuthority.d.ts.map +0 -1
- package/dist/utils/acpExecutionAuthority.js +0 -46
- package/dist/utils/acpExecutionAuthority.js.map +0 -1
- package/dist/utils/advertisedReasoning.d.ts +0 -25
- package/dist/utils/advertisedReasoning.d.ts.map +0 -1
- package/dist/utils/advertisedReasoning.js +0 -62
- package/dist/utils/advertisedReasoning.js.map +0 -1
- package/dist/utils/agentAttachedContextPrompt.d.ts +0 -2
- package/dist/utils/agentAttachedContextPrompt.d.ts.map +0 -1
- package/dist/utils/agentAttachedContextPrompt.js +0 -2
- package/dist/utils/agentAttachedContextPrompt.js.map +0 -1
- package/dist/utils/agentModelContext.d.ts +0 -2
- package/dist/utils/agentModelContext.d.ts.map +0 -1
- package/dist/utils/agentModelContext.js +0 -2
- package/dist/utils/agentModelContext.js.map +0 -1
- package/dist/utils/agentReasoningEffort.d.ts +0 -3
- package/dist/utils/agentReasoningEffort.d.ts.map +0 -1
- package/dist/utils/agentReasoningEffort.js +0 -2
- package/dist/utils/agentReasoningEffort.js.map +0 -1
- package/dist/utils/agentRuntimeProvider.d.ts +0 -3
- package/dist/utils/agentRuntimeProvider.d.ts.map +0 -1
- package/dist/utils/agentRuntimeProvider.js +0 -2
- package/dist/utils/agentRuntimeProvider.js.map +0 -1
- package/dist/utils/clipboardCopy.d.ts +0 -4
- package/dist/utils/clipboardCopy.d.ts.map +0 -1
- package/dist/utils/clipboardCopy.js +0 -27
- package/dist/utils/clipboardCopy.js.map +0 -1
- package/dist/utils/cloudModels.d.ts +0 -3
- package/dist/utils/cloudModels.d.ts.map +0 -1
- package/dist/utils/cloudModels.js +0 -181
- package/dist/utils/cloudModels.js.map +0 -1
- package/dist/utils/conversationRuntime.d.ts +0 -3
- package/dist/utils/conversationRuntime.d.ts.map +0 -1
- package/dist/utils/conversationRuntime.js +0 -2
- package/dist/utils/conversationRuntime.js.map +0 -1
- package/dist/utils/fileCreateIntent.d.ts +0 -16
- package/dist/utils/fileCreateIntent.d.ts.map +0 -1
- package/dist/utils/fileCreateIntent.js +0 -123
- package/dist/utils/fileCreateIntent.js.map +0 -1
- package/dist/utils/legacyAgentRuntimeEvents.d.ts +0 -2
- package/dist/utils/legacyAgentRuntimeEvents.d.ts.map +0 -1
- package/dist/utils/legacyAgentRuntimeEvents.js +0 -2
- package/dist/utils/legacyAgentRuntimeEvents.js.map +0 -1
- package/dist/utils/markdownWindowsPaths.d.ts +0 -2
- package/dist/utils/markdownWindowsPaths.d.ts.map +0 -1
- package/dist/utils/markdownWindowsPaths.js +0 -2
- package/dist/utils/markdownWindowsPaths.js.map +0 -1
- package/dist/utils/modelAvailability.d.ts +0 -11
- package/dist/utils/modelAvailability.d.ts.map +0 -1
- package/dist/utils/modelAvailability.js +0 -10
- package/dist/utils/modelAvailability.js.map +0 -1
- package/dist/utils/providerAvailability.d.ts +0 -8
- package/dist/utils/providerAvailability.d.ts.map +0 -1
- package/dist/utils/providerAvailability.js +0 -13
- package/dist/utils/providerAvailability.js.map +0 -1
- package/dist/utils/providerCommandArgs.d.ts +0 -11
- package/dist/utils/providerCommandArgs.d.ts.map +0 -1
- package/dist/utils/providerCommandArgs.js +0 -18
- package/dist/utils/providerCommandArgs.js.map +0 -1
- package/dist/utils/workspaceSelectionContinuation.d.ts +0 -2
- package/dist/utils/workspaceSelectionContinuation.d.ts.map +0 -1
- package/dist/utils/workspaceSelectionContinuation.js +0 -2
- package/dist/utils/workspaceSelectionContinuation.js.map +0 -1
|
@@ -1,597 +0,0 @@
|
|
|
1
|
-
import { CollaborationAppendJournalError, prepareCollaborationAppend, recoverCollaborationAppend, settleCollaborationAppend } from './conversationCollaborationAppendJournal.js';
|
|
2
|
-
const KEY = 'xeno.agent.live-collaboration.v1';
|
|
3
|
-
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
4
|
-
const TOKEN = /^[a-f0-9]{64}$/;
|
|
5
|
-
export class ConversationCollaborationController {
|
|
6
|
-
request;
|
|
7
|
-
storage;
|
|
8
|
-
state = { status: 'idle', access: null, preview: null, events: [], participants: [], transientShare: null, pendingAppend: null, error: null };
|
|
9
|
-
listeners = new Set();
|
|
10
|
-
generation = 0;
|
|
11
|
-
pendingToken = null;
|
|
12
|
-
previewSequence = 0;
|
|
13
|
-
catchupFlight = null;
|
|
14
|
-
registerFlight = null;
|
|
15
|
-
acceptFlight = null;
|
|
16
|
-
appendFlights = new Map();
|
|
17
|
-
ownerDesired = new Map();
|
|
18
|
-
ownerAttempts = new Map();
|
|
19
|
-
ownerAcknowledged = new Map();
|
|
20
|
-
ownerDrain = null;
|
|
21
|
-
mutationFlights = new Map();
|
|
22
|
-
stopLiveRead = null;
|
|
23
|
-
storageWarning = null;
|
|
24
|
-
historyCaughtUp = false;
|
|
25
|
-
constructor(request, storage) {
|
|
26
|
-
this.request = request;
|
|
27
|
-
this.storage = storage;
|
|
28
|
-
}
|
|
29
|
-
snapshot = () => this.state;
|
|
30
|
-
subscribe = (listener) => { this.listeners.add(listener); return () => this.listeners.delete(listener); };
|
|
31
|
-
publish(patch) { this.state = { ...this.state, ...patch, ...(patch.error === null && this.storageWarning ? { error: this.storageWarning } : {}) }; for (const listener of this.listeners)
|
|
32
|
-
listener(this.state); }
|
|
33
|
-
unavailable() { if (this.request)
|
|
34
|
-
return false; this.publish({ status: 'unavailable', error: 'Live collaboration is unavailable on this account host.' }); return true; }
|
|
35
|
-
load(localConversationId) {
|
|
36
|
-
this.stopLiveRead?.();
|
|
37
|
-
this.historyCaughtUp = false;
|
|
38
|
-
const generation = ++this.generation;
|
|
39
|
-
this.pendingToken = null;
|
|
40
|
-
this.catchupFlight = null;
|
|
41
|
-
this.registerFlight = null;
|
|
42
|
-
this.acceptFlight = null;
|
|
43
|
-
this.appendFlights.clear();
|
|
44
|
-
this.ownerDesired.clear();
|
|
45
|
-
this.ownerAttempts.clear();
|
|
46
|
-
this.ownerAcknowledged.clear();
|
|
47
|
-
this.ownerDrain = null;
|
|
48
|
-
this.mutationFlights.clear();
|
|
49
|
-
const stored = readCollaborationAccess(this.storage, localConversationId);
|
|
50
|
-
// Only identity is persisted, not the event projection. A saved cursor is
|
|
51
|
-
// not a snapshot: replay authorized durable events before advancing it.
|
|
52
|
-
const access = stored ? { ...stored, cursor: 0 } : null;
|
|
53
|
-
if (generation !== this.generation)
|
|
54
|
-
return;
|
|
55
|
-
this.publish({ access, status: access ? 'ready' : 'idle', preview: null, events: [], participants: [], transientShare: null, pendingAppend: null, error: null });
|
|
56
|
-
}
|
|
57
|
-
startLive(intervalMs = 1000) {
|
|
58
|
-
this.stopLiveRead?.();
|
|
59
|
-
const generation = this.generation;
|
|
60
|
-
let stopped = false, timer;
|
|
61
|
-
const stop = () => {
|
|
62
|
-
stopped = true;
|
|
63
|
-
if (timer !== undefined)
|
|
64
|
-
clearTimeout(timer);
|
|
65
|
-
if (this.stopLiveRead === stop)
|
|
66
|
-
this.stopLiveRead = null;
|
|
67
|
-
};
|
|
68
|
-
this.stopLiveRead = stop;
|
|
69
|
-
const tick = async () => {
|
|
70
|
-
if (stopped || generation !== this.generation || !this.state.access)
|
|
71
|
-
return;
|
|
72
|
-
await this.catchUp();
|
|
73
|
-
if (!stopped && generation === this.generation && this.state.status === 'ready' && this.ownerDesired.size)
|
|
74
|
-
await this.flushOwner();
|
|
75
|
-
if (stopped || generation !== this.generation || ['revoked', 'unavailable'].includes(this.state.status))
|
|
76
|
-
return;
|
|
77
|
-
// Schedule after settlement, never an overlapping interval. Offline reads
|
|
78
|
-
// retry without user mutation; every read rechecks server authorization.
|
|
79
|
-
timer = setTimeout(() => { void tick(); }, Math.max(100, intervalMs) * (this.state.status === 'offline' ? 5 : 1));
|
|
80
|
-
};
|
|
81
|
-
void tick();
|
|
82
|
-
return stop;
|
|
83
|
-
}
|
|
84
|
-
persist(access) {
|
|
85
|
-
if (!this.storage)
|
|
86
|
-
return;
|
|
87
|
-
let entries = [];
|
|
88
|
-
try {
|
|
89
|
-
const raw = this.storage.getItem(KEY);
|
|
90
|
-
const parsed = raw ? JSON.parse(raw) : [];
|
|
91
|
-
if (Array.isArray(parsed))
|
|
92
|
-
entries = parsed;
|
|
93
|
-
}
|
|
94
|
-
catch { }
|
|
95
|
-
const retained = entries.filter(value => {
|
|
96
|
-
if (!value || typeof value !== 'object')
|
|
97
|
-
return false;
|
|
98
|
-
const item = value;
|
|
99
|
-
return access.kind === 'owner' ? !(item.kind === 'owner' && item.localConversationId === access.localConversationId) : !(item.kind === 'participant' && item.conversationId === access.conversationId);
|
|
100
|
-
});
|
|
101
|
-
try {
|
|
102
|
-
this.storage.setItem(KEY, JSON.stringify([...retained, access].slice(-100)));
|
|
103
|
-
this.storageWarning = null;
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
this.storageWarning = 'Collaboration is connected, but this device could not save its resume identity. Keep the conversation open until local storage is available.';
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
async registerOwner(localConversationId, title, modelId) {
|
|
110
|
-
if (this.registerFlight)
|
|
111
|
-
return this.registerFlight;
|
|
112
|
-
if (this.unavailable())
|
|
113
|
-
return false;
|
|
114
|
-
const scope = ++this.generation;
|
|
115
|
-
const flight = (async () => {
|
|
116
|
-
this.publish({ status: 'loading', error: null });
|
|
117
|
-
const result = await this.safe({ action: 'collaboration-register', collaborationLocalConversationId: localConversationId, collaborationTitle: title, collaborationModelId: modelId });
|
|
118
|
-
if (scope !== this.generation)
|
|
119
|
-
return false;
|
|
120
|
-
const id = result.collaborationConversation?.id;
|
|
121
|
-
if (!result.success || !id || !UUID.test(id))
|
|
122
|
-
return this.failure(result);
|
|
123
|
-
const access = { kind: 'owner', localConversationId, conversationId: id, cursor: 0 };
|
|
124
|
-
this.persist(access);
|
|
125
|
-
this.publish({ status: 'ready', access, error: null });
|
|
126
|
-
return true;
|
|
127
|
-
})().finally(() => { if (this.registerFlight === flight)
|
|
128
|
-
this.registerFlight = null; });
|
|
129
|
-
this.registerFlight = flight;
|
|
130
|
-
return flight;
|
|
131
|
-
}
|
|
132
|
-
async preview(linkOrToken) {
|
|
133
|
-
if (this.unavailable())
|
|
134
|
-
return false;
|
|
135
|
-
const token = extractToken(linkOrToken);
|
|
136
|
-
if (!token) {
|
|
137
|
-
this.publish({ status: 'failed', error: 'Enter a valid live collaboration link.' });
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
const scope = this.generation, sequence = ++this.previewSequence;
|
|
141
|
-
this.pendingToken = token;
|
|
142
|
-
this.publish({ status: 'loading', preview: null, error: null });
|
|
143
|
-
const result = await this.safe({ action: 'collaboration-preview', collaborationToken: token });
|
|
144
|
-
if (scope !== this.generation || sequence !== this.previewSequence || this.pendingToken !== token)
|
|
145
|
-
return false;
|
|
146
|
-
const value = result.collaborationConversation;
|
|
147
|
-
if (!result.success || !value?.id || !value.role) {
|
|
148
|
-
this.pendingToken = null;
|
|
149
|
-
return this.failure(result);
|
|
150
|
-
}
|
|
151
|
-
this.publish({ status: 'ready', preview: { conversationId: value.id, title: value.title, role: value.role, ownerName: value.owner?.name, messages: value.messages || [] }, error: null });
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
async accept() {
|
|
155
|
-
if (this.acceptFlight)
|
|
156
|
-
return this.acceptFlight;
|
|
157
|
-
if (this.unavailable())
|
|
158
|
-
return null;
|
|
159
|
-
const token = this.pendingToken;
|
|
160
|
-
if (!token) {
|
|
161
|
-
this.publish({ status: 'failed', error: 'Preview a live collaboration link before joining.' });
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
const scope = ++this.generation;
|
|
165
|
-
const flight = (async () => {
|
|
166
|
-
this.publish({ status: 'loading', error: null });
|
|
167
|
-
const result = await this.safe({ action: 'collaboration-accept', collaborationToken: token });
|
|
168
|
-
if (scope !== this.generation)
|
|
169
|
-
return null;
|
|
170
|
-
this.pendingToken = null;
|
|
171
|
-
const conversation = result.collaborationConversation, participant = result.collaborationParticipant;
|
|
172
|
-
if (!result.success || !conversation?.id || !participant?.role) {
|
|
173
|
-
this.publish({ preview: null });
|
|
174
|
-
this.failure(result);
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
const access = { kind: 'participant', conversationId: conversation.id, role: participant.role, cursor: 0 };
|
|
178
|
-
const preview = conversation.messages ? { conversationId: conversation.id, title: conversation.title, role: participant.role, messages: conversation.messages } : this.state.preview;
|
|
179
|
-
this.persist(access);
|
|
180
|
-
this.load(conversation.id);
|
|
181
|
-
this.publish({ status: 'ready', access, preview, error: null });
|
|
182
|
-
return conversation.id;
|
|
183
|
-
})().finally(() => { if (this.acceptFlight === flight)
|
|
184
|
-
this.acceptFlight = null; });
|
|
185
|
-
this.acceptFlight = flight;
|
|
186
|
-
return flight;
|
|
187
|
-
}
|
|
188
|
-
async createShare(role, expiresInDays, visibility = 'public') {
|
|
189
|
-
const access = this.state.access;
|
|
190
|
-
if (this.unavailable() || access?.kind !== 'owner') {
|
|
191
|
-
this.publish({ status: 'failed', error: 'Register this Agent conversation before creating a live share.' });
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
const scope = this.generation;
|
|
195
|
-
return this.singleflight(`share:${access.conversationId}`, JSON.stringify({ role, expiresInDays, visibility }), async () => {
|
|
196
|
-
this.publish({ status: 'loading', transientShare: null, error: null });
|
|
197
|
-
const result = await this.safe({ action: 'collaboration-create-share', collaborationConversationId: access.conversationId, collaborationRole: role, collaborationExpiresInDays: expiresInDays, collaborationVisibility: visibility });
|
|
198
|
-
if (scope !== this.generation || this.state.access?.conversationId !== access.conversationId || this.state.access?.kind !== 'owner')
|
|
199
|
-
return;
|
|
200
|
-
if (!result.success || !result.collaborationShare) {
|
|
201
|
-
this.failure(result);
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
this.publish({ status: 'ready', transientShare: result.collaborationShare, error: null });
|
|
205
|
-
}).then(() => this.state.transientShare);
|
|
206
|
-
}
|
|
207
|
-
clearTransientShare() { this.publish({ transientShare: null }); }
|
|
208
|
-
async restorePendingAppend() {
|
|
209
|
-
const access = this.state.access;
|
|
210
|
-
if (!this.request || access?.kind !== 'participant')
|
|
211
|
-
return;
|
|
212
|
-
const scope = this.generation, status = await this.safe({ action: 'status' });
|
|
213
|
-
if (scope !== this.generation || this.state.access?.conversationId !== access.conversationId || this.state.access?.kind !== 'participant')
|
|
214
|
-
return;
|
|
215
|
-
const expectedAccountId = status.status?.state === 'signed-in' ? status.status.account?.id : undefined;
|
|
216
|
-
if (!status.success || !expectedAccountId || !UUID.test(expectedAccountId)) {
|
|
217
|
-
this.failure({ ...status, error: status.error || 'Sign in before recovering a collaboration entry.' }, true);
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
try {
|
|
221
|
-
this.publish({ pendingAppend: recoverCollaborationAppend(this.storage, expectedAccountId, access.conversationId), error: null });
|
|
222
|
-
}
|
|
223
|
-
catch (error) {
|
|
224
|
-
this.publish({ status: 'conflict', pendingAppend: null, error: error instanceof CollaborationAppendJournalError ? error.message : 'Collaboration entry recovery failed.' });
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
catchUp() {
|
|
228
|
-
if (this.catchupFlight)
|
|
229
|
-
return this.catchupFlight;
|
|
230
|
-
const access = this.state.access;
|
|
231
|
-
if (!this.request || !access)
|
|
232
|
-
return Promise.resolve();
|
|
233
|
-
const scope = this.generation;
|
|
234
|
-
this.historyCaughtUp = false;
|
|
235
|
-
const flight = (async () => {
|
|
236
|
-
let after = access.cursor, hasMore = true, pages = 0;
|
|
237
|
-
while (hasMore && pages++ < 50) {
|
|
238
|
-
const result = await this.safe({ action: 'collaboration-events', collaborationConversationId: access.conversationId, collaborationCursor: after });
|
|
239
|
-
if (scope !== this.generation)
|
|
240
|
-
return;
|
|
241
|
-
if (!result.success || result.collaborationCursor === undefined) {
|
|
242
|
-
this.failure(result, true);
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
const incoming = result.collaborationEvents || [];
|
|
246
|
-
for (const event of incoming) {
|
|
247
|
-
if (event.sequence <= after)
|
|
248
|
-
continue;
|
|
249
|
-
if (event.sequence !== after + 1) {
|
|
250
|
-
this.publish({ status: 'conflict', error: 'Collaboration history has a cursor gap. Reload before continuing.' });
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
after = event.sequence;
|
|
254
|
-
}
|
|
255
|
-
if (result.collaborationCursor !== after) {
|
|
256
|
-
this.publish({ status: 'conflict', error: 'Collaboration history returned an inconsistent cursor.' });
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
const current = result.collaborationCurrentCursor;
|
|
260
|
-
if (current !== undefined && (!Number.isSafeInteger(current) || current < after || (result.collaborationHasMore === true) !== (current > after))) {
|
|
261
|
-
this.publish({ status: 'conflict', error: 'Collaboration history returned an inconsistent head.' });
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
if (incoming.length === 0) {
|
|
265
|
-
if (result.collaborationHasMore === true) {
|
|
266
|
-
this.publish({ status: 'conflict', error: 'Collaboration cursor did not advance.' });
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
// A successful idle read revalidates access, not the entire projection.
|
|
270
|
-
// Preserve array identity so renderers need not fold all history again.
|
|
271
|
-
this.historyCaughtUp = true;
|
|
272
|
-
this.publish({ status: 'ready', error: null });
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
const bindings = new Map();
|
|
276
|
-
for (const event of [...this.state.events, ...incoming]) {
|
|
277
|
-
const source = event.payload.logicalMessageId;
|
|
278
|
-
if (!source)
|
|
279
|
-
continue;
|
|
280
|
-
if (!event.messageId || (bindings.has(source) && bindings.get(source) !== event.messageId)) {
|
|
281
|
-
this.publish({ status: 'conflict', error: 'Collaboration message identity changed across revisions.' });
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
bindings.set(source, event.messageId);
|
|
285
|
-
}
|
|
286
|
-
const merged = new Map(this.state.events.map(event => [event.sequence, event]));
|
|
287
|
-
if (incoming.some(event => merged.has(event.sequence) && JSON.stringify(merged.get(event.sequence)) !== JSON.stringify(event))) {
|
|
288
|
-
this.publish({ status: 'conflict', error: 'Previously observed collaboration history changed.' });
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
for (const event of incoming)
|
|
292
|
-
if (event.sequence <= after) {
|
|
293
|
-
merged.set(event.sequence, event);
|
|
294
|
-
const payload = event.payload;
|
|
295
|
-
if (event.type === 'owner.message.synced' && payload.logicalMessageId && payload.revision && (payload.role === 'user' || payload.role === 'assistant')) {
|
|
296
|
-
const known = this.ownerAcknowledged.get(payload.logicalMessageId);
|
|
297
|
-
if (!known || known.revision < payload.revision)
|
|
298
|
-
this.ownerAcknowledged.set(payload.logicalMessageId, { revision: payload.revision,
|
|
299
|
-
hash: ownerFingerprint({ eventId: '', kind: payload.role, content: payload.content, state: payload.state }) });
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
const nextAccess = { ...access, cursor: after };
|
|
303
|
-
hasMore = result.collaborationHasMore === true;
|
|
304
|
-
this.historyCaughtUp = !hasMore;
|
|
305
|
-
this.persist(nextAccess);
|
|
306
|
-
this.publish({ status: hasMore ? 'loading' : 'ready', access: nextAccess, events: [...merged.values()].sort((a, b) => a.sequence - b.sequence), error: null });
|
|
307
|
-
}
|
|
308
|
-
})().finally(() => { if (this.catchupFlight === flight)
|
|
309
|
-
this.catchupFlight = null; });
|
|
310
|
-
this.catchupFlight = flight;
|
|
311
|
-
return flight;
|
|
312
|
-
}
|
|
313
|
-
append(kind, content, idempotencyKey) {
|
|
314
|
-
const access = this.state.access;
|
|
315
|
-
if (['revoked', 'unavailable'].includes(this.state.status))
|
|
316
|
-
return Promise.resolve(false);
|
|
317
|
-
if (!access || access.kind !== 'participant') {
|
|
318
|
-
this.publish({ status: 'failed', error: 'Only an accepted collaborator can add an entry.' });
|
|
319
|
-
return Promise.resolve(false);
|
|
320
|
-
}
|
|
321
|
-
if (access.role === 'viewer' || (kind === 'message' && access.role !== 'contributor')) {
|
|
322
|
-
this.publish({ status: 'failed', error: `${access.role} role cannot add this entry.` });
|
|
323
|
-
return Promise.resolve(false);
|
|
324
|
-
}
|
|
325
|
-
const scope = this.generation, hash = JSON.stringify({ conversationId: access.conversationId, kind, content }), key = idempotencyKey ? `append:${idempotencyKey}` : `append-intent:${hash}`, existing = this.appendFlights.get(key);
|
|
326
|
-
if (existing) {
|
|
327
|
-
if (existing.hash !== hash) {
|
|
328
|
-
this.publish({ status: 'conflict', error: 'An in-flight collaboration operation changed payload.' });
|
|
329
|
-
return Promise.resolve(false);
|
|
330
|
-
}
|
|
331
|
-
return existing.promise;
|
|
332
|
-
}
|
|
333
|
-
const promise = (async () => {
|
|
334
|
-
this.publish({ status: 'loading', error: null });
|
|
335
|
-
const status = await this.safe({ action: 'status' });
|
|
336
|
-
if (scope !== this.generation || this.state.access?.conversationId !== access.conversationId || this.state.access?.kind !== 'participant')
|
|
337
|
-
return false;
|
|
338
|
-
const expectedAccountId = status.status?.state === 'signed-in' ? status.status.account?.id : undefined;
|
|
339
|
-
if (!status.success || !expectedAccountId || !UUID.test(expectedAccountId))
|
|
340
|
-
return this.failure({ ...status, error: status.error || 'Sign in before sending a collaboration entry.' }, true);
|
|
341
|
-
let intent;
|
|
342
|
-
try {
|
|
343
|
-
intent = prepareCollaborationAppend(this.storage, { operationId: idempotencyKey, expectedAccountId, conversationId: access.conversationId, kind, content });
|
|
344
|
-
}
|
|
345
|
-
catch (error) {
|
|
346
|
-
this.publish({ status: 'conflict', error: error instanceof CollaborationAppendJournalError ? error.message : 'Collaboration entry preparation failed.' });
|
|
347
|
-
return false;
|
|
348
|
-
}
|
|
349
|
-
const result = await this.safe({ action: 'collaboration-append', expectedAccountId: intent.expectedAccountId, collaborationConversationId: intent.conversationId, collaborationKind: intent.kind, collaborationContent: intent.content, collaborationIdempotencyKey: intent.operationId });
|
|
350
|
-
if (scope !== this.generation || this.state.access?.conversationId !== access.conversationId || this.state.access?.kind !== 'participant')
|
|
351
|
-
return false;
|
|
352
|
-
const acknowledged = result.success && result.collaborationEvents?.some(event => exactAppendAcknowledgement(event, intent.operationId, intent.expectedAccountId, access.role, intent.kind, intent.content)) === true;
|
|
353
|
-
try {
|
|
354
|
-
settleCollaborationAppend(this.storage, intent.operationId, acknowledged ? 'committed' : result.success || result.code === 'REQUEST_FAILED' || result.code === 'UNCERTAIN' ? 'uncertain' : 'rejected');
|
|
355
|
-
}
|
|
356
|
-
catch (error) {
|
|
357
|
-
this.publish({ status: 'conflict', error: error instanceof CollaborationAppendJournalError ? error.message : 'Collaboration entry outcome could not be saved.' });
|
|
358
|
-
return false;
|
|
359
|
-
}
|
|
360
|
-
if (!result.success) {
|
|
361
|
-
this.failure(result);
|
|
362
|
-
return false;
|
|
363
|
-
}
|
|
364
|
-
if (!acknowledged) {
|
|
365
|
-
this.publish({ status: 'conflict', error: 'The account host did not acknowledge the exact collaboration entry.' });
|
|
366
|
-
return false;
|
|
367
|
-
}
|
|
368
|
-
this.publish({ status: 'ready', pendingAppend: null, error: null });
|
|
369
|
-
await this.catchUp();
|
|
370
|
-
return true;
|
|
371
|
-
})().finally(() => { if (this.appendFlights.get(key)?.promise === promise)
|
|
372
|
-
this.appendFlights.delete(key); });
|
|
373
|
-
this.appendFlights.set(key, { hash, promise });
|
|
374
|
-
return promise;
|
|
375
|
-
}
|
|
376
|
-
ownerSync(events) {
|
|
377
|
-
if (this.state.access?.kind !== 'owner' || events.length === 0)
|
|
378
|
-
return Promise.resolve();
|
|
379
|
-
for (const event of events)
|
|
380
|
-
this.ownerDesired.set(event.logicalMessageId || event.eventId, structuredClone(event));
|
|
381
|
-
return this.flushOwner();
|
|
382
|
-
}
|
|
383
|
-
flushOwner() {
|
|
384
|
-
if (this.ownerDrain)
|
|
385
|
-
return this.ownerDrain;
|
|
386
|
-
const access = this.state.access;
|
|
387
|
-
if (!access || access.kind !== 'owner')
|
|
388
|
-
return Promise.resolve();
|
|
389
|
-
const scope = this.generation;
|
|
390
|
-
const flight = (async () => {
|
|
391
|
-
// Recover the last authoritative revision before assigning another one.
|
|
392
|
-
await this.catchUp();
|
|
393
|
-
if (scope !== this.generation || this.state.status !== 'ready' || !this.historyCaughtUp)
|
|
394
|
-
return;
|
|
395
|
-
while (this.ownerDesired.size) {
|
|
396
|
-
if (scope !== this.generation || this.state.access?.conversationId !== access.conversationId || this.state.access?.kind !== 'owner')
|
|
397
|
-
return;
|
|
398
|
-
const [key, desired] = this.ownerDesired.entries().next().value;
|
|
399
|
-
let known = this.ownerAcknowledged.get(key);
|
|
400
|
-
let attempt = this.ownerAttempts.get(key);
|
|
401
|
-
if (attempt?.logicalMessageId && known && known.revision >= attempt.revision) {
|
|
402
|
-
if (known.revision === attempt.revision && known.hash !== ownerFingerprint(attempt)) {
|
|
403
|
-
this.publish({ status: 'conflict', error: 'Recovered collaboration revision does not match the dispatched message.' });
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
this.ownerAttempts.delete(key);
|
|
407
|
-
if (ownerFingerprint(desired) === ownerFingerprint(attempt)) {
|
|
408
|
-
this.ownerDesired.delete(key);
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
|
-
attempt = undefined;
|
|
412
|
-
}
|
|
413
|
-
if (!attempt && known?.hash === ownerFingerprint(desired)) {
|
|
414
|
-
this.ownerDesired.delete(key);
|
|
415
|
-
continue;
|
|
416
|
-
}
|
|
417
|
-
if (!attempt) {
|
|
418
|
-
attempt = desired.logicalMessageId ? { ...desired, expectedRevision: known?.revision ?? 0,
|
|
419
|
-
revision: (known?.revision ?? 0) + 1, eventId: await revisionEventId(key, (known?.revision ?? 0) + 1) } : { ...desired };
|
|
420
|
-
if (scope !== this.generation)
|
|
421
|
-
return;
|
|
422
|
-
this.ownerAttempts.set(key, attempt);
|
|
423
|
-
}
|
|
424
|
-
// Unknown acknowledgement keeps this exact immutable attempt. New render
|
|
425
|
-
// frames coalesce in ownerDesired, never mutate a dispatched revision.
|
|
426
|
-
const result = await this.safe({ action: 'collaboration-owner-sync', collaborationConversationId: access.conversationId, collaborationOwnerEvent: attempt });
|
|
427
|
-
if (scope !== this.generation)
|
|
428
|
-
return;
|
|
429
|
-
if (!result.success) {
|
|
430
|
-
this.failure(result);
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
if (!result.collaborationEvents?.some(event => exactOwnerAcknowledgement(event, attempt))) {
|
|
434
|
-
this.publish({ status: 'conflict', error: 'Provider did not acknowledge the exact collaboration message revision.' });
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
known = this.ownerAcknowledged.get(key);
|
|
438
|
-
if (!known || known.revision < (attempt.revision ?? 0))
|
|
439
|
-
this.ownerAcknowledged.set(key, { hash: ownerFingerprint(attempt), revision: attempt.revision ?? 0 });
|
|
440
|
-
else if (known.revision === (attempt.revision ?? 0) && known.hash !== ownerFingerprint(attempt)) {
|
|
441
|
-
this.publish({ status: 'conflict', error: 'Acknowledged collaboration revision conflicts with recovered history.' });
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
this.ownerAttempts.delete(key);
|
|
445
|
-
if (ownerFingerprint(this.ownerDesired.get(key)) === ownerFingerprint(attempt))
|
|
446
|
-
this.ownerDesired.delete(key);
|
|
447
|
-
}
|
|
448
|
-
await this.catchUp();
|
|
449
|
-
})().finally(() => { if (this.ownerDrain === flight)
|
|
450
|
-
this.ownerDrain = null; });
|
|
451
|
-
this.ownerDrain = flight;
|
|
452
|
-
return flight;
|
|
453
|
-
}
|
|
454
|
-
async loadParticipants() {
|
|
455
|
-
const access = this.state.access;
|
|
456
|
-
if (!access || access.kind !== 'owner' || !this.request)
|
|
457
|
-
return;
|
|
458
|
-
const scope = this.generation;
|
|
459
|
-
const result = await this.safe({ action: 'collaboration-participants', collaborationConversationId: access.conversationId });
|
|
460
|
-
if (scope !== this.generation || this.state.access?.conversationId !== access.conversationId || this.state.access?.kind !== 'owner')
|
|
461
|
-
return;
|
|
462
|
-
if (!result.success) {
|
|
463
|
-
this.failure(result);
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
this.publish({ status: 'ready', participants: result.collaborationParticipants || [], error: null });
|
|
467
|
-
}
|
|
468
|
-
async revokeParticipant(userId) {
|
|
469
|
-
const access = this.state.access;
|
|
470
|
-
if (!access || access.kind !== 'owner' || !this.request)
|
|
471
|
-
return;
|
|
472
|
-
const scope = this.generation;
|
|
473
|
-
await this.singleflight(`revoke:${access.conversationId}:${userId}`, userId, async () => {
|
|
474
|
-
const result = await this.safe({ action: 'collaboration-revoke-participant', collaborationConversationId: access.conversationId, collaborationParticipantUserId: userId });
|
|
475
|
-
if (scope !== this.generation || this.state.access?.conversationId !== access.conversationId || this.state.access?.kind !== 'owner')
|
|
476
|
-
return;
|
|
477
|
-
if (!result.success) {
|
|
478
|
-
this.failure(result);
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
await this.loadParticipants();
|
|
482
|
-
await this.catchUp();
|
|
483
|
-
});
|
|
484
|
-
}
|
|
485
|
-
singleflight(key, hash, operation) {
|
|
486
|
-
const existing = this.mutationFlights.get(key);
|
|
487
|
-
if (existing) {
|
|
488
|
-
if (existing.hash !== hash)
|
|
489
|
-
this.publish({ status: 'conflict', error: 'An in-flight collaboration operation changed payload.' });
|
|
490
|
-
return existing.promise;
|
|
491
|
-
}
|
|
492
|
-
const promise = operation().finally(() => { if (this.mutationFlights.get(key)?.promise === promise)
|
|
493
|
-
this.mutationFlights.delete(key); });
|
|
494
|
-
this.mutationFlights.set(key, { hash, promise });
|
|
495
|
-
return promise;
|
|
496
|
-
}
|
|
497
|
-
async safe(input) {
|
|
498
|
-
try {
|
|
499
|
-
return await this.request(structuredClone(input));
|
|
500
|
-
}
|
|
501
|
-
catch {
|
|
502
|
-
return { success: false, code: 'REQUEST_FAILED', error: 'Collaboration is offline. No operation was confirmed.' };
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
failure(result, accessCheck = false) {
|
|
506
|
-
const error = result.error || 'Collaboration operation failed.';
|
|
507
|
-
const status = result.code === 'CONFLICT' || result.code === 'UNCERTAIN' ? 'conflict' : accessCheck && ['REFUSED', 'SIGNED_OUT'].includes(result.code || '') ? 'revoked' : result.code === 'UNCONFIGURED' ? 'unavailable' : /offline/i.test(error) ? 'offline' : 'failed';
|
|
508
|
-
if (status === 'revoked') {
|
|
509
|
-
++this.generation;
|
|
510
|
-
this.stopLiveRead?.();
|
|
511
|
-
this.historyCaughtUp = false;
|
|
512
|
-
this.ownerDesired.clear();
|
|
513
|
-
this.ownerAttempts.clear();
|
|
514
|
-
this.ownerAcknowledged.clear();
|
|
515
|
-
}
|
|
516
|
-
this.publish({ status, error, ...(status === 'revoked' ? { preview: null, events: [], participants: [], transientShare: null, pendingAppend: null } : {}) });
|
|
517
|
-
return false;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
export function extractToken(value) {
|
|
521
|
-
const trimmed = value.trim();
|
|
522
|
-
if (TOKEN.test(trimmed))
|
|
523
|
-
return trimmed;
|
|
524
|
-
try {
|
|
525
|
-
const token = new URL(trimmed).pathname.split('/').filter(Boolean).at(-1) || '';
|
|
526
|
-
return TOKEN.test(token) ? token : null;
|
|
527
|
-
}
|
|
528
|
-
catch {
|
|
529
|
-
return null;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
export function readCollaborationAccess(storage, localConversationId) {
|
|
533
|
-
try {
|
|
534
|
-
const raw = storage?.getItem(KEY), entries = raw ? JSON.parse(raw) : [];
|
|
535
|
-
if (!Array.isArray(entries))
|
|
536
|
-
return null;
|
|
537
|
-
const candidate = entries.find(value => {
|
|
538
|
-
if (!value || typeof value !== 'object')
|
|
539
|
-
return false;
|
|
540
|
-
const item = value;
|
|
541
|
-
return localConversationId
|
|
542
|
-
? (item.kind === 'owner' && item.localConversationId === localConversationId) || (item.kind === 'participant' && item.conversationId === localConversationId)
|
|
543
|
-
: item.kind === 'participant';
|
|
544
|
-
});
|
|
545
|
-
if (!candidate || !UUID.test(String(candidate.conversationId)) || !Number.isSafeInteger(candidate.cursor) || Number(candidate.cursor) < 0)
|
|
546
|
-
return null;
|
|
547
|
-
if (candidate.kind === 'owner' && typeof candidate.localConversationId === 'string')
|
|
548
|
-
return { kind: 'owner', localConversationId: candidate.localConversationId, conversationId: String(candidate.conversationId), cursor: Number(candidate.cursor) };
|
|
549
|
-
if (candidate.kind === 'participant' && ['viewer', 'commenter', 'contributor'].includes(String(candidate.role)))
|
|
550
|
-
return { kind: 'participant', conversationId: String(candidate.conversationId), role: candidate.role, cursor: Number(candidate.cursor) };
|
|
551
|
-
}
|
|
552
|
-
catch {
|
|
553
|
-
try {
|
|
554
|
-
storage?.removeItem(KEY);
|
|
555
|
-
}
|
|
556
|
-
catch { }
|
|
557
|
-
}
|
|
558
|
-
return null;
|
|
559
|
-
}
|
|
560
|
-
function identity() { return globalThis.crypto?.randomUUID?.() || `collab-${Date.now()}-${Math.random().toString(36).slice(2)}`; }
|
|
561
|
-
function ownerFingerprint(event) { return JSON.stringify({ kind: event.kind, content: event.content, tool: event.tool, state: event.state }); }
|
|
562
|
-
function exactOwnerAcknowledgement(event, attempt) {
|
|
563
|
-
const payload = event.payload;
|
|
564
|
-
if (attempt.kind === 'tool')
|
|
565
|
-
return event.type === 'owner.tool.synced' && payload.kind === 'tool'
|
|
566
|
-
&& payload.sourceEventId === attempt.eventId && payload.tool?.name === attempt.tool?.name && payload.tool?.status === attempt.tool?.status;
|
|
567
|
-
if (event.type !== 'owner.message.synced' || payload.kind !== 'message' || payload.role !== attempt.kind || payload.content !== attempt.content)
|
|
568
|
-
return false;
|
|
569
|
-
return attempt.logicalMessageId ? payload.logicalMessageId === attempt.logicalMessageId && payload.revision === attempt.revision && payload.state === attempt.state : payload.sourceEventId === attempt.eventId;
|
|
570
|
-
}
|
|
571
|
-
function exactAppendAcknowledgement(event, operationId, expectedAccountId, role, kind, content) {
|
|
572
|
-
return event.type === `${kind}.appended` && event.actorUserId === expectedAccountId && event.payload.sourceEventId === operationId && event.payload.kind === kind && event.payload.role === role && event.payload.content === content;
|
|
573
|
-
}
|
|
574
|
-
async function revisionEventId(logicalId, revision) {
|
|
575
|
-
const digest = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(logicalId));
|
|
576
|
-
return `message-${Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('')}:r${revision}`;
|
|
577
|
-
}
|
|
578
|
-
/** The event log is immutable; the visible conversation folds revisions of one message. */
|
|
579
|
-
export function collaborationEntries(state) {
|
|
580
|
-
const entries = new Map();
|
|
581
|
-
for (const message of state.preview?.messages || [])
|
|
582
|
-
entries.set(message.id, { id: message.id, messageId: message.id, revision: 0, role: message.role, content: message.content });
|
|
583
|
-
for (const event of state.events) {
|
|
584
|
-
const { payload } = event;
|
|
585
|
-
if (payload.content === undefined && !payload.tool)
|
|
586
|
-
continue;
|
|
587
|
-
const id = event.messageId || event.commentId || `event:${event.sequence}`;
|
|
588
|
-
const previous = entries.get(id), revision = payload.revision ?? 0;
|
|
589
|
-
if (previous?.sequence !== undefined && (revision < previous.revision || (revision === previous.revision && event.sequence <= previous.sequence)))
|
|
590
|
-
continue;
|
|
591
|
-
entries.set(id, { id, ...(event.messageId ? { messageId: event.messageId } : {}), sequence: event.sequence, revision,
|
|
592
|
-
role: payload.role || payload.kind || event.type, content: payload.content ?? `${payload.tool.name}: ${payload.tool.status}`,
|
|
593
|
-
...(payload.state ? { state: payload.state } : {}) });
|
|
594
|
-
}
|
|
595
|
-
return [...entries.values()];
|
|
596
|
-
}
|
|
597
|
-
//# sourceMappingURL=conversationCollaborationController.js.map
|