@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.
Files changed (288) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +69 -0
  3. package/README.md +61 -2
  4. package/dist/client.d.ts +5 -0
  5. package/dist/client.d.ts.map +1 -0
  6. package/dist/client.js +4 -0
  7. package/dist/client.js.map +1 -0
  8. package/dist/daemon/admission.d.ts +39 -0
  9. package/dist/daemon/admission.d.ts.map +1 -0
  10. package/dist/daemon/admission.js +175 -0
  11. package/dist/daemon/admission.js.map +1 -0
  12. package/dist/daemon/agent-logs.d.ts +11 -0
  13. package/dist/daemon/agent-logs.d.ts.map +1 -0
  14. package/dist/daemon/agent-logs.js +31 -0
  15. package/dist/daemon/agent-logs.js.map +1 -0
  16. package/dist/daemon/bootstrap.d.ts +7 -0
  17. package/dist/daemon/bootstrap.d.ts.map +1 -0
  18. package/dist/daemon/bootstrap.js +4 -0
  19. package/dist/daemon/bootstrap.js.map +1 -0
  20. package/dist/daemon/broker.d.ts +94 -0
  21. package/dist/daemon/broker.d.ts.map +1 -0
  22. package/dist/daemon/broker.js +374 -0
  23. package/dist/daemon/broker.js.map +1 -0
  24. package/dist/daemon/commands.d.ts +47 -0
  25. package/dist/daemon/commands.d.ts.map +1 -0
  26. package/dist/daemon/commands.js +639 -0
  27. package/dist/daemon/commands.js.map +1 -0
  28. package/dist/daemon/control-recovery.d.ts +9 -0
  29. package/dist/daemon/control-recovery.d.ts.map +1 -0
  30. package/dist/daemon/control-recovery.js +68 -0
  31. package/dist/daemon/control-recovery.js.map +1 -0
  32. package/dist/daemon/control-state.d.ts +1915 -0
  33. package/dist/daemon/control-state.d.ts.map +1 -0
  34. package/dist/daemon/control-state.js +154 -0
  35. package/dist/daemon/control-state.js.map +1 -0
  36. package/dist/daemon/control-store.d.ts +113 -0
  37. package/dist/daemon/control-store.d.ts.map +1 -0
  38. package/dist/daemon/control-store.js +901 -0
  39. package/dist/daemon/control-store.js.map +1 -0
  40. package/dist/daemon/execution-policy.d.ts +15 -0
  41. package/dist/daemon/execution-policy.d.ts.map +1 -0
  42. package/dist/daemon/execution-policy.js +42 -0
  43. package/dist/daemon/execution-policy.js.map +1 -0
  44. package/dist/daemon/execution-services.d.ts +5 -0
  45. package/dist/daemon/execution-services.d.ts.map +1 -0
  46. package/dist/daemon/execution-services.js +73 -0
  47. package/dist/daemon/execution-services.js.map +1 -0
  48. package/dist/daemon/executions.d.ts +134 -0
  49. package/dist/daemon/executions.d.ts.map +1 -0
  50. package/dist/daemon/executions.js +486 -0
  51. package/dist/daemon/executions.js.map +1 -0
  52. package/dist/daemon/extension-ui.d.ts +24 -0
  53. package/dist/daemon/extension-ui.d.ts.map +1 -0
  54. package/dist/daemon/extension-ui.js +161 -0
  55. package/dist/daemon/extension-ui.js.map +1 -0
  56. package/dist/daemon/fanout.d.ts +40 -0
  57. package/dist/daemon/fanout.d.ts.map +1 -0
  58. package/dist/daemon/fanout.js +304 -0
  59. package/dist/daemon/fanout.js.map +1 -0
  60. package/dist/daemon/headless-json.d.ts +3 -0
  61. package/dist/daemon/headless-json.d.ts.map +1 -0
  62. package/dist/daemon/headless-json.js +20 -0
  63. package/dist/daemon/headless-json.js.map +1 -0
  64. package/dist/daemon/host-config.d.ts +61 -0
  65. package/dist/daemon/host-config.d.ts.map +1 -0
  66. package/dist/daemon/host-config.js +167 -0
  67. package/dist/daemon/host-config.js.map +1 -0
  68. package/dist/daemon/host-services.d.ts +61 -0
  69. package/dist/daemon/host-services.d.ts.map +1 -0
  70. package/dist/daemon/host-services.js +2 -0
  71. package/dist/daemon/host-services.js.map +1 -0
  72. package/dist/daemon/ipc.d.ts +14 -0
  73. package/dist/daemon/ipc.d.ts.map +1 -0
  74. package/dist/daemon/ipc.js +233 -0
  75. package/dist/daemon/ipc.js.map +1 -0
  76. package/dist/daemon/legacy-sessions.d.ts +48 -0
  77. package/dist/daemon/legacy-sessions.d.ts.map +1 -0
  78. package/dist/daemon/legacy-sessions.js +328 -0
  79. package/dist/daemon/legacy-sessions.js.map +1 -0
  80. package/dist/daemon/lifecycle.d.ts +44 -0
  81. package/dist/daemon/lifecycle.d.ts.map +1 -0
  82. package/dist/daemon/lifecycle.js +164 -0
  83. package/dist/daemon/lifecycle.js.map +1 -0
  84. package/dist/daemon/main.d.ts +3 -0
  85. package/dist/daemon/main.d.ts.map +1 -0
  86. package/dist/daemon/main.js +83 -0
  87. package/dist/daemon/main.js.map +1 -0
  88. package/dist/daemon/message-display.d.ts +4 -0
  89. package/dist/daemon/message-display.d.ts.map +1 -0
  90. package/dist/daemon/message-display.js +20 -0
  91. package/dist/daemon/message-display.js.map +1 -0
  92. package/dist/daemon/native-ownership.d.ts +13 -0
  93. package/dist/daemon/native-ownership.d.ts.map +1 -0
  94. package/dist/daemon/native-ownership.js +173 -0
  95. package/dist/daemon/native-ownership.js.map +1 -0
  96. package/dist/daemon/operational-log.d.ts +23 -0
  97. package/dist/daemon/operational-log.d.ts.map +1 -0
  98. package/dist/daemon/operational-log.js +79 -0
  99. package/dist/daemon/operational-log.js.map +1 -0
  100. package/dist/daemon/ownership.d.ts +35 -0
  101. package/dist/daemon/ownership.d.ts.map +1 -0
  102. package/dist/daemon/ownership.js +395 -0
  103. package/dist/daemon/ownership.js.map +1 -0
  104. package/dist/daemon/persistence.d.ts +110 -0
  105. package/dist/daemon/persistence.d.ts.map +1 -0
  106. package/dist/daemon/persistence.js +540 -0
  107. package/dist/daemon/persistence.js.map +1 -0
  108. package/dist/daemon/process-adapter.d.ts +23 -0
  109. package/dist/daemon/process-adapter.d.ts.map +1 -0
  110. package/dist/daemon/process-adapter.js +2 -0
  111. package/dist/daemon/process-adapter.js.map +1 -0
  112. package/dist/daemon/processes.d.ts +102 -0
  113. package/dist/daemon/processes.d.ts.map +1 -0
  114. package/dist/daemon/processes.js +691 -0
  115. package/dist/daemon/processes.js.map +1 -0
  116. package/dist/daemon/profile-storage.d.ts +16 -0
  117. package/dist/daemon/profile-storage.d.ts.map +1 -0
  118. package/dist/daemon/profile-storage.js +148 -0
  119. package/dist/daemon/profile-storage.js.map +1 -0
  120. package/dist/daemon/projection.d.ts +110 -0
  121. package/dist/daemon/projection.d.ts.map +1 -0
  122. package/dist/daemon/projection.js +661 -0
  123. package/dist/daemon/projection.js.map +1 -0
  124. package/dist/daemon/provider-loader.d.ts +28 -0
  125. package/dist/daemon/provider-loader.d.ts.map +1 -0
  126. package/dist/daemon/provider-loader.js +376 -0
  127. package/dist/daemon/provider-loader.js.map +1 -0
  128. package/dist/daemon/provider.d.ts +92 -0
  129. package/dist/daemon/provider.d.ts.map +1 -0
  130. package/dist/daemon/provider.js +31 -0
  131. package/dist/daemon/provider.js.map +1 -0
  132. package/dist/daemon/registry.d.ts +209 -0
  133. package/dist/daemon/registry.d.ts.map +1 -0
  134. package/dist/daemon/registry.js +1591 -0
  135. package/dist/daemon/registry.js.map +1 -0
  136. package/dist/daemon/runtime-dependencies.d.ts +5 -0
  137. package/dist/daemon/runtime-dependencies.d.ts.map +1 -0
  138. package/dist/daemon/runtime-dependencies.js +31 -0
  139. package/dist/daemon/runtime-dependencies.js.map +1 -0
  140. package/dist/daemon/server.d.ts +110 -0
  141. package/dist/daemon/server.d.ts.map +1 -0
  142. package/dist/daemon/server.js +556 -0
  143. package/dist/daemon/server.js.map +1 -0
  144. package/dist/daemon/service.d.ts +38 -0
  145. package/dist/daemon/service.d.ts.map +1 -0
  146. package/dist/daemon/service.js +275 -0
  147. package/dist/daemon/service.js.map +1 -0
  148. package/dist/daemon/session-cursor.d.ts +3 -0
  149. package/dist/daemon/session-cursor.d.ts.map +1 -0
  150. package/dist/daemon/session-cursor.js +4 -0
  151. package/dist/daemon/session-cursor.js.map +1 -0
  152. package/dist/daemon/session-execution.d.ts +88 -0
  153. package/dist/daemon/session-execution.d.ts.map +1 -0
  154. package/dist/daemon/session-execution.js +2 -0
  155. package/dist/daemon/session-execution.js.map +1 -0
  156. package/dist/daemon/session-executions.d.ts +5 -0
  157. package/dist/daemon/session-executions.d.ts.map +1 -0
  158. package/dist/daemon/session-executions.js +146 -0
  159. package/dist/daemon/session-executions.js.map +1 -0
  160. package/dist/daemon/session-files.d.ts +28 -0
  161. package/dist/daemon/session-files.d.ts.map +1 -0
  162. package/dist/daemon/session-files.js +189 -0
  163. package/dist/daemon/session-files.js.map +1 -0
  164. package/dist/daemon/session-lifecycle-contracts.d.ts +104 -0
  165. package/dist/daemon/session-lifecycle-contracts.d.ts.map +1 -0
  166. package/dist/daemon/session-lifecycle-contracts.js +2 -0
  167. package/dist/daemon/session-lifecycle-contracts.js.map +1 -0
  168. package/dist/daemon/session-lifecycle.d.ts +26 -0
  169. package/dist/daemon/session-lifecycle.d.ts.map +1 -0
  170. package/dist/daemon/session-lifecycle.js +246 -0
  171. package/dist/daemon/session-lifecycle.js.map +1 -0
  172. package/dist/daemon/session-runtime.d.ts +64 -0
  173. package/dist/daemon/session-runtime.d.ts.map +1 -0
  174. package/dist/daemon/session-runtime.js +3 -0
  175. package/dist/daemon/session-runtime.js.map +1 -0
  176. package/dist/daemon/session-services.d.ts +6 -0
  177. package/dist/daemon/session-services.d.ts.map +1 -0
  178. package/dist/daemon/session-services.js +144 -0
  179. package/dist/daemon/session-services.js.map +1 -0
  180. package/dist/daemon/shutdown-error.d.ts +12 -0
  181. package/dist/daemon/shutdown-error.d.ts.map +1 -0
  182. package/dist/daemon/shutdown-error.js +51 -0
  183. package/dist/daemon/shutdown-error.js.map +1 -0
  184. package/dist/daemon/supervisor.d.ts +28 -0
  185. package/dist/daemon/supervisor.d.ts.map +1 -0
  186. package/dist/daemon/supervisor.js +159 -0
  187. package/dist/daemon/supervisor.js.map +1 -0
  188. package/dist/daemon/target.d.ts +55 -0
  189. package/dist/daemon/target.d.ts.map +1 -0
  190. package/dist/daemon/target.js +191 -0
  191. package/dist/daemon/target.js.map +1 -0
  192. package/dist/daemon/tree.d.ts +7 -0
  193. package/dist/daemon/tree.d.ts.map +1 -0
  194. package/dist/daemon/tree.js +126 -0
  195. package/dist/daemon/tree.js.map +1 -0
  196. package/dist/daemon/ui-vocabulary.d.ts +29 -0
  197. package/dist/daemon/ui-vocabulary.d.ts.map +1 -0
  198. package/dist/daemon/ui-vocabulary.js +323 -0
  199. package/dist/daemon/ui-vocabulary.js.map +1 -0
  200. package/dist/daemon/worker.d.ts +5 -0
  201. package/dist/daemon/worker.d.ts.map +1 -0
  202. package/dist/daemon/worker.js +49 -0
  203. package/dist/daemon/worker.js.map +1 -0
  204. package/dist/index.d.ts +4 -0
  205. package/dist/index.d.ts.map +1 -0
  206. package/dist/index.js +12 -0
  207. package/dist/index.js.map +1 -0
  208. package/dist/lock.d.ts +27 -0
  209. package/dist/lock.d.ts.map +1 -0
  210. package/dist/lock.js +432 -0
  211. package/dist/lock.js.map +1 -0
  212. package/dist/logger.d.ts +9 -0
  213. package/dist/logger.d.ts.map +1 -0
  214. package/dist/logger.js +56 -0
  215. package/dist/logger.js.map +1 -0
  216. package/dist/remote/backoff.d.ts +17 -0
  217. package/dist/remote/backoff.d.ts.map +1 -0
  218. package/dist/remote/backoff.js +18 -0
  219. package/dist/remote/backoff.js.map +1 -0
  220. package/dist/remote/batch.d.ts +3 -0
  221. package/dist/remote/batch.d.ts.map +1 -0
  222. package/dist/remote/batch.js +27 -0
  223. package/dist/remote/batch.js.map +1 -0
  224. package/dist/remote/command-journal.d.ts +30 -0
  225. package/dist/remote/command-journal.d.ts.map +1 -0
  226. package/dist/remote/command-journal.js +72 -0
  227. package/dist/remote/command-journal.js.map +1 -0
  228. package/dist/remote/commands.d.ts +48 -0
  229. package/dist/remote/commands.d.ts.map +1 -0
  230. package/dist/remote/commands.js +145 -0
  231. package/dist/remote/commands.js.map +1 -0
  232. package/dist/remote/connector.d.ts +135 -0
  233. package/dist/remote/connector.d.ts.map +1 -0
  234. package/dist/remote/connector.js +1069 -0
  235. package/dist/remote/connector.js.map +1 -0
  236. package/dist/remote/content.d.ts +17 -0
  237. package/dist/remote/content.d.ts.map +1 -0
  238. package/dist/remote/content.js +116 -0
  239. package/dist/remote/content.js.map +1 -0
  240. package/dist/remote/outbox.d.ts +52 -0
  241. package/dist/remote/outbox.d.ts.map +1 -0
  242. package/dist/remote/outbox.js +321 -0
  243. package/dist/remote/outbox.js.map +1 -0
  244. package/dist/remote/platform.d.ts +53 -0
  245. package/dist/remote/platform.d.ts.map +1 -0
  246. package/dist/remote/platform.js +231 -0
  247. package/dist/remote/platform.js.map +1 -0
  248. package/dist/remote/realtime.d.ts +45 -0
  249. package/dist/remote/realtime.d.ts.map +1 -0
  250. package/dist/remote/realtime.js +265 -0
  251. package/dist/remote/realtime.js.map +1 -0
  252. package/dist/remote/sharing.d.ts +16 -0
  253. package/dist/remote/sharing.d.ts.map +1 -0
  254. package/dist/remote/sharing.js +24 -0
  255. package/dist/remote/sharing.js.map +1 -0
  256. package/dist/remote/state.d.ts +52 -0
  257. package/dist/remote/state.d.ts.map +1 -0
  258. package/dist/remote/state.js +156 -0
  259. package/dist/remote/state.js.map +1 -0
  260. package/dist/remote/text.d.ts +11 -0
  261. package/dist/remote/text.d.ts.map +1 -0
  262. package/dist/remote/text.js +188 -0
  263. package/dist/remote/text.js.map +1 -0
  264. package/dist/remote/token.d.ts +39 -0
  265. package/dist/remote/token.d.ts.map +1 -0
  266. package/dist/remote/token.js +238 -0
  267. package/dist/remote/token.js.map +1 -0
  268. package/dist/runtime-factory.d.ts +12 -0
  269. package/dist/runtime-factory.d.ts.map +1 -0
  270. package/dist/runtime-factory.js +12 -0
  271. package/dist/runtime-factory.js.map +1 -0
  272. package/dist/session-history.d.ts +29 -0
  273. package/dist/session-history.d.ts.map +1 -0
  274. package/dist/session-history.js +374 -0
  275. package/dist/session-history.js.map +1 -0
  276. package/dist/setup/dependencies.d.ts +15 -0
  277. package/dist/setup/dependencies.d.ts.map +1 -0
  278. package/dist/setup/dependencies.js +2 -0
  279. package/dist/setup/dependencies.js.map +1 -0
  280. package/dist/setup/open.d.ts +8 -0
  281. package/dist/setup/open.d.ts.map +1 -0
  282. package/dist/setup/open.js +16 -0
  283. package/dist/setup/open.js.map +1 -0
  284. package/dist/version.d.ts +2 -0
  285. package/dist/version.d.ts.map +1 -0
  286. package/dist/version.js +4 -0
  287. package/dist/version.js.map +1 -0
  288. 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