@felan-ai/daemon 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/NOTICE +69 -0
- package/README.md +61 -2
- package/dist/client.d.ts +5 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +4 -0
- package/dist/client.js.map +1 -0
- package/dist/daemon/admission.d.ts +39 -0
- package/dist/daemon/admission.d.ts.map +1 -0
- package/dist/daemon/admission.js +175 -0
- package/dist/daemon/admission.js.map +1 -0
- package/dist/daemon/agent-logs.d.ts +11 -0
- package/dist/daemon/agent-logs.d.ts.map +1 -0
- package/dist/daemon/agent-logs.js +31 -0
- package/dist/daemon/agent-logs.js.map +1 -0
- package/dist/daemon/bootstrap.d.ts +7 -0
- package/dist/daemon/bootstrap.d.ts.map +1 -0
- package/dist/daemon/bootstrap.js +4 -0
- package/dist/daemon/bootstrap.js.map +1 -0
- package/dist/daemon/broker.d.ts +94 -0
- package/dist/daemon/broker.d.ts.map +1 -0
- package/dist/daemon/broker.js +374 -0
- package/dist/daemon/broker.js.map +1 -0
- package/dist/daemon/commands.d.ts +47 -0
- package/dist/daemon/commands.d.ts.map +1 -0
- package/dist/daemon/commands.js +639 -0
- package/dist/daemon/commands.js.map +1 -0
- package/dist/daemon/control-recovery.d.ts +9 -0
- package/dist/daemon/control-recovery.d.ts.map +1 -0
- package/dist/daemon/control-recovery.js +68 -0
- package/dist/daemon/control-recovery.js.map +1 -0
- package/dist/daemon/control-state.d.ts +1915 -0
- package/dist/daemon/control-state.d.ts.map +1 -0
- package/dist/daemon/control-state.js +154 -0
- package/dist/daemon/control-state.js.map +1 -0
- package/dist/daemon/control-store.d.ts +113 -0
- package/dist/daemon/control-store.d.ts.map +1 -0
- package/dist/daemon/control-store.js +901 -0
- package/dist/daemon/control-store.js.map +1 -0
- package/dist/daemon/execution-policy.d.ts +15 -0
- package/dist/daemon/execution-policy.d.ts.map +1 -0
- package/dist/daemon/execution-policy.js +42 -0
- package/dist/daemon/execution-policy.js.map +1 -0
- package/dist/daemon/execution-services.d.ts +5 -0
- package/dist/daemon/execution-services.d.ts.map +1 -0
- package/dist/daemon/execution-services.js +73 -0
- package/dist/daemon/execution-services.js.map +1 -0
- package/dist/daemon/executions.d.ts +134 -0
- package/dist/daemon/executions.d.ts.map +1 -0
- package/dist/daemon/executions.js +486 -0
- package/dist/daemon/executions.js.map +1 -0
- package/dist/daemon/extension-ui.d.ts +24 -0
- package/dist/daemon/extension-ui.d.ts.map +1 -0
- package/dist/daemon/extension-ui.js +161 -0
- package/dist/daemon/extension-ui.js.map +1 -0
- package/dist/daemon/fanout.d.ts +40 -0
- package/dist/daemon/fanout.d.ts.map +1 -0
- package/dist/daemon/fanout.js +304 -0
- package/dist/daemon/fanout.js.map +1 -0
- package/dist/daemon/headless-json.d.ts +3 -0
- package/dist/daemon/headless-json.d.ts.map +1 -0
- package/dist/daemon/headless-json.js +20 -0
- package/dist/daemon/headless-json.js.map +1 -0
- package/dist/daemon/host-config.d.ts +61 -0
- package/dist/daemon/host-config.d.ts.map +1 -0
- package/dist/daemon/host-config.js +167 -0
- package/dist/daemon/host-config.js.map +1 -0
- package/dist/daemon/host-services.d.ts +61 -0
- package/dist/daemon/host-services.d.ts.map +1 -0
- package/dist/daemon/host-services.js +2 -0
- package/dist/daemon/host-services.js.map +1 -0
- package/dist/daemon/ipc.d.ts +14 -0
- package/dist/daemon/ipc.d.ts.map +1 -0
- package/dist/daemon/ipc.js +233 -0
- package/dist/daemon/ipc.js.map +1 -0
- package/dist/daemon/legacy-sessions.d.ts +48 -0
- package/dist/daemon/legacy-sessions.d.ts.map +1 -0
- package/dist/daemon/legacy-sessions.js +328 -0
- package/dist/daemon/legacy-sessions.js.map +1 -0
- package/dist/daemon/lifecycle.d.ts +44 -0
- package/dist/daemon/lifecycle.d.ts.map +1 -0
- package/dist/daemon/lifecycle.js +164 -0
- package/dist/daemon/lifecycle.js.map +1 -0
- package/dist/daemon/main.d.ts +3 -0
- package/dist/daemon/main.d.ts.map +1 -0
- package/dist/daemon/main.js +83 -0
- package/dist/daemon/main.js.map +1 -0
- package/dist/daemon/message-display.d.ts +4 -0
- package/dist/daemon/message-display.d.ts.map +1 -0
- package/dist/daemon/message-display.js +20 -0
- package/dist/daemon/message-display.js.map +1 -0
- package/dist/daemon/native-ownership.d.ts +13 -0
- package/dist/daemon/native-ownership.d.ts.map +1 -0
- package/dist/daemon/native-ownership.js +173 -0
- package/dist/daemon/native-ownership.js.map +1 -0
- package/dist/daemon/operational-log.d.ts +23 -0
- package/dist/daemon/operational-log.d.ts.map +1 -0
- package/dist/daemon/operational-log.js +79 -0
- package/dist/daemon/operational-log.js.map +1 -0
- package/dist/daemon/ownership.d.ts +35 -0
- package/dist/daemon/ownership.d.ts.map +1 -0
- package/dist/daemon/ownership.js +395 -0
- package/dist/daemon/ownership.js.map +1 -0
- package/dist/daemon/persistence.d.ts +110 -0
- package/dist/daemon/persistence.d.ts.map +1 -0
- package/dist/daemon/persistence.js +540 -0
- package/dist/daemon/persistence.js.map +1 -0
- package/dist/daemon/process-adapter.d.ts +23 -0
- package/dist/daemon/process-adapter.d.ts.map +1 -0
- package/dist/daemon/process-adapter.js +2 -0
- package/dist/daemon/process-adapter.js.map +1 -0
- package/dist/daemon/processes.d.ts +102 -0
- package/dist/daemon/processes.d.ts.map +1 -0
- package/dist/daemon/processes.js +691 -0
- package/dist/daemon/processes.js.map +1 -0
- package/dist/daemon/profile-storage.d.ts +16 -0
- package/dist/daemon/profile-storage.d.ts.map +1 -0
- package/dist/daemon/profile-storage.js +148 -0
- package/dist/daemon/profile-storage.js.map +1 -0
- package/dist/daemon/projection.d.ts +110 -0
- package/dist/daemon/projection.d.ts.map +1 -0
- package/dist/daemon/projection.js +661 -0
- package/dist/daemon/projection.js.map +1 -0
- package/dist/daemon/provider-loader.d.ts +28 -0
- package/dist/daemon/provider-loader.d.ts.map +1 -0
- package/dist/daemon/provider-loader.js +376 -0
- package/dist/daemon/provider-loader.js.map +1 -0
- package/dist/daemon/provider.d.ts +92 -0
- package/dist/daemon/provider.d.ts.map +1 -0
- package/dist/daemon/provider.js +31 -0
- package/dist/daemon/provider.js.map +1 -0
- package/dist/daemon/registry.d.ts +209 -0
- package/dist/daemon/registry.d.ts.map +1 -0
- package/dist/daemon/registry.js +1591 -0
- package/dist/daemon/registry.js.map +1 -0
- package/dist/daemon/runtime-dependencies.d.ts +5 -0
- package/dist/daemon/runtime-dependencies.d.ts.map +1 -0
- package/dist/daemon/runtime-dependencies.js +31 -0
- package/dist/daemon/runtime-dependencies.js.map +1 -0
- package/dist/daemon/server.d.ts +110 -0
- package/dist/daemon/server.d.ts.map +1 -0
- package/dist/daemon/server.js +556 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/service.d.ts +38 -0
- package/dist/daemon/service.d.ts.map +1 -0
- package/dist/daemon/service.js +275 -0
- package/dist/daemon/service.js.map +1 -0
- package/dist/daemon/session-cursor.d.ts +3 -0
- package/dist/daemon/session-cursor.d.ts.map +1 -0
- package/dist/daemon/session-cursor.js +4 -0
- package/dist/daemon/session-cursor.js.map +1 -0
- package/dist/daemon/session-execution.d.ts +88 -0
- package/dist/daemon/session-execution.d.ts.map +1 -0
- package/dist/daemon/session-execution.js +2 -0
- package/dist/daemon/session-execution.js.map +1 -0
- package/dist/daemon/session-executions.d.ts +5 -0
- package/dist/daemon/session-executions.d.ts.map +1 -0
- package/dist/daemon/session-executions.js +146 -0
- package/dist/daemon/session-executions.js.map +1 -0
- package/dist/daemon/session-files.d.ts +28 -0
- package/dist/daemon/session-files.d.ts.map +1 -0
- package/dist/daemon/session-files.js +189 -0
- package/dist/daemon/session-files.js.map +1 -0
- package/dist/daemon/session-lifecycle-contracts.d.ts +104 -0
- package/dist/daemon/session-lifecycle-contracts.d.ts.map +1 -0
- package/dist/daemon/session-lifecycle-contracts.js +2 -0
- package/dist/daemon/session-lifecycle-contracts.js.map +1 -0
- package/dist/daemon/session-lifecycle.d.ts +26 -0
- package/dist/daemon/session-lifecycle.d.ts.map +1 -0
- package/dist/daemon/session-lifecycle.js +246 -0
- package/dist/daemon/session-lifecycle.js.map +1 -0
- package/dist/daemon/session-runtime.d.ts +64 -0
- package/dist/daemon/session-runtime.d.ts.map +1 -0
- package/dist/daemon/session-runtime.js +3 -0
- package/dist/daemon/session-runtime.js.map +1 -0
- package/dist/daemon/session-services.d.ts +6 -0
- package/dist/daemon/session-services.d.ts.map +1 -0
- package/dist/daemon/session-services.js +144 -0
- package/dist/daemon/session-services.js.map +1 -0
- package/dist/daemon/shutdown-error.d.ts +12 -0
- package/dist/daemon/shutdown-error.d.ts.map +1 -0
- package/dist/daemon/shutdown-error.js +51 -0
- package/dist/daemon/shutdown-error.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +28 -0
- package/dist/daemon/supervisor.d.ts.map +1 -0
- package/dist/daemon/supervisor.js +159 -0
- package/dist/daemon/supervisor.js.map +1 -0
- package/dist/daemon/target.d.ts +55 -0
- package/dist/daemon/target.d.ts.map +1 -0
- package/dist/daemon/target.js +191 -0
- package/dist/daemon/target.js.map +1 -0
- package/dist/daemon/tree.d.ts +7 -0
- package/dist/daemon/tree.d.ts.map +1 -0
- package/dist/daemon/tree.js +126 -0
- package/dist/daemon/tree.js.map +1 -0
- package/dist/daemon/ui-vocabulary.d.ts +29 -0
- package/dist/daemon/ui-vocabulary.d.ts.map +1 -0
- package/dist/daemon/ui-vocabulary.js +323 -0
- package/dist/daemon/ui-vocabulary.js.map +1 -0
- package/dist/daemon/worker.d.ts +5 -0
- package/dist/daemon/worker.d.ts.map +1 -0
- package/dist/daemon/worker.js +49 -0
- package/dist/daemon/worker.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/lock.d.ts +27 -0
- package/dist/lock.d.ts.map +1 -0
- package/dist/lock.js +432 -0
- package/dist/lock.js.map +1 -0
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +56 -0
- package/dist/logger.js.map +1 -0
- package/dist/remote/backoff.d.ts +17 -0
- package/dist/remote/backoff.d.ts.map +1 -0
- package/dist/remote/backoff.js +18 -0
- package/dist/remote/backoff.js.map +1 -0
- package/dist/remote/batch.d.ts +3 -0
- package/dist/remote/batch.d.ts.map +1 -0
- package/dist/remote/batch.js +27 -0
- package/dist/remote/batch.js.map +1 -0
- package/dist/remote/command-journal.d.ts +30 -0
- package/dist/remote/command-journal.d.ts.map +1 -0
- package/dist/remote/command-journal.js +72 -0
- package/dist/remote/command-journal.js.map +1 -0
- package/dist/remote/commands.d.ts +48 -0
- package/dist/remote/commands.d.ts.map +1 -0
- package/dist/remote/commands.js +145 -0
- package/dist/remote/commands.js.map +1 -0
- package/dist/remote/connector.d.ts +135 -0
- package/dist/remote/connector.d.ts.map +1 -0
- package/dist/remote/connector.js +1069 -0
- package/dist/remote/connector.js.map +1 -0
- package/dist/remote/content.d.ts +17 -0
- package/dist/remote/content.d.ts.map +1 -0
- package/dist/remote/content.js +116 -0
- package/dist/remote/content.js.map +1 -0
- package/dist/remote/outbox.d.ts +52 -0
- package/dist/remote/outbox.d.ts.map +1 -0
- package/dist/remote/outbox.js +321 -0
- package/dist/remote/outbox.js.map +1 -0
- package/dist/remote/platform.d.ts +53 -0
- package/dist/remote/platform.d.ts.map +1 -0
- package/dist/remote/platform.js +231 -0
- package/dist/remote/platform.js.map +1 -0
- package/dist/remote/realtime.d.ts +45 -0
- package/dist/remote/realtime.d.ts.map +1 -0
- package/dist/remote/realtime.js +265 -0
- package/dist/remote/realtime.js.map +1 -0
- package/dist/remote/sharing.d.ts +16 -0
- package/dist/remote/sharing.d.ts.map +1 -0
- package/dist/remote/sharing.js +24 -0
- package/dist/remote/sharing.js.map +1 -0
- package/dist/remote/state.d.ts +52 -0
- package/dist/remote/state.d.ts.map +1 -0
- package/dist/remote/state.js +156 -0
- package/dist/remote/state.js.map +1 -0
- package/dist/remote/text.d.ts +11 -0
- package/dist/remote/text.d.ts.map +1 -0
- package/dist/remote/text.js +188 -0
- package/dist/remote/text.js.map +1 -0
- package/dist/remote/token.d.ts +39 -0
- package/dist/remote/token.d.ts.map +1 -0
- package/dist/remote/token.js +238 -0
- package/dist/remote/token.js.map +1 -0
- package/dist/runtime-factory.d.ts +12 -0
- package/dist/runtime-factory.d.ts.map +1 -0
- package/dist/runtime-factory.js +12 -0
- package/dist/runtime-factory.js.map +1 -0
- package/dist/session-history.d.ts +29 -0
- package/dist/session-history.d.ts.map +1 -0
- package/dist/session-history.js +374 -0
- package/dist/session-history.js.map +1 -0
- package/dist/setup/dependencies.d.ts +15 -0
- package/dist/setup/dependencies.d.ts.map +1 -0
- package/dist/setup/dependencies.js +2 -0
- package/dist/setup/dependencies.js.map +1 -0
- package/dist/setup/open.d.ts +8 -0
- package/dist/setup/open.d.ts.map +1 -0
- package/dist/setup/open.js +16 -0
- package/dist/setup/open.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/package.json +65 -3
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { isConversationEntry } from './session-files.js';
|
|
3
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
4
|
+
import { getSupportedThinkingLevels } from '@felan-ai/agent-core';
|
|
5
|
+
import { CatalogSchema, JsonValueSchema, LIMITS, MessageSchema, OPERATION_CATALOG, PresentationSchema, PromptSchema, parseSchema, trimSnapshotLists, validateBase64, validatePrompt, MESSAGE_DISPLAY_KEY, MESSAGE_DISPLAY_ID, } from '@felan-ai/session-protocol';
|
|
6
|
+
import { messageDisplay } from './message-display.js';
|
|
7
|
+
import { sessionTip } from './session-cursor.js';
|
|
8
|
+
export class SessionProjection {
|
|
9
|
+
options;
|
|
10
|
+
session;
|
|
11
|
+
id;
|
|
12
|
+
entryIds = new Set();
|
|
13
|
+
scannedEntries = 0;
|
|
14
|
+
lastScannedEntryId;
|
|
15
|
+
messageIds = new Map();
|
|
16
|
+
liveIds = new WeakMap();
|
|
17
|
+
origins = new Map();
|
|
18
|
+
tools = new Map();
|
|
19
|
+
dirtyTools = new Set();
|
|
20
|
+
streams = new Map();
|
|
21
|
+
streamsBytes = 0;
|
|
22
|
+
completionOnly = new Map();
|
|
23
|
+
dirtyCompletionOnly = new Set();
|
|
24
|
+
presentation = new Map();
|
|
25
|
+
completedStreams = new Set();
|
|
26
|
+
queue = [];
|
|
27
|
+
queued = new Map();
|
|
28
|
+
queueContext = new AsyncLocalStorage();
|
|
29
|
+
restoreQueueMethods = [];
|
|
30
|
+
queueLost = false;
|
|
31
|
+
queueObserved = false;
|
|
32
|
+
queueKey = digest([]);
|
|
33
|
+
queueNotice = '';
|
|
34
|
+
origin;
|
|
35
|
+
assistant;
|
|
36
|
+
timer;
|
|
37
|
+
disposed = false;
|
|
38
|
+
unsubscribe;
|
|
39
|
+
catalogKey = '';
|
|
40
|
+
cachedCatalog;
|
|
41
|
+
executionsGeneration;
|
|
42
|
+
runtimeRunId;
|
|
43
|
+
compacting = false;
|
|
44
|
+
compactionDirty = false;
|
|
45
|
+
activeDisplay;
|
|
46
|
+
displayDirty = false;
|
|
47
|
+
constructor(options) {
|
|
48
|
+
this.options = options;
|
|
49
|
+
this.session = options.binding.runtime.session;
|
|
50
|
+
this.id = options.binding.sessionId;
|
|
51
|
+
const saved = options.store.session(this.id);
|
|
52
|
+
if (saved.projections.some(row => row.value.type === 'queue' && row.value.ids.length > 0) && this.session.pendingMessageCount === 0) {
|
|
53
|
+
this.presentation.set('interrupted-queue', { id: 'interrupted-queue', kind: 'notification', level: 'warning', text: 'Pending queued turns were interrupted. Submit them again explicitly; no queued command was replayed.' });
|
|
54
|
+
}
|
|
55
|
+
for (const row of saved.projections)
|
|
56
|
+
if (row.value.type === 'message')
|
|
57
|
+
this.messageIds.set(row.value.entryId, row.value.id);
|
|
58
|
+
// Absence is proved against all entries, not the current branch or a history page.
|
|
59
|
+
const entries = this.session.sessionManager.getEntries();
|
|
60
|
+
const allIds = new Set(entries.map(entry => entry.id));
|
|
61
|
+
for (const entry of entries)
|
|
62
|
+
this.entryIds.add(entry.id);
|
|
63
|
+
this.scannedEntries = entries.length;
|
|
64
|
+
this.lastScannedEntryId = entries.at(-1)?.id;
|
|
65
|
+
const present = entries.filter(isMessageEntry).slice(-256).map(entry => ({ id: this.messageId(entry), entryId: entry.id }));
|
|
66
|
+
const absentEntryIds = [...this.messageIds.keys()].filter(id => !allIds.has(id));
|
|
67
|
+
if (absentEntryIds.length)
|
|
68
|
+
options.store.reconcilePi(this.id, { present: [], absentEntryIds });
|
|
69
|
+
if (present.length)
|
|
70
|
+
options.store.reconcilePi(this.id, { present, absentEntryIds: [] });
|
|
71
|
+
this.catalogKey = this.refreshCatalog().key;
|
|
72
|
+
this.observePublicQueue();
|
|
73
|
+
this.unsubscribe = this.session.subscribe(event => {
|
|
74
|
+
try {
|
|
75
|
+
this.receive(event);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
options.onError(error);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
setOrigin(origin) { this.origin = structuredClone(origin); }
|
|
83
|
+
get canObserveQueue() { return this.queueObserved && !this.queueLost; }
|
|
84
|
+
async withQueueOrigin(origin, effect) {
|
|
85
|
+
if (!this.canObserveQueue)
|
|
86
|
+
throw new Error('Queue observation is unavailable.');
|
|
87
|
+
const context = { origin: structuredClone(origin), ids: [] };
|
|
88
|
+
const value = await this.queueContext.run(context, effect);
|
|
89
|
+
this.refresh();
|
|
90
|
+
return { value, ids: [...context.ids] };
|
|
91
|
+
}
|
|
92
|
+
transitionForSafety(transition, expectedRunId) {
|
|
93
|
+
const previous = this.options.store.sessionMetadata(this.id);
|
|
94
|
+
if (expectedRunId !== undefined && previous.run.id !== expectedRunId)
|
|
95
|
+
return;
|
|
96
|
+
const commit = this.options.store.transitionSessionForSafety(this.id, transition);
|
|
97
|
+
if (commit.revisions[this.id] === undefined)
|
|
98
|
+
return;
|
|
99
|
+
const { value } = commit;
|
|
100
|
+
const change = transition === 'cancelling' || transition === 'idle'
|
|
101
|
+
? { type: 'run.updated', run: value.run }
|
|
102
|
+
: { type: 'metadata.updated', title: value.title, state: value.state, residency: value.residency };
|
|
103
|
+
this.options.publish({ v: 1, session: this.id, rev: value.revision, change });
|
|
104
|
+
}
|
|
105
|
+
settleRun(expectedRunId) {
|
|
106
|
+
if (expectedRunId === undefined || !this.session.isIdle || this.options.canSettle?.() === false)
|
|
107
|
+
return;
|
|
108
|
+
this.transitionForSafety('idle', expectedRunId);
|
|
109
|
+
}
|
|
110
|
+
/** Only a patch is durable; live views (tools, presentations, catalog, executions) take transient revisions. */
|
|
111
|
+
/** Makes a draft visible to listings. Publishing keeps the client's revision sequence contiguous. */
|
|
112
|
+
materialize() {
|
|
113
|
+
const stored = this.options.store.sessionMetadata(this.id);
|
|
114
|
+
this.publish({ type: 'metadata.updated', title: stored.title, state: stored.state, residency: stored.residency }, { draft: false });
|
|
115
|
+
}
|
|
116
|
+
publish(change, patch) {
|
|
117
|
+
const rev = patch ? this.options.store.updateSession(this.id, patch).value.revision : this.options.store.advanceTransientRevision(this.id);
|
|
118
|
+
this.options.publish({ v: 1, session: this.id, rev, change });
|
|
119
|
+
}
|
|
120
|
+
setExecutionsGeneration(generation) {
|
|
121
|
+
if (this.executionsGeneration === generation)
|
|
122
|
+
return;
|
|
123
|
+
this.publish({ type: 'executions.changed', generation });
|
|
124
|
+
this.executionsGeneration = generation;
|
|
125
|
+
}
|
|
126
|
+
refresh() {
|
|
127
|
+
if (this.disposed)
|
|
128
|
+
return;
|
|
129
|
+
if (this.timer)
|
|
130
|
+
clearTimeout(this.timer);
|
|
131
|
+
this.timer = undefined;
|
|
132
|
+
this.refreshNow();
|
|
133
|
+
}
|
|
134
|
+
syncCursor() {
|
|
135
|
+
const manager = this.session.sessionManager;
|
|
136
|
+
const stored = this.options.store.sessionMetadata(this.id);
|
|
137
|
+
const branch = manager.getLeafId() ?? this.id;
|
|
138
|
+
const observedTip = sessionTip(manager);
|
|
139
|
+
if (stored.branch === branch && stored.observedTip === observedTip)
|
|
140
|
+
return;
|
|
141
|
+
const commit = this.options.store.updateSession(this.id, { branch, observedTip });
|
|
142
|
+
const history = this.history(undefined, commit.value.revision);
|
|
143
|
+
this.options.publish({ v: 1, session: this.id, rev: commit.value.revision, change: { type: 'branch.updated', branch, history } });
|
|
144
|
+
}
|
|
145
|
+
refreshNow() {
|
|
146
|
+
this.options.store.owner.assertCurrent();
|
|
147
|
+
if (this.compactionDirty) {
|
|
148
|
+
const id = 'compaction:progress';
|
|
149
|
+
if (!this.compacting || this.presentation.has(id) || this.presentation.size < LIMITS.pageEntries) {
|
|
150
|
+
if (this.compacting)
|
|
151
|
+
this.setPresentation({ id, kind: 'status', key: id, value: { kind: 'text', text: 'Compacting context…' }, fallback: 'Compacting context…' });
|
|
152
|
+
else if (this.presentation.has(id))
|
|
153
|
+
this.setPresentation({ id, remove: true });
|
|
154
|
+
this.compactionDirty = false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
for (const id of this.dirtyCompletionOnly)
|
|
158
|
+
this.publishCompletionOnly(id);
|
|
159
|
+
const manager = this.session.sessionManager;
|
|
160
|
+
const entries = manager.getEntries();
|
|
161
|
+
const start = entries.length >= this.scannedEntries && entries[this.scannedEntries - 1]?.id === this.lastScannedEntryId ? this.scannedEntries : 0;
|
|
162
|
+
for (let index = start; index < entries.length; index++) {
|
|
163
|
+
const entry = entries[index];
|
|
164
|
+
if (this.entryIds.has(entry.id))
|
|
165
|
+
continue;
|
|
166
|
+
if (!isMessageEntry(entry)) {
|
|
167
|
+
this.entryIds.add(entry.id);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (isConversationEntry(entry) && this.options.store.sessionMetadata(this.id).draft)
|
|
171
|
+
this.materialize();
|
|
172
|
+
if (this.origin)
|
|
173
|
+
this.origins.set(entry.id, this.origin);
|
|
174
|
+
const message = this.message(entry);
|
|
175
|
+
const commit = this.options.store.project(this.id, [{ type: 'message', id: message.id, entryId: entry.id, state: 'observed' }]);
|
|
176
|
+
this.entryIds.add(entry.id);
|
|
177
|
+
for (const [id, stream] of this.streams)
|
|
178
|
+
if (stream.value.message === message.id)
|
|
179
|
+
this.removeStream(id);
|
|
180
|
+
for (const [id, stream] of this.completionOnly)
|
|
181
|
+
if (stream.message === message.id)
|
|
182
|
+
this.completionOnly.delete(id);
|
|
183
|
+
this.options.publish({ v: 1, session: this.id, rev: commit.value.revision, change: { type: 'message.completed', message } });
|
|
184
|
+
if (this.activeDisplay?.message === message.id) {
|
|
185
|
+
this.activeDisplay = undefined;
|
|
186
|
+
this.displayDirty = true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
this.scannedEntries = entries.length;
|
|
190
|
+
this.lastScannedEntryId = entries.at(-1)?.id;
|
|
191
|
+
this.flushDisplay();
|
|
192
|
+
for (const id of this.dirtyTools)
|
|
193
|
+
this.publish({ type: 'tool.updated', tool: this.tools.get(id) });
|
|
194
|
+
this.dirtyTools.clear();
|
|
195
|
+
for (const [id, tool] of this.tools)
|
|
196
|
+
if (this.tools.size > LIMITS.pageEntries && tool.state !== 'running')
|
|
197
|
+
this.tools.delete(id);
|
|
198
|
+
this.refreshQueue();
|
|
199
|
+
const stored = this.options.store.sessionMetadata(this.id);
|
|
200
|
+
const title = manager.getSessionName() ?? '';
|
|
201
|
+
if (stored.title !== title)
|
|
202
|
+
this.publish({ type: 'metadata.updated', title, state: stored.state, residency: stored.residency }, { title });
|
|
203
|
+
const model = this.session.model && { provider: this.session.model.provider, id: this.session.model.id };
|
|
204
|
+
const thinking = this.session.thinkingLevel;
|
|
205
|
+
if (model && (digest(stored.model) !== digest(model) || stored.thinking !== thinking))
|
|
206
|
+
this.publish({ type: 'model.updated', model, thinking }, { model, thinking });
|
|
207
|
+
this.syncCursor();
|
|
208
|
+
const catalog = this.refreshCatalog();
|
|
209
|
+
if (this.catalogKey !== catalog.key) {
|
|
210
|
+
this.publish({ type: 'catalog.updated', catalog: catalog.value });
|
|
211
|
+
this.catalogKey = catalog.key;
|
|
212
|
+
}
|
|
213
|
+
this.options.onRefresh?.();
|
|
214
|
+
}
|
|
215
|
+
catalog() { return this.refreshCatalog().value; }
|
|
216
|
+
snapshot() {
|
|
217
|
+
this.refresh();
|
|
218
|
+
const stored = this.options.store.sessionMetadata(this.id);
|
|
219
|
+
if (!stored.model || !stored.thinking)
|
|
220
|
+
throw new Error('Session has no selected model; select an available model before attaching.');
|
|
221
|
+
const history = this.history(undefined, stored.revision);
|
|
222
|
+
return trimSnapshotLists({
|
|
223
|
+
session: this.id, root: stored.root, ...(stored.parent ? { parent: stored.parent } : {}),
|
|
224
|
+
revision: stored.revision, cwd: stored.cwd, title: stored.title, state: stored.state, residency: stored.residency,
|
|
225
|
+
branch: history.branch, history, model: stored.model, thinking: stored.thinking, run: stored.run,
|
|
226
|
+
queue: this.queue, policy: stored.policy, catalog: this.cachedCatalog.value, ...this.options.extras(),
|
|
227
|
+
tools: [...this.tools.values()], presentation: [...this.presentation.values()],
|
|
228
|
+
...(this.executionsGeneration ? { executionsGeneration: this.executionsGeneration } : {}),
|
|
229
|
+
activeStreams: [...this.streams.values()].map(stream => stream.value), completionOnlyStreams: [...this.completionOnly.values()],
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
history(cursor, revision = this.options.store.sessionMetadata(this.id).revision, limit = LIMITS.pageEntries) {
|
|
233
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > LIMITS.pageEntries)
|
|
234
|
+
throw new Error('Invalid history page limit.');
|
|
235
|
+
const branch = this.session.sessionManager.getLeafId() ?? this.id;
|
|
236
|
+
const entries = this.session.sessionManager.getBranch().filter(isMessageEntry);
|
|
237
|
+
let end = entries.length;
|
|
238
|
+
if (cursor) {
|
|
239
|
+
let parsed;
|
|
240
|
+
try {
|
|
241
|
+
parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
throw new Error('resync_required');
|
|
245
|
+
}
|
|
246
|
+
if (!Array.isArray(parsed) || parsed[0] !== this.id || parsed[1] !== revision || parsed[2] !== branch
|
|
247
|
+
|| !Number.isSafeInteger(parsed[3]) || parsed[3] < 0 || parsed[3] > end)
|
|
248
|
+
throw new Error('resync_required');
|
|
249
|
+
end = parsed[3];
|
|
250
|
+
}
|
|
251
|
+
const page = [];
|
|
252
|
+
let bytes = 0;
|
|
253
|
+
while (end > 0 && page.length < limit) {
|
|
254
|
+
const message = this.message(entries[end - 1]);
|
|
255
|
+
const size = Buffer.byteLength(JSON.stringify(message));
|
|
256
|
+
if (bytes + size > LIMITS.pageBytes / 2) {
|
|
257
|
+
if (!page.length)
|
|
258
|
+
throw new Error('A transcript entry exceeds the history page budget.');
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
page.unshift(message);
|
|
262
|
+
bytes += size;
|
|
263
|
+
end--;
|
|
264
|
+
}
|
|
265
|
+
return { revision, branch, entries: page, ...(end ? { cursor: Buffer.from(JSON.stringify([this.id, revision, branch, end])).toString('base64url') } : {}) };
|
|
266
|
+
}
|
|
267
|
+
presentations() { return [...this.presentation.values()]; }
|
|
268
|
+
setPresentation(value) {
|
|
269
|
+
if ('remove' in value) {
|
|
270
|
+
this.presentation.delete(value.id);
|
|
271
|
+
if (this.compactionDirty || this.displayDirty)
|
|
272
|
+
this.schedule();
|
|
273
|
+
this.publish({ type: 'presentation.removed', id: value.id });
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const parsed = parseSchema(PresentationSchema, value);
|
|
277
|
+
this.publish({ type: 'presentation.updated', presentation: parsed });
|
|
278
|
+
this.presentation.set(parsed.id, parsed);
|
|
279
|
+
}
|
|
280
|
+
dispose() {
|
|
281
|
+
if (this.disposed)
|
|
282
|
+
return;
|
|
283
|
+
this.disposed = true;
|
|
284
|
+
if (this.timer)
|
|
285
|
+
clearTimeout(this.timer);
|
|
286
|
+
this.unsubscribe();
|
|
287
|
+
for (const restore of this.restoreQueueMethods.reverse())
|
|
288
|
+
restore();
|
|
289
|
+
this.restoreQueueMethods.length = 0;
|
|
290
|
+
this.queued.clear();
|
|
291
|
+
this.streams.clear();
|
|
292
|
+
this.streamsBytes = 0;
|
|
293
|
+
this.completionOnly.clear();
|
|
294
|
+
this.dirtyCompletionOnly.clear();
|
|
295
|
+
}
|
|
296
|
+
receive(event) {
|
|
297
|
+
if (this.disposed)
|
|
298
|
+
return;
|
|
299
|
+
if (event.type === 'compaction_start' || event.type === 'compaction_end') {
|
|
300
|
+
this.compacting = event.type === 'compaction_start';
|
|
301
|
+
this.compactionDirty = true;
|
|
302
|
+
}
|
|
303
|
+
if (event.type === 'message_start') {
|
|
304
|
+
const observed = [...this.queued].find(([, entry]) => entry.message === event.message);
|
|
305
|
+
if (observed)
|
|
306
|
+
this.queued.delete(observed[0]);
|
|
307
|
+
}
|
|
308
|
+
if (event.type === 'agent_start') {
|
|
309
|
+
const previous = this.options.store.sessionMetadata(this.id).run;
|
|
310
|
+
const run = { state: 'running', id: (previous.state === 'running' || previous.state === 'waiting') && previous.id ? previous.id : randomUUID(), ...(this.origin ? { origin: this.origin } : {}) };
|
|
311
|
+
this.publish({ type: 'run.updated', run }, { run });
|
|
312
|
+
this.runtimeRunId = run.id;
|
|
313
|
+
}
|
|
314
|
+
if (event.type === 'agent_settled') {
|
|
315
|
+
this.settleRun(this.runtimeRunId);
|
|
316
|
+
this.runtimeRunId = undefined;
|
|
317
|
+
}
|
|
318
|
+
if (event.type === 'message_start' && event.message.role === 'assistant') {
|
|
319
|
+
// Cursor changes clear client streams, so publish them before response deltas.
|
|
320
|
+
this.syncCursor();
|
|
321
|
+
this.assistant = randomUUID();
|
|
322
|
+
this.liveIds.set(event.message, this.assistant);
|
|
323
|
+
const display = messageDisplay(event.message);
|
|
324
|
+
this.activeDisplay = display?.kind === 'assistant' ? { message: this.assistant, display } : undefined;
|
|
325
|
+
this.displayDirty = true;
|
|
326
|
+
this.flushDisplay();
|
|
327
|
+
}
|
|
328
|
+
if (event.type === 'message_update' && (this.liveIds.get(event.message) ?? this.assistant)) {
|
|
329
|
+
if (!this.activeDisplay) {
|
|
330
|
+
const display = messageDisplay(event.message);
|
|
331
|
+
if (display?.kind === 'assistant') {
|
|
332
|
+
this.activeDisplay = { message: (this.liveIds.get(event.message) ?? this.assistant), display };
|
|
333
|
+
this.displayDirty = true;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const delta = event.assistantMessageEvent;
|
|
337
|
+
if (delta.type === 'text_delta' || delta.type === 'thinking_delta')
|
|
338
|
+
this.delta((this.liveIds.get(event.message) ?? this.assistant), delta.contentIndex, delta.type === 'text_delta' ? 'assistant' : 'thinking', delta.delta);
|
|
339
|
+
}
|
|
340
|
+
if (event.type === 'message_end' && event.message.role === 'assistant' && this.assistant) {
|
|
341
|
+
this.liveIds.set(event.message, this.assistant);
|
|
342
|
+
this.completedStreams.add(this.assistant);
|
|
343
|
+
if (this.completedStreams.size > 256)
|
|
344
|
+
this.completedStreams.delete(this.completedStreams.values().next().value);
|
|
345
|
+
this.assistant = undefined;
|
|
346
|
+
}
|
|
347
|
+
if (event.type === 'tool_execution_start') {
|
|
348
|
+
this.tools.set(event.toolCallId, { id: event.toolCallId, name: event.toolName, message: this.assistant ?? this.latestAssistantId(), state: 'running', output: '', truncated: false });
|
|
349
|
+
this.dirtyTools.add(event.toolCallId);
|
|
350
|
+
}
|
|
351
|
+
if (event.type === 'tool_execution_update' || event.type === 'tool_execution_end') {
|
|
352
|
+
const tool = this.tools.get(event.toolCallId);
|
|
353
|
+
if (tool) {
|
|
354
|
+
const result = event.type === 'tool_execution_update' ? event.partialResult : event.result;
|
|
355
|
+
const output = resultText(result);
|
|
356
|
+
this.tools.set(tool.id, { ...tool, output: boundedText(output, LIMITS.statusTextBytes), truncated: Buffer.byteLength(output) > LIMITS.statusTextBytes,
|
|
357
|
+
state: event.type === 'tool_execution_end' ? event.isError ? 'failed' : 'completed' : tool.state });
|
|
358
|
+
this.dirtyTools.add(tool.id);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
this.schedule();
|
|
362
|
+
}
|
|
363
|
+
schedule() {
|
|
364
|
+
this.timer ??= setTimeout(() => { this.timer = undefined; try {
|
|
365
|
+
this.refresh();
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
this.options.onError(error);
|
|
369
|
+
} }, this.options.coalesceMs ?? 25);
|
|
370
|
+
this.timer.unref();
|
|
371
|
+
}
|
|
372
|
+
flushDisplay() {
|
|
373
|
+
if (!this.displayDirty)
|
|
374
|
+
return;
|
|
375
|
+
const id = MESSAGE_DISPLAY_ID;
|
|
376
|
+
if (this.activeDisplay) {
|
|
377
|
+
if (!this.presentation.has(id) && this.presentation.size >= LIMITS.pageEntries)
|
|
378
|
+
return;
|
|
379
|
+
this.setPresentation({ id, kind: 'message_display', data: this.activeDisplay });
|
|
380
|
+
}
|
|
381
|
+
else if (this.presentation.has(id))
|
|
382
|
+
this.setPresentation({ id, remove: true });
|
|
383
|
+
this.displayDirty = false;
|
|
384
|
+
}
|
|
385
|
+
latestAssistantId() {
|
|
386
|
+
const entry = [...this.session.sessionManager.getEntries()].reverse().find(item => item.type === 'message' && item.message.role === 'assistant');
|
|
387
|
+
return entry && isMessageEntry(entry) ? this.messageId(entry) : this.id;
|
|
388
|
+
}
|
|
389
|
+
delta(message, index, kind, text) {
|
|
390
|
+
if (this.completedStreams.has(message))
|
|
391
|
+
return;
|
|
392
|
+
const id = `${message}:${index}`;
|
|
393
|
+
if (this.completionOnly.has(id))
|
|
394
|
+
return;
|
|
395
|
+
const old = this.streams.get(id);
|
|
396
|
+
text = (old?.pendingHighSurrogate ?? '') + text;
|
|
397
|
+
const lastCodeUnit = text.charCodeAt(text.length - 1);
|
|
398
|
+
const pendingHighSurrogate = lastCodeUnit >= 0xd800 && lastCodeUnit <= 0xdbff ? text.slice(-1) : '';
|
|
399
|
+
// JSON frames cannot contain half a surrogate pair; completion replaces any withheld tail.
|
|
400
|
+
if (pendingHighSurrogate)
|
|
401
|
+
text = text.slice(0, -1);
|
|
402
|
+
const offset = old?.value.offset ?? 0;
|
|
403
|
+
let nextOffset = offset;
|
|
404
|
+
let malformed = false;
|
|
405
|
+
for (const point of text) {
|
|
406
|
+
if (point.length === 1 && point >= '\ud800' && point <= '\udfff') {
|
|
407
|
+
malformed = true;
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
nextOffset++;
|
|
411
|
+
}
|
|
412
|
+
const bytes = Buffer.byteLength(text);
|
|
413
|
+
if (malformed || (!old && this.streams.size + this.completionOnly.size >= LIMITS.activeStreams)
|
|
414
|
+
|| (old?.bytes ?? 0) + bytes > LIMITS.activeStreamBytes
|
|
415
|
+
|| this.streamsBytes + bytes > LIMITS.activeStreamsBytes) {
|
|
416
|
+
this.removeStream(id);
|
|
417
|
+
if (this.streams.size + this.completionOnly.size >= LIMITS.activeStreams)
|
|
418
|
+
throw new Error('Active stream capacity exceeded.');
|
|
419
|
+
const stream = { id, message, kind, reason: malformed ? 'lost' : 'limit' };
|
|
420
|
+
this.completionOnly.set(id, stream);
|
|
421
|
+
this.dirtyCompletionOnly.add(id);
|
|
422
|
+
this.publishCompletionOnly(id);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
this.streams.set(id, { value: { id, message, kind, text: (old?.value.text ?? '') + text, offset: nextOffset }, bytes: (old?.bytes ?? 0) + bytes,
|
|
426
|
+
pendingHighSurrogate });
|
|
427
|
+
this.streamsBytes += bytes;
|
|
428
|
+
if (text)
|
|
429
|
+
this.options.publish({ v: 1, session: this.id, stream: { id, message, kind, offset }, text });
|
|
430
|
+
}
|
|
431
|
+
removeStream(id) {
|
|
432
|
+
const stream = this.streams.get(id);
|
|
433
|
+
if (!stream)
|
|
434
|
+
return;
|
|
435
|
+
this.streamsBytes -= stream.bytes;
|
|
436
|
+
this.streams.delete(id);
|
|
437
|
+
}
|
|
438
|
+
publishCompletionOnly(id) {
|
|
439
|
+
const stream = this.completionOnly.get(id);
|
|
440
|
+
if (stream)
|
|
441
|
+
this.publish({ type: 'stream.completion_only', stream });
|
|
442
|
+
this.dirtyCompletionOnly.delete(id);
|
|
443
|
+
}
|
|
444
|
+
refreshQueue() {
|
|
445
|
+
if (this.queueObserved && this.session.isIdle && !this.session.agent.hasQueuedMessages()) {
|
|
446
|
+
this.queued.clear();
|
|
447
|
+
this.queueLost = false;
|
|
448
|
+
}
|
|
449
|
+
const next = [...this.queued.values()].flatMap(entry => entry.item ? [entry.item] : []);
|
|
450
|
+
const key = digest(next);
|
|
451
|
+
if (key !== this.queueKey) {
|
|
452
|
+
const commit = this.options.store.project(this.id, [{ type: 'queue', ids: next.map(item => item.id), identity: { sha256: digest(next), bytes: Buffer.byteLength(JSON.stringify(next)) } }]);
|
|
453
|
+
this.queue = next;
|
|
454
|
+
this.queueKey = key;
|
|
455
|
+
this.options.publish({ v: 1, session: this.id, rev: commit.value.revision, change: { type: 'queue.updated', queue: next } });
|
|
456
|
+
}
|
|
457
|
+
const notice = this.queueLost ? 'Native queue observation is incomplete. Queued input remains under runtime control; targeted editing is unavailable.'
|
|
458
|
+
: [...this.queued.values()].some(entry => !entry.item) ? 'Some native queued input cannot be represented as a client prompt. It remains under runtime control.' : '';
|
|
459
|
+
if (notice !== this.queueNotice) {
|
|
460
|
+
this.setPresentation(notice ? { id: 'unrepresented-queue', kind: 'notification', level: 'warning', text: notice } : { id: 'unrepresented-queue', remove: true });
|
|
461
|
+
this.queueNotice = notice;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
observePublicQueue() {
|
|
465
|
+
const agent = this.session.agent;
|
|
466
|
+
if (!agent || typeof agent.steer !== 'function' || typeof agent.followUp !== 'function')
|
|
467
|
+
return;
|
|
468
|
+
this.queueObserved = true;
|
|
469
|
+
this.queueKey = digest([]);
|
|
470
|
+
if (agent.hasQueuedMessages())
|
|
471
|
+
this.loseQueueObservation();
|
|
472
|
+
const projection = this;
|
|
473
|
+
for (const [name, kind] of [['steer', 'steer'], ['followUp', 'follow_up']]) {
|
|
474
|
+
const original = agent[name];
|
|
475
|
+
const wrapped = function (message) {
|
|
476
|
+
const result = original.call(this, message);
|
|
477
|
+
if (this === agent && !projection.disposed && !projection.queueLost) {
|
|
478
|
+
try {
|
|
479
|
+
projection.observeEnqueue(message, kind);
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
projection.loseQueueObservation();
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return result;
|
|
486
|
+
};
|
|
487
|
+
agent[name] = wrapped;
|
|
488
|
+
this.restoreQueueMethods.push(() => { if (agent[name] === wrapped)
|
|
489
|
+
agent[name] = original; });
|
|
490
|
+
}
|
|
491
|
+
for (const name of ['clearSteeringQueue', 'clearFollowUpQueue', 'clearAllQueues']) {
|
|
492
|
+
const original = agent[name];
|
|
493
|
+
const wrapped = function () {
|
|
494
|
+
const result = original.call(this);
|
|
495
|
+
if (this === agent && !projection.disposed) {
|
|
496
|
+
for (const [id, entry] of projection.queued) {
|
|
497
|
+
if (name === 'clearAllQueues' || entry.kind === (name === 'clearSteeringQueue' ? 'steer' : 'follow_up'))
|
|
498
|
+
projection.queued.delete(id);
|
|
499
|
+
}
|
|
500
|
+
projection.schedule();
|
|
501
|
+
}
|
|
502
|
+
return result;
|
|
503
|
+
};
|
|
504
|
+
agent[name] = wrapped;
|
|
505
|
+
this.restoreQueueMethods.push(() => { if (agent[name] === wrapped)
|
|
506
|
+
agent[name] = original; });
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
observeEnqueue(message, kind) {
|
|
510
|
+
if (this.queued.size >= LIMITS.pageEntries) {
|
|
511
|
+
this.loseQueueObservation();
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const id = randomUUID();
|
|
515
|
+
const context = this.queueContext.getStore();
|
|
516
|
+
let item;
|
|
517
|
+
if (message.role === 'user') {
|
|
518
|
+
try {
|
|
519
|
+
const prompt = typeof message.content === 'string' ? { text: message.content }
|
|
520
|
+
: { text: '', attachments: message.content.map(part => part.type === 'text' ? { type: 'text', text: part.text }
|
|
521
|
+
: { type: 'image', mimeType: part.mimeType, data: part.data }) };
|
|
522
|
+
const parsed = parseSchema(PromptSchema, prompt);
|
|
523
|
+
validatePrompt(parsed);
|
|
524
|
+
item = { id, kind, prompt: parsed, origin: context?.origin ?? { kind: 'automation', source: 'runtime' } };
|
|
525
|
+
}
|
|
526
|
+
catch { /* Unrepresentable queued input stays untouched in the native runtime. */ }
|
|
527
|
+
}
|
|
528
|
+
this.queued.set(id, { message, kind, ...(item ? { item } : {}) });
|
|
529
|
+
if (context) {
|
|
530
|
+
if (context.ids.length >= LIMITS.pageEntries)
|
|
531
|
+
this.loseQueueObservation();
|
|
532
|
+
else
|
|
533
|
+
context.ids.push(id);
|
|
534
|
+
}
|
|
535
|
+
if (Buffer.byteLength(JSON.stringify([...this.queued.values()].flatMap(entry => entry.item ? [entry.item] : []))) > LIMITS.pageBytes / 2)
|
|
536
|
+
this.loseQueueObservation();
|
|
537
|
+
this.schedule();
|
|
538
|
+
}
|
|
539
|
+
loseQueueObservation() {
|
|
540
|
+
this.queueLost = true;
|
|
541
|
+
this.queued.clear();
|
|
542
|
+
this.schedule();
|
|
543
|
+
}
|
|
544
|
+
refreshCatalog() {
|
|
545
|
+
const active = new Set(this.session.getActiveToolNames());
|
|
546
|
+
const provenance = new Map(this.options.binding.tools.provenance.map(tool => [tool.name, tool]));
|
|
547
|
+
const tools = this.session.getAllTools().map(tool => {
|
|
548
|
+
const source = provenance.get(tool.name);
|
|
549
|
+
return { name: tool.name, label: tool.name, description: tool.description, parameters: tool.parameters,
|
|
550
|
+
provenance: source?.source ?? 'external', source: boundedText(source?.sourceId ?? 'unknown', 128), active: active.has(tool.name) };
|
|
551
|
+
});
|
|
552
|
+
const commands = this.session.extensionRunner.getRegisteredCommands().filter(command => this.options.commandAvailable?.(command.name) ?? false).map(command => ({ name: command.name,
|
|
553
|
+
description: command.description ?? '', source: boundedText(command.sourceInfo?.path ?? 'extension', 128), scope: command.sourceInfo?.scope, kind: 'extension' }));
|
|
554
|
+
const skills = this.session.settingsManager?.getEnableSkillCommands() === false ? [] : this.session.resourceLoader.getSkills().skills;
|
|
555
|
+
for (const skill of skills) {
|
|
556
|
+
const name = `skill:${skill.name}`;
|
|
557
|
+
if (!commands.some(command => command.name === name) && (this.options.commandAvailable?.(name) ?? false))
|
|
558
|
+
commands.push({ name,
|
|
559
|
+
description: skill.description, source: boundedText(skill.filePath, 128), scope: skill.sourceInfo.scope, kind: 'skill' });
|
|
560
|
+
}
|
|
561
|
+
const available = this.session.scopedModels.length ? this.session.scopedModels.map(({ model }) => model)
|
|
562
|
+
: this.session.modelRuntime.getAvailableSnapshot();
|
|
563
|
+
const models = available.map(model => ({ provider: model.provider, id: model.id, label: model.name, thinking: getSupportedThinkingLevels(model) }));
|
|
564
|
+
const raw = { tools, commands, models };
|
|
565
|
+
const serialized = JSON.stringify(raw);
|
|
566
|
+
if (this.cachedCatalog?.serialized === serialized)
|
|
567
|
+
return this.cachedCatalog;
|
|
568
|
+
const value = parseSchema(CatalogSchema, raw);
|
|
569
|
+
return this.cachedCatalog = { serialized, key: digest(value), value };
|
|
570
|
+
}
|
|
571
|
+
messageId(entry) {
|
|
572
|
+
let id = this.messageIds.get(entry.id);
|
|
573
|
+
if (!id) {
|
|
574
|
+
id = entry.type === 'message' ? this.liveIds.get(entry.message) ?? entry.id : entry.id;
|
|
575
|
+
this.messageIds.set(entry.id, id);
|
|
576
|
+
}
|
|
577
|
+
return id;
|
|
578
|
+
}
|
|
579
|
+
message(entry) {
|
|
580
|
+
const message = projectPiMessage(entry, this.messageId(entry), this.origins.get(entry.id));
|
|
581
|
+
const project = this.options.binding.projectMessage;
|
|
582
|
+
if (!project)
|
|
583
|
+
return message;
|
|
584
|
+
const value = parseSchema(MessageSchema, project(entry, message));
|
|
585
|
+
if (value.id !== message.id || value.entryId !== message.entryId)
|
|
586
|
+
throw new Error('Message projection changed canonical identity.');
|
|
587
|
+
return value;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
// Custom messages marked hidden are model context only (memory, guidance); clients and publication never receive them.
|
|
591
|
+
function isMessageEntry(entry) {
|
|
592
|
+
return entry.type === 'message' || entry.type === 'custom_message' && entry.display || entry.type === 'compaction' || entry.type === 'branch_summary';
|
|
593
|
+
}
|
|
594
|
+
export function projectPiMessage(entry, id = entry.id, origin) {
|
|
595
|
+
const raw = entry.type === 'compaction' ? { role: 'system', content: entry.summary }
|
|
596
|
+
: entry.type === 'branch_summary' ? { role: 'system', content: entry.summary } : entry.type === 'custom_message' ? { role: 'custom', content: entry.content, customType: entry.customType, details: entry.details } : entry.message;
|
|
597
|
+
const content = [];
|
|
598
|
+
const rawContent = 'content' in raw ? raw.content : raw.role === 'bashExecution' ? `$ ${raw.command}\n${raw.output}` : 'summary' in raw ? raw.summary : undefined;
|
|
599
|
+
if (typeof rawContent === 'string')
|
|
600
|
+
content.push({ type: 'text', text: rawContent });
|
|
601
|
+
else if (Array.isArray(rawContent))
|
|
602
|
+
for (const part of rawContent) {
|
|
603
|
+
if (part.type === 'text')
|
|
604
|
+
content.push({ type: 'text', text: part.text });
|
|
605
|
+
else if (part.type === 'thinking')
|
|
606
|
+
content.push({ type: 'thinking', text: part.thinking });
|
|
607
|
+
else if (part.type === 'image') {
|
|
608
|
+
validateBase64(part.data);
|
|
609
|
+
content.push({ type: 'image', mimeType: part.mimeType, data: part.data });
|
|
610
|
+
}
|
|
611
|
+
else if (part.type === 'toolCall')
|
|
612
|
+
content.push({ type: 'tool_call', id: part.id, name: part.name, arguments: parseSchema(JsonValueSchema, part.arguments) });
|
|
613
|
+
}
|
|
614
|
+
if (raw.role === 'toolResult') {
|
|
615
|
+
const images = content.filter(part => part.type === 'image');
|
|
616
|
+
content.splice(0, content.length, { type: 'tool_result', id: raw.toolCallId, text: resultText(raw), isError: raw.isError,
|
|
617
|
+
...(raw.details === undefined ? {} : { details: parseSchema(JsonValueSchema, raw.details) }) }, ...images);
|
|
618
|
+
}
|
|
619
|
+
const display = entry.type === 'message' ? messageDisplay(entry.message) : undefined;
|
|
620
|
+
let details = 'details' in raw ? raw.details : undefined;
|
|
621
|
+
if (raw.role === 'bashExecution') {
|
|
622
|
+
details = { command: raw.command, exitCode: raw.exitCode ?? null, cancelled: raw.cancelled, truncated: raw.truncated,
|
|
623
|
+
excludeFromContext: raw.excludeFromContext ?? false, ...(raw.fullOutputPath ? { fullOutputPath: raw.fullOutputPath } : {}) };
|
|
624
|
+
content.splice(0, content.length, { type: 'text', text: raw.output });
|
|
625
|
+
}
|
|
626
|
+
// Tool results keep their own details on the tool_result part, so non-object details are replaced to carry display metadata.
|
|
627
|
+
if (display)
|
|
628
|
+
details = details !== null && typeof details === 'object' && !Array.isArray(details)
|
|
629
|
+
? { ...details, [MESSAGE_DISPLAY_KEY]: display } : { [MESSAGE_DISPLAY_KEY]: display };
|
|
630
|
+
return parseSchema(MessageSchema, { id, entryId: entry.id, ...(entry.parentId ? { parentEntryId: entry.parentId } : {}),
|
|
631
|
+
role: raw.role === 'toolResult' ? 'tool' : ['user', 'assistant', 'system', 'custom'].includes(raw.role) ? raw.role : 'custom',
|
|
632
|
+
content, timestamp: Math.max(0, Date.parse(entry.timestamp) || 0),
|
|
633
|
+
...('customType' in raw ? { customType: raw.customType } : {}),
|
|
634
|
+
...(raw.role === 'bashExecution' ? { customType: 'bashExecution' } : {}),
|
|
635
|
+
...(details === undefined ? {} : { details }),
|
|
636
|
+
...(origin ? { origin } : {}),
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
export function digest(value) { return createHash('sha256').update(JSON.stringify(value) ?? 'null').digest('hex'); }
|
|
640
|
+
export function boundedText(value, bytes) {
|
|
641
|
+
if (Buffer.byteLength(value) <= bytes)
|
|
642
|
+
return value;
|
|
643
|
+
let result = '';
|
|
644
|
+
let size = 0;
|
|
645
|
+
for (const point of value) {
|
|
646
|
+
size += Buffer.byteLength(point);
|
|
647
|
+
if (size > bytes)
|
|
648
|
+
break;
|
|
649
|
+
result += point;
|
|
650
|
+
}
|
|
651
|
+
return result;
|
|
652
|
+
}
|
|
653
|
+
function resultText(value) {
|
|
654
|
+
if (!value || typeof value !== 'object' || !('content' in value) || !Array.isArray(value.content))
|
|
655
|
+
return '';
|
|
656
|
+
return value.content.filter(part => part?.type === 'text' && typeof part.text === 'string').map(part => part.text).join('\n');
|
|
657
|
+
}
|
|
658
|
+
export function profilePolicy(session) {
|
|
659
|
+
return { mode: session.policy.effective, generation: session.policy.generation };
|
|
660
|
+
}
|
|
661
|
+
//# sourceMappingURL=projection.js.map
|