@markusylisiurunen/tau 0.2.128 → 0.3.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 (232) hide show
  1. package/README.md +130 -75
  2. package/dist/core/async/cli.js +12 -2
  3. package/dist/core/async/cli.js.map +1 -1
  4. package/dist/core/async/http_protocol.js +7 -1
  5. package/dist/core/async/http_protocol.js.map +1 -1
  6. package/dist/core/async/http_server.js +9 -1
  7. package/dist/core/async/http_server.js.map +1 -1
  8. package/dist/core/async/index.js.map +1 -1
  9. package/dist/core/async/session_manager.js +94 -51
  10. package/dist/core/async/session_manager.js.map +1 -1
  11. package/dist/core/auth/auth_manager.js +0 -92
  12. package/dist/core/auth/auth_manager.js.map +1 -1
  13. package/dist/core/auth/credential_store.js +247 -0
  14. package/dist/core/auth/credential_store.js.map +1 -0
  15. package/dist/core/auth/index.js +0 -1
  16. package/dist/core/auth/index.js.map +1 -1
  17. package/dist/core/cli.js +12 -15
  18. package/dist/core/cli.js.map +1 -1
  19. package/dist/core/commands/registry.js +11 -55
  20. package/dist/core/commands/registry.js.map +1 -1
  21. package/dist/core/config/index.js.map +1 -1
  22. package/dist/core/config/runtime.js +0 -1
  23. package/dist/core/config/runtime.js.map +1 -1
  24. package/dist/core/config/schema.js +102 -17
  25. package/dist/core/config/schema.js.map +1 -1
  26. package/dist/core/debug.js +1 -20
  27. package/dist/core/debug.js.map +1 -1
  28. package/dist/core/diff_review/{session.js → bridge.js} +57 -114
  29. package/dist/core/diff_review/bridge.js.map +1 -0
  30. package/dist/core/diff_review/index.js +2 -3
  31. package/dist/core/diff_review/index.js.map +1 -1
  32. package/dist/core/diff_review/review_thread.js +48 -23
  33. package/dist/core/diff_review/review_thread.js.map +1 -1
  34. package/dist/core/diff_review/snapshot.js +40 -11
  35. package/dist/core/diff_review/snapshot.js.map +1 -1
  36. package/dist/core/events/parser.js +43 -12
  37. package/dist/core/events/parser.js.map +1 -1
  38. package/dist/core/index.js +1 -2
  39. package/dist/core/index.js.map +1 -1
  40. package/dist/core/models/catalog.js +3 -3
  41. package/dist/core/models/catalog.js.map +1 -1
  42. package/dist/core/modes/index.js +1 -2
  43. package/dist/core/modes/index.js.map +1 -1
  44. package/dist/core/modes/rpc_server.js +15 -325
  45. package/dist/core/modes/rpc_server.js.map +1 -1
  46. package/dist/core/modes/websocket_server.js +181 -0
  47. package/dist/core/modes/websocket_server.js.map +1 -0
  48. package/dist/core/personas.js +11 -1
  49. package/dist/core/personas.js.map +1 -1
  50. package/dist/core/runtime/chat_runtime.js +19 -12
  51. package/dist/core/runtime/chat_runtime.js.map +1 -1
  52. package/dist/core/runtime/conversation_turn_runtime.js +16 -1
  53. package/dist/core/runtime/conversation_turn_runtime.js.map +1 -1
  54. package/dist/core/runtime/runtime_bootstrap.js +326 -0
  55. package/dist/core/runtime/runtime_bootstrap.js.map +1 -1
  56. package/dist/core/runtime/steering.js +6 -0
  57. package/dist/core/runtime/steering.js.map +1 -0
  58. package/dist/core/session/compaction.js +225 -22
  59. package/dist/core/session/compaction.js.map +1 -1
  60. package/dist/core/session/core_session.js +9 -0
  61. package/dist/core/session/core_session.js.map +1 -1
  62. package/dist/core/session/direct_bash.js +34 -0
  63. package/dist/core/session/direct_bash.js.map +1 -0
  64. package/dist/core/session/pruning.js +595 -0
  65. package/dist/core/session/pruning.js.map +1 -0
  66. package/dist/core/session/runner.js +47 -8
  67. package/dist/core/session/runner.js.map +1 -1
  68. package/dist/core/session/session_engine.js +128 -55
  69. package/dist/core/session/session_engine.js.map +1 -1
  70. package/dist/core/static/prompts/diff-review-wrapper.md +1 -1
  71. package/dist/core/subagents/subagent_engine.js +19 -27
  72. package/dist/core/subagents/subagent_engine.js.map +1 -1
  73. package/dist/core/tools/catalog.js.map +1 -1
  74. package/dist/core/tools/diff_review.js +12 -12
  75. package/dist/core/tools/diff_review.js.map +1 -1
  76. package/dist/core/tools/execution_backend.js +9 -2
  77. package/dist/core/tools/execution_backend.js.map +1 -1
  78. package/dist/core/tools/registry.js.map +1 -1
  79. package/dist/core/usage/logs.js.map +1 -1
  80. package/dist/core/utils/compact.js +3 -1
  81. package/dist/core/utils/compact.js.map +1 -1
  82. package/dist/core/utils/context.js +1 -1
  83. package/dist/core/utils/context.js.map +1 -1
  84. package/dist/core/utils/context_builder.js +1 -18
  85. package/dist/core/utils/context_builder.js.map +1 -1
  86. package/dist/core/utils/gemini_speech.js +1 -1
  87. package/dist/core/utils/gemini_speech.js.map +1 -1
  88. package/dist/core/utils/model_stream.js +105 -14
  89. package/dist/core/utils/model_stream.js.map +1 -1
  90. package/dist/core/utils/project_files.js +11 -16
  91. package/dist/core/utils/project_files.js.map +1 -1
  92. package/dist/core/utils/spawn_capture.js +2 -2
  93. package/dist/core/utils/spawn_capture.js.map +1 -1
  94. package/dist/core/utils/user_metadata.js +35 -2
  95. package/dist/core/utils/user_metadata.js.map +1 -1
  96. package/dist/core/version.js +1 -1
  97. package/dist/core/version.js.map +1 -1
  98. package/dist/diff_tool/app/dist/assets/{index-jn1I_CPZ.js → index-ivRn2dP2.js} +34 -34
  99. package/dist/diff_tool/app/dist/index.html +1 -1
  100. package/dist/execution/cloudflare_sandbox_execution_environment.js +400 -0
  101. package/dist/execution/cloudflare_sandbox_execution_environment.js.map +1 -0
  102. package/dist/execution/execution_environment.js +24 -0
  103. package/dist/execution/execution_environment.js.map +1 -0
  104. package/dist/execution/fly_sprite_execution_environment.js +559 -0
  105. package/dist/execution/fly_sprite_execution_environment.js.map +1 -0
  106. package/dist/execution/local_execution_environment.js +93 -0
  107. package/dist/execution/local_execution_environment.js.map +1 -0
  108. package/dist/execution/runtime_config_snapshot.js +209 -0
  109. package/dist/execution/runtime_config_snapshot.js.map +1 -0
  110. package/dist/execution/sandbox_tool_helpers.js +55 -0
  111. package/dist/execution/sandbox_tool_helpers.js.map +1 -0
  112. package/dist/execution/tool_backend_execution_environment.js +55 -0
  113. package/dist/execution/tool_backend_execution_environment.js.map +1 -0
  114. package/dist/host/hosted_ephemeral_agent_session.js +286 -0
  115. package/dist/host/hosted_ephemeral_agent_session.js.map +1 -0
  116. package/dist/host/index.js +3 -0
  117. package/dist/host/index.js.map +1 -0
  118. package/dist/host/local_session_host.js +1464 -0
  119. package/dist/host/local_session_host.js.map +1 -0
  120. package/dist/host/session_host.js +2 -0
  121. package/dist/host/session_host.js.map +1 -0
  122. package/dist/host/session_protocol_handler.js +873 -0
  123. package/dist/host/session_protocol_handler.js.map +1 -0
  124. package/dist/main.js +537 -77
  125. package/dist/main.js.map +1 -1
  126. package/dist/protocol/index.d.ts +2 -0
  127. package/dist/protocol/index.js +2 -0
  128. package/dist/protocol/index.js.map +1 -0
  129. package/dist/protocol/session_protocol.d.ts +691 -0
  130. package/dist/protocol/session_protocol.js +2117 -0
  131. package/dist/protocol/session_protocol.js.map +1 -0
  132. package/dist/sdk/client.d.ts +2 -1
  133. package/dist/sdk/client.js +136 -392
  134. package/dist/sdk/client.js.map +1 -1
  135. package/dist/sdk/index.d.ts +7 -4
  136. package/dist/sdk/index.js +4 -2
  137. package/dist/sdk/index.js.map +1 -1
  138. package/dist/sdk/session.d.ts +5 -0
  139. package/dist/sdk/session.js +406 -0
  140. package/dist/sdk/session.js.map +1 -0
  141. package/dist/sdk/types.d.ts +97 -78
  142. package/dist/store/file_session_store.js +157 -0
  143. package/dist/store/file_session_store.js.map +1 -0
  144. package/dist/store/memory_session_store.js +24 -0
  145. package/dist/store/memory_session_store.js.map +1 -0
  146. package/dist/store/session_store.js +32 -0
  147. package/dist/store/session_store.js.map +1 -0
  148. package/dist/transport/errors.d.ts +29 -0
  149. package/dist/{sdk → transport}/errors.js +5 -5
  150. package/dist/transport/errors.js.map +1 -0
  151. package/dist/transport/in_process_session_transport.js +102 -0
  152. package/dist/transport/in_process_session_transport.js.map +1 -0
  153. package/dist/transport/index.d.ts +7 -0
  154. package/dist/transport/index.js +4 -0
  155. package/dist/transport/index.js.map +1 -0
  156. package/dist/transport/pending_session_protocol_requests.js +67 -0
  157. package/dist/transport/pending_session_protocol_requests.js.map +1 -0
  158. package/dist/transport/session_protocol_transport_helpers.js +98 -0
  159. package/dist/transport/session_protocol_transport_helpers.js.map +1 -0
  160. package/dist/transport/session_transport.d.ts +11 -0
  161. package/dist/transport/session_transport.js +2 -0
  162. package/dist/transport/session_transport.js.map +1 -0
  163. package/dist/transport/stdio_session_transport.d.ts +38 -0
  164. package/dist/transport/stdio_session_transport.js +243 -0
  165. package/dist/transport/stdio_session_transport.js.map +1 -0
  166. package/dist/transport/websocket_session_transport.d.ts +52 -0
  167. package/dist/transport/websocket_session_transport.js +235 -0
  168. package/dist/transport/websocket_session_transport.js.map +1 -0
  169. package/dist/tui/chat_controller/assistant_clipboard.js +39 -0
  170. package/dist/tui/chat_controller/assistant_clipboard.js.map +1 -0
  171. package/dist/tui/chat_controller/command_hints.js +43 -0
  172. package/dist/tui/chat_controller/command_hints.js.map +1 -0
  173. package/dist/tui/chat_controller/diff_review_service.js +9 -9
  174. package/dist/tui/chat_controller/diff_review_service.js.map +1 -1
  175. package/dist/tui/chat_controller/diff_review_user_message.js +17 -0
  176. package/dist/tui/chat_controller/diff_review_user_message.js.map +1 -0
  177. package/dist/tui/chat_controller/history_labels.js +8 -0
  178. package/dist/tui/chat_controller/history_labels.js.map +1 -0
  179. package/dist/tui/chat_controller/history_message_model.js +36 -0
  180. package/dist/tui/chat_controller/history_message_model.js.map +1 -0
  181. package/dist/tui/chat_controller/queued_user_messages.js +18 -1
  182. package/dist/tui/chat_controller/queued_user_messages.js.map +1 -1
  183. package/dist/tui/chat_controller/status_format.js +22 -0
  184. package/dist/tui/chat_controller/status_format.js.map +1 -0
  185. package/dist/tui/chat_view.js +3 -1
  186. package/dist/tui/chat_view.js.map +1 -1
  187. package/dist/tui/index.js +1 -1
  188. package/dist/tui/index.js.map +1 -1
  189. package/dist/tui/listen_capture.js +87 -0
  190. package/dist/tui/listen_capture.js.map +1 -0
  191. package/dist/tui/memory_mode.js +23 -0
  192. package/dist/tui/memory_mode.js.map +1 -0
  193. package/dist/tui/session_chat_app.js +227 -0
  194. package/dist/tui/session_chat_app.js.map +1 -0
  195. package/dist/tui/session_chat_controller.js +2167 -0
  196. package/dist/tui/session_chat_controller.js.map +1 -0
  197. package/dist/tui/session_tool_execution_backend.js +170 -0
  198. package/dist/tui/session_tool_execution_backend.js.map +1 -0
  199. package/dist/tui/speech_playback.js +107 -0
  200. package/dist/tui/speech_playback.js.map +1 -0
  201. package/dist/tui/ui/assistant_message.js +27 -21
  202. package/dist/tui/ui/assistant_message.js.map +1 -1
  203. package/dist/tui/ui/components/editor.js +17 -13
  204. package/dist/tui/ui/components/editor.js.map +1 -1
  205. package/dist/tui/ui/custom_editor.js +25 -5
  206. package/dist/tui/ui/custom_editor.js.map +1 -1
  207. package/dist/tui/ui/queued_messages.js +1 -1
  208. package/dist/tui/ui/queued_messages.js.map +1 -1
  209. package/dist/tui/ui/slash_autocomplete.js +14 -36
  210. package/dist/tui/ui/slash_autocomplete.js.map +1 -1
  211. package/dist/tui/ui/tool_ui_registry.js +21 -0
  212. package/dist/tui/ui/tool_ui_registry.js.map +1 -1
  213. package/package.json +14 -11
  214. package/dist/core/auth/credential_resolver.js +0 -36
  215. package/dist/core/auth/credential_resolver.js.map +0 -1
  216. package/dist/core/config/bash_commands.js +0 -44
  217. package/dist/core/config/bash_commands.js.map +0 -1
  218. package/dist/core/diff_review/session.js.map +0 -1
  219. package/dist/core/modes/rpc_adapter.js +0 -17
  220. package/dist/core/modes/rpc_adapter.js.map +0 -1
  221. package/dist/core/modes/rpc_protocol.js +0 -500
  222. package/dist/core/modes/rpc_protocol.js.map +0 -1
  223. package/dist/core/session/checkpoint.js +0 -34
  224. package/dist/core/session/checkpoint.js.map +0 -1
  225. package/dist/sdk/errors.d.ts +0 -33
  226. package/dist/sdk/errors.js.map +0 -1
  227. package/dist/tui/app.js +0 -64
  228. package/dist/tui/app.js.map +0 -1
  229. package/dist/tui/chat_controller/session_maintenance_service.js +0 -634
  230. package/dist/tui/chat_controller/session_maintenance_service.js.map +0 -1
  231. package/dist/tui/chat_controller.js +0 -2532
  232. package/dist/tui/chat_controller.js.map +0 -1
@@ -0,0 +1,1464 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { ChatRuntime } from "../core/runtime/chat_runtime.js";
3
+ import { resolvePersonaSkillsForPromptContext, } from "../core/runtime/runtime_bootstrap.js";
4
+ import { TOOL_NAME_DIFF_REVIEW } from "../core/tools/tool_names.js";
5
+ import { autocompleteProjectPathsWithBackend } from "../core/utils/project_files.js";
6
+ import { applySessionProtocolDelta, createSessionProtocolDeltaMessage, createSessionProtocolEphemeralMessage, } from "../protocol/session_protocol.js";
7
+ import { HostedEphemeralAgentSession } from "./hosted_ephemeral_agent_session.js";
8
+ export class LocalSessionHost {
9
+ sessions = new Set();
10
+ sessionRecoveryPromises = new Map();
11
+ store;
12
+ sessionOptions;
13
+ shutdownPromise;
14
+ shuttingDown = false;
15
+ constructor(options) {
16
+ const { store, ...sessionOptions } = options;
17
+ this.store = store;
18
+ this.sessionOptions = sessionOptions;
19
+ }
20
+ async createSession(input) {
21
+ this.assertHostActive();
22
+ const executionEnvironment = await this.sessionOptions.executionEnvironmentResolver.resolve(input.executionEnvironment);
23
+ if (this.shuttingDown) {
24
+ await executionEnvironment.dispose();
25
+ throw new Error("local session host is shut down");
26
+ }
27
+ let hostedSession;
28
+ try {
29
+ hostedSession = await this.createLocalSessionHandle(executionEnvironment, undefined, input);
30
+ }
31
+ catch (error) {
32
+ if (!this.sessionsHasExecutionEnvironment(executionEnvironment)) {
33
+ await executionEnvironment.dispose();
34
+ }
35
+ throw error;
36
+ }
37
+ if (this.shuttingDown) {
38
+ await hostedSession.dispose();
39
+ this.sessions.delete(hostedSession);
40
+ throw new Error("local session host is shut down");
41
+ }
42
+ return hostedSession;
43
+ }
44
+ createSessionNow(executionEnvironment, initialHistory = []) {
45
+ this.assertHostActive();
46
+ const hostedSession = this.createLocalSessionHandleSync(executionEnvironment);
47
+ for (const message of initialHistory) {
48
+ hostedSession.session.addMessage(message);
49
+ }
50
+ return hostedSession;
51
+ }
52
+ async createRecoveredSession(snapshot, executionEnvironment) {
53
+ const recovered = normalizeRecoveredSnapshot(snapshot);
54
+ const hostedSession = await this.createLocalSessionHandle(executionEnvironment, recovered.snapshot, undefined, recovered.changed);
55
+ hostedSession.session.restoreState({
56
+ sessionId: recovered.snapshot.sessionId,
57
+ historyEntries: recovered.snapshot.messages.flatMap((entry) => entry.modelVisible && isCoreMessage(entry.message)
58
+ ? [{ id: entry.id, message: entry.message }]
59
+ : []),
60
+ });
61
+ return hostedSession;
62
+ }
63
+ async createLocalSessionHandle(executionEnvironment, committedSnapshot, createParams, forceNextSnapshotRevision = false) {
64
+ const bootstrap = await this.resolveNewSessionBootstrap(executionEnvironment);
65
+ if (committedSnapshot) {
66
+ this.applySnapshotSettingsToBootstrap(bootstrap, committedSnapshot);
67
+ }
68
+ else if (createParams) {
69
+ this.applyCreateParamsToBootstrap(bootstrap, createParams);
70
+ }
71
+ const runtimeContext = await executionEnvironment.resolveRuntimeContext({
72
+ persona: bootstrap.persona,
73
+ discoveredSkills: bootstrap.discoveredSkills,
74
+ includeAgentContext: this.sessionOptions.includeAgentContext,
75
+ });
76
+ const catalog = createContentCatalogSnapshot(bootstrap);
77
+ return this.createLocalSessionHandleFromRuntimeContext(executionEnvironment, runtimeContext, bootstrap, catalog, committedSnapshot, forceNextSnapshotRevision);
78
+ }
79
+ createLocalSessionHandleSync(executionEnvironment, committedSnapshot) {
80
+ const bootstrap = committedSnapshot
81
+ ? this.applySnapshotSettingsToBootstrap(this.defaultSessionBootstrap(), committedSnapshot)
82
+ : this.defaultSessionBootstrap();
83
+ const runtimeContext = executionEnvironment.resolveRuntimeContext({
84
+ persona: bootstrap.persona,
85
+ discoveredSkills: bootstrap.discoveredSkills,
86
+ includeAgentContext: this.sessionOptions.includeAgentContext,
87
+ });
88
+ if (isPromiseLike(runtimeContext)) {
89
+ throw new Error("cannot synchronously create a session for an async execution environment");
90
+ }
91
+ return this.createLocalSessionHandleFromRuntimeContext(executionEnvironment, runtimeContext, bootstrap, createContentCatalogSnapshot(bootstrap), committedSnapshot);
92
+ }
93
+ createLocalSessionHandleFromRuntimeContext(executionEnvironment, runtimeContext, bootstrap, catalog, committedSnapshot, forceNextSnapshotRevision = false) {
94
+ const runtime = ChatRuntime.create({
95
+ persona: bootstrap.persona,
96
+ riskLevel: bootstrap.riskLevel,
97
+ toolRegistry: runtimeContext.toolRegistry,
98
+ promptContext: runtimeContext.promptBootstrap.promptContext,
99
+ environment: this.sessionOptions.environment,
100
+ initialPromptComposition: committedSnapshot
101
+ ? promptCompositionFromSnapshot(committedSnapshot)
102
+ : undefined,
103
+ config: bootstrap.config,
104
+ deps: this.sessionOptions.deps,
105
+ });
106
+ const hostedSession = new LocalHostedSessionHandle(runtime, runtimeContext.promptBootstrap, catalog, bootstrap, this.sessionOptions.includeAgentContext, executionEnvironment, this.store, committedSnapshot, forceNextSnapshotRevision, (session) => this.sessions.delete(session));
107
+ this.sessions.add(hostedSession);
108
+ return hostedSession;
109
+ }
110
+ async resolveNewSessionBootstrap(executionEnvironment) {
111
+ const resolver = this.sessionOptions.resolveSessionBootstrap;
112
+ if (!resolver) {
113
+ return this.defaultSessionBootstrap();
114
+ }
115
+ const bootstrap = await resolver({ executionEnvironment });
116
+ return cloneResolvedBootstrap(bootstrap);
117
+ }
118
+ applySnapshotSettingsToBootstrap(bootstrap, snapshot) {
119
+ const requested = snapshot.settings.personaId.toLowerCase();
120
+ const persona = bootstrap.personas.find((candidate) => candidate.id.toLowerCase() === requested) ??
121
+ bootstrap.persona;
122
+ bootstrap.persona = clonePersona(persona);
123
+ if (snapshot.settings.reasoning !== undefined) {
124
+ bootstrap.persona.settings.reasoning = snapshot.settings.reasoning;
125
+ }
126
+ if (snapshot.settings.serviceTier !== undefined) {
127
+ bootstrap.persona.settings.serviceTier = snapshot.settings.serviceTier;
128
+ }
129
+ bootstrap.riskLevel = snapshot.settings.riskLevel;
130
+ return bootstrap;
131
+ }
132
+ applyCreateParamsToBootstrap(bootstrap, createParams) {
133
+ if (createParams.personaId !== undefined) {
134
+ const requested = createParams.personaId.toLowerCase();
135
+ const persona = bootstrap.personas.find((candidate) => candidate.id.toLowerCase() === requested);
136
+ if (!persona) {
137
+ throw new Error(`unknown persona '${createParams.personaId}'`);
138
+ }
139
+ bootstrap.persona = clonePersona(persona);
140
+ }
141
+ if (createParams.reasoning !== undefined) {
142
+ bootstrap.persona.settings.reasoning = createParams.reasoning;
143
+ }
144
+ if (createParams.riskLevel !== undefined) {
145
+ bootstrap.riskLevel = createParams.riskLevel;
146
+ }
147
+ }
148
+ defaultSessionBootstrap() {
149
+ return cloneResolvedBootstrap({
150
+ persona: this.sessionOptions.persona,
151
+ riskLevel: this.sessionOptions.riskLevel,
152
+ discoveredSkills: this.sessionOptions.discoveredSkills,
153
+ personas: this.sessionOptions.personas,
154
+ prompts: this.sessionOptions.prompts,
155
+ config: this.sessionOptions.config,
156
+ });
157
+ }
158
+ async observeSession(sessionId) {
159
+ this.assertHostActive();
160
+ await this.refreshStore();
161
+ const liveSession = this.findLiveSession(sessionId);
162
+ if (liveSession) {
163
+ return liveSession;
164
+ }
165
+ const existingRecovery = this.sessionRecoveryPromises.get(sessionId);
166
+ if (existingRecovery) {
167
+ return await existingRecovery;
168
+ }
169
+ const recovery = this.recoverSession(sessionId);
170
+ this.sessionRecoveryPromises.set(sessionId, recovery);
171
+ try {
172
+ return await recovery;
173
+ }
174
+ finally {
175
+ if (this.sessionRecoveryPromises.get(sessionId) === recovery) {
176
+ this.sessionRecoveryPromises.delete(sessionId);
177
+ }
178
+ }
179
+ }
180
+ async recoverSession(sessionId) {
181
+ if (this.shuttingDown) {
182
+ return undefined;
183
+ }
184
+ const liveSession = this.findLiveSession(sessionId);
185
+ if (liveSession) {
186
+ return liveSession;
187
+ }
188
+ const snapshot = await this.store.loadSession(sessionId);
189
+ if (!snapshot || !this.canRestoreSnapshot(snapshot)) {
190
+ return undefined;
191
+ }
192
+ const executionEnvironment = await this.sessionOptions.executionEnvironmentResolver.restore(snapshot.executionEnvironment);
193
+ if (this.shuttingDown) {
194
+ await executionEnvironment.dispose();
195
+ return undefined;
196
+ }
197
+ const restoredLiveSession = this.findLiveSession(sessionId);
198
+ if (restoredLiveSession) {
199
+ await executionEnvironment.dispose();
200
+ return restoredLiveSession;
201
+ }
202
+ return await this.createRecoveredSession(snapshot, executionEnvironment);
203
+ }
204
+ canRestoreSnapshot(snapshot) {
205
+ return this.sessionOptions.executionEnvironmentResolver.canRestore(snapshot.executionEnvironment);
206
+ }
207
+ findLiveSession(sessionId) {
208
+ for (const session of this.sessions) {
209
+ if (session.sessionId === sessionId) {
210
+ return session;
211
+ }
212
+ }
213
+ return undefined;
214
+ }
215
+ async listSessions() {
216
+ this.assertHostActive();
217
+ await this.refreshStore();
218
+ const snapshots = await this.store.listSessionSnapshots();
219
+ return snapshots
220
+ .filter((snapshot) => this.canRestoreSnapshot(snapshot))
221
+ .map((snapshot) => ({
222
+ sessionId: snapshot.sessionId,
223
+ lifecycle: this.findLiveSession(snapshot.sessionId) ? snapshot.lifecycle : "idle",
224
+ }));
225
+ }
226
+ async shutdown() {
227
+ if (this.shutdownPromise) {
228
+ return await this.shutdownPromise;
229
+ }
230
+ this.shuttingDown = true;
231
+ this.shutdownPromise = this.shutdownNow();
232
+ return await this.shutdownPromise;
233
+ }
234
+ async shutdownNow() {
235
+ const errors = [];
236
+ const recoveryResults = await Promise.allSettled(this.sessionRecoveryPromises.values());
237
+ for (const result of recoveryResults) {
238
+ if (result.status === "rejected") {
239
+ errors.push(result.reason);
240
+ }
241
+ }
242
+ for (const session of this.sessions) {
243
+ try {
244
+ await session.snapshot();
245
+ }
246
+ catch (error) {
247
+ errors.push(error);
248
+ }
249
+ try {
250
+ await session.dispose();
251
+ }
252
+ catch (error) {
253
+ errors.push(error);
254
+ }
255
+ }
256
+ this.sessions.clear();
257
+ if (errors.length > 0) {
258
+ throw new AggregateError(errors, "failed to shut down local session host");
259
+ }
260
+ }
261
+ async refreshStore() {
262
+ for (const session of this.sessions) {
263
+ await session.snapshot();
264
+ }
265
+ }
266
+ assertHostActive() {
267
+ if (this.shuttingDown) {
268
+ throw new Error("local session host is shut down");
269
+ }
270
+ }
271
+ sessionsHasExecutionEnvironment(executionEnvironment) {
272
+ for (const session of this.sessions) {
273
+ if (session.hasExecutionEnvironment(executionEnvironment)) {
274
+ return true;
275
+ }
276
+ }
277
+ return false;
278
+ }
279
+ }
280
+ function isPromiseLike(value) {
281
+ return typeof value.then === "function";
282
+ }
283
+ class LocalHostedSessionHandle {
284
+ runtime;
285
+ promptBootstrap;
286
+ catalog;
287
+ bootstrap;
288
+ includeAgentContext;
289
+ executionEnvironment;
290
+ store;
291
+ removeFromHost;
292
+ session;
293
+ committedSessionId;
294
+ committedSnapshot;
295
+ persistedSnapshotRevision;
296
+ draftAssistantMessage;
297
+ messageStates = new Map();
298
+ restoredMessageIds;
299
+ restoredTimelineMessageIds;
300
+ timelineExtras = [];
301
+ tools = new Map();
302
+ agents = new Map();
303
+ facets = new Map();
304
+ deltaListeners = new Set();
305
+ ephemeralListeners = new Set();
306
+ ephemeralAgentSessions = new Map();
307
+ unsubscribeSubagentEvent;
308
+ runtimeEventQueue = Promise.resolve();
309
+ snapshotQueue = Promise.resolve();
310
+ forceNextSnapshotRevision;
311
+ disposed = false;
312
+ constructor(runtime, promptBootstrap, catalog, bootstrap, includeAgentContext, executionEnvironment, store, committedSnapshot, forceNextSnapshotRevision = false, removeFromHost = () => { }) {
313
+ this.runtime = runtime;
314
+ this.promptBootstrap = promptBootstrap;
315
+ this.catalog = catalog;
316
+ this.bootstrap = bootstrap;
317
+ this.includeAgentContext = includeAgentContext;
318
+ this.executionEnvironment = executionEnvironment;
319
+ this.store = store;
320
+ this.removeFromHost = removeFromHost;
321
+ this.session = runtime.session;
322
+ this.committedSessionId = committedSnapshot?.sessionId ?? this.session.sessionId;
323
+ this.committedSnapshot = committedSnapshot
324
+ ? cloneSessionProtocolSnapshot(committedSnapshot)
325
+ : undefined;
326
+ this.persistedSnapshotRevision = committedSnapshot?.revision;
327
+ this.forceNextSnapshotRevision = forceNextSnapshotRevision;
328
+ this.restoreProtocolState(committedSnapshot);
329
+ this.unsubscribeSubagentEvent = this.session.onSubagentEvent((event) => {
330
+ void this.enqueueRuntimeEvent(event).catch(() => undefined);
331
+ });
332
+ }
333
+ get isTurnRunning() {
334
+ return this.runtime.isTurnRunning;
335
+ }
336
+ get sessionId() {
337
+ return this.session.sessionId;
338
+ }
339
+ get isDisposed() {
340
+ return this.disposed;
341
+ }
342
+ hasExecutionEnvironment(executionEnvironment) {
343
+ return this.executionEnvironment === executionEnvironment;
344
+ }
345
+ onDelta(handler) {
346
+ this.deltaListeners.add(handler);
347
+ return () => {
348
+ this.deltaListeners.delete(handler);
349
+ };
350
+ }
351
+ onEphemeral(handler) {
352
+ this.ephemeralListeners.add(handler);
353
+ return () => {
354
+ this.ephemeralListeners.delete(handler);
355
+ };
356
+ }
357
+ async record(options) {
358
+ this.assertActive();
359
+ const userHistoryEntryId = this.session.addUserText(options.text, options.historyEntryId ? { historyEntryId: options.historyEntryId } : undefined);
360
+ const snapshot = await this.commitSnapshot();
361
+ this.emitSnapshotReset("user-message", snapshot);
362
+ return {
363
+ snapshot,
364
+ userHistoryEntryId,
365
+ };
366
+ }
367
+ async runTurn() {
368
+ this.assertActive();
369
+ try {
370
+ const result = await this.runtime.runTurn({
371
+ onEvent: (event) => this.recordTurnRuntimeEvent(event),
372
+ });
373
+ if (result.aborted && this.draftAssistantMessage) {
374
+ await this.interruptDraftAssistantMessage();
375
+ }
376
+ await this.emitSnapshotResetIfChanged("assistant-message");
377
+ return result;
378
+ }
379
+ catch (error) {
380
+ try {
381
+ await this.cleanupFailedTurn();
382
+ }
383
+ catch {
384
+ // Preserve the original turn failure for the protocol response.
385
+ }
386
+ throw error;
387
+ }
388
+ }
389
+ interruptTurn() {
390
+ this.assertActive();
391
+ return this.runtime.interruptTurn();
392
+ }
393
+ requestTurnBoundaryStop() {
394
+ this.assertActive();
395
+ return this.runtime.requestTurnBoundaryStop();
396
+ }
397
+ async exec(options) {
398
+ this.assertActive();
399
+ const backend = this.executionEnvironment.getToolExecutionBackend();
400
+ return await backend.runBash(options.command, {
401
+ ...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
402
+ ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
403
+ ...(options.signal !== undefined ? { signal: options.signal } : {}),
404
+ });
405
+ }
406
+ async setRiskLevel(level) {
407
+ this.assertActive();
408
+ this.runtime.setRiskLevel(level);
409
+ const snapshot = await this.commitSnapshot();
410
+ this.emitSnapshotReset("configuration", snapshot);
411
+ return snapshot;
412
+ }
413
+ async setReasoning(reasoning) {
414
+ this.assertActive();
415
+ const persona = clonePersona(this.runtime.persona);
416
+ persona.settings.reasoning = reasoning;
417
+ const skillsContext = resolvePersonaSkillsForPromptContext({
418
+ persona,
419
+ discoveredSkills: this.bootstrap.discoveredSkills,
420
+ });
421
+ this.runtime.setPersona(persona, {
422
+ skillsBlock: skillsContext.skillsBlock,
423
+ });
424
+ this.bootstrap.persona = clonePersona(persona);
425
+ const snapshot = await this.commitSnapshot();
426
+ this.emitSnapshotReset("configuration", snapshot);
427
+ return snapshot;
428
+ }
429
+ async setPersona(personaId) {
430
+ this.assertActive();
431
+ const persona = this.bootstrap.personas.find((persona) => persona.id.toLowerCase() === personaId.toLowerCase());
432
+ if (!persona) {
433
+ throw new Error(`unknown persona '${personaId}'`);
434
+ }
435
+ const selectedPersona = clonePersona(persona);
436
+ const skillsContext = resolvePersonaSkillsForPromptContext({
437
+ persona: selectedPersona,
438
+ discoveredSkills: this.bootstrap.discoveredSkills,
439
+ });
440
+ this.runtime.setPersona(selectedPersona, {
441
+ skillsBlock: skillsContext.skillsBlock,
442
+ });
443
+ this.bootstrap.persona = clonePersona(selectedPersona);
444
+ const snapshot = await this.commitSnapshot();
445
+ this.emitSnapshotReset("configuration", snapshot);
446
+ return snapshot;
447
+ }
448
+ async reload() {
449
+ this.assertActive();
450
+ const runtimeConfig = await this.executionEnvironment.resolveRuntimeConfig();
451
+ if (runtimeConfig.personas.length === 0) {
452
+ throw new Error("reload failed: no personas available");
453
+ }
454
+ const personas = runtimeConfig.personas.map((persona) => this.normalizeReloadedPersona(persona));
455
+ const currentPersonaId = this.runtime.persona.id.toLowerCase();
456
+ const nextPersona = personas.find((persona) => persona.id.toLowerCase() === currentPersonaId) ?? personas[0];
457
+ const runtimeContext = await this.executionEnvironment.resolveRuntimeContext({
458
+ persona: nextPersona,
459
+ discoveredSkills: runtimeConfig.skills,
460
+ includeAgentContext: this.includeAgentContext,
461
+ });
462
+ this.runtime.setConfig(runtimeConfig.config);
463
+ this.runtime.updatePromptContext(runtimeContext.promptBootstrap.promptContext);
464
+ this.runtime.setPersona(nextPersona, {
465
+ skillsBlock: runtimeContext.promptBootstrap.promptContext.skillsBlock,
466
+ });
467
+ this.bootstrap = {
468
+ persona: clonePersona(nextPersona),
469
+ riskLevel: this.runtime.currentRiskLevel,
470
+ discoveredSkills: structuredClone(runtimeConfig.skills),
471
+ personas: personas.map(clonePersona),
472
+ prompts: structuredClone(runtimeConfig.prompts),
473
+ config: runtimeConfig.config,
474
+ };
475
+ this.catalog = createContentCatalogSnapshot(this.bootstrap);
476
+ const unknownSkillWarnings = runtimeContext.promptBootstrap.unknownSkills.map((skill) => `unknown skill enabled by persona '${nextPersona.id}': ${skill}`);
477
+ const snapshot = await this.commitSnapshot();
478
+ this.emitSnapshotReset("configuration", snapshot);
479
+ return {
480
+ snapshot,
481
+ warnings: [
482
+ ...runtimeConfig.warnings,
483
+ ...runtimeContext.promptBootstrap.warnings,
484
+ ...unknownSkillWarnings,
485
+ ],
486
+ counts: {
487
+ personas: runtimeConfig.personas.length,
488
+ prompts: runtimeConfig.prompts.length,
489
+ skills: runtimeConfig.skills.length,
490
+ },
491
+ };
492
+ }
493
+ normalizeReloadedPersona(persona) {
494
+ const existing = this.catalog.personas.find((candidate) => candidate.id.toLowerCase() === persona.id.toLowerCase());
495
+ if (existing?.tools?.includes(TOOL_NAME_DIFF_REVIEW) !== false) {
496
+ return clonePersona(persona);
497
+ }
498
+ const clone = clonePersona(persona);
499
+ if (clone.tools) {
500
+ clone.tools = clone.tools.filter((tool) => tool !== TOOL_NAME_DIFF_REVIEW);
501
+ }
502
+ return clone;
503
+ }
504
+ async resolvePrompt(promptId) {
505
+ this.assertActive();
506
+ const runtimeConfig = await this.executionEnvironment.resolveRuntimeConfig();
507
+ const prompt = runtimeConfig.prompts.find((candidate) => candidate.id.toLowerCase() === promptId.toLowerCase());
508
+ if (!prompt) {
509
+ throw new Error(`unknown prompt '${promptId}'`);
510
+ }
511
+ return { promptId: prompt.id, text: prompt.template };
512
+ }
513
+ async autocompletePaths(options) {
514
+ this.assertActive();
515
+ return {
516
+ paths: await autocompleteProjectPathsWithBackend(this.executionEnvironment.getToolExecutionBackend(), options),
517
+ };
518
+ }
519
+ async compact(options) {
520
+ this.assertActive();
521
+ const result = await this.session.compact({
522
+ mode: options.mode === "summary-only" ? "only-summary" : "with-last-assistant",
523
+ ...(options.guidance !== undefined ? { guidance: options.guidance } : {}),
524
+ });
525
+ const snapshot = await this.commitSnapshot();
526
+ this.emitSnapshotReset("maintenance", snapshot);
527
+ return {
528
+ snapshot,
529
+ compactionMessage: result.compactionMessage,
530
+ includedLastAssistant: result.includedLastAssistant,
531
+ };
532
+ }
533
+ async pruneToolResults(options) {
534
+ this.assertActive();
535
+ const result = await this.session.pruneToolResults({
536
+ strategy: options.strategy,
537
+ fraction: options.fraction,
538
+ ...(options.guidance !== undefined ? { guidance: options.guidance } : {}),
539
+ });
540
+ const snapshot = await this.commitSnapshot();
541
+ this.emitSnapshotReset("maintenance", snapshot);
542
+ return {
543
+ snapshot,
544
+ message: result.message,
545
+ noop: result.noop,
546
+ bashResultsPruned: result.bashResultsPruned,
547
+ editCallsPruned: result.editCallsPruned,
548
+ editResultsPruned: result.editResultsPruned,
549
+ bytesPruned: result.bytesPruned,
550
+ };
551
+ }
552
+ async rewindToHistoryEntryId(historyEntryId) {
553
+ this.assertActive();
554
+ const result = this.session.rewindToHistoryEntryId(historyEntryId);
555
+ if (!result) {
556
+ throw new Error("rewind failed");
557
+ }
558
+ const snapshot = await this.commitSnapshot();
559
+ this.emitSnapshotReset("maintenance", snapshot);
560
+ return {
561
+ snapshot,
562
+ ...result,
563
+ };
564
+ }
565
+ async terminateSubagent(subagentId) {
566
+ this.assertActive();
567
+ return { found: await this.session.terminateSubagent(subagentId) };
568
+ }
569
+ async createEphemeralContext(options) {
570
+ this.assertActive();
571
+ const contextId = `ephemeral-${randomUUID()}`;
572
+ const session = new HostedEphemeralAgentSession({
573
+ contextId,
574
+ persona: this.runtime.persona,
575
+ config: this.bootstrap.config ?? {},
576
+ discoveredSkills: this.bootstrap.discoveredSkills,
577
+ includeAgentContext: this.includeAgentContext,
578
+ executionEnvironment: this.executionEnvironment,
579
+ instructions: options.instructions,
580
+ tools: options.tools,
581
+ riskLevel: options.riskLevel,
582
+ emitUpdate: (threadId, update) => {
583
+ this.emitEphemeral(createSessionProtocolEphemeralMessage({
584
+ sessionId: this.session.sessionId,
585
+ event: {
586
+ type: "ephemeral-agent.thread-update",
587
+ contextId,
588
+ threadId,
589
+ update,
590
+ },
591
+ }));
592
+ },
593
+ });
594
+ this.ephemeralAgentSessions.set(contextId, session);
595
+ return { contextId };
596
+ }
597
+ async submitEphemeralThread(options) {
598
+ this.assertActive();
599
+ const session = this.ephemeralAgentSessions.get(options.contextId);
600
+ if (!session) {
601
+ throw new Error(`unknown ephemeral context '${options.contextId}'`);
602
+ }
603
+ return await session.submitThreadMessage(options);
604
+ }
605
+ async closeEphemeralContext(contextId) {
606
+ const session = this.ephemeralAgentSessions.get(contextId);
607
+ if (!session) {
608
+ return { closed: false };
609
+ }
610
+ this.ephemeralAgentSessions.delete(contextId);
611
+ session.dispose();
612
+ return { closed: true };
613
+ }
614
+ async snapshot() {
615
+ this.assertActive();
616
+ return await this.commitSnapshot();
617
+ }
618
+ async dispose() {
619
+ if (this.disposed) {
620
+ return;
621
+ }
622
+ this.disposed = true;
623
+ try {
624
+ this.unsubscribeSubagentEvent();
625
+ for (const session of this.ephemeralAgentSessions.values()) {
626
+ session.dispose();
627
+ }
628
+ this.ephemeralAgentSessions.clear();
629
+ this.session.dispose();
630
+ await this.executionEnvironment.dispose();
631
+ }
632
+ finally {
633
+ this.removeFromHost(this);
634
+ }
635
+ }
636
+ async commitSnapshot() {
637
+ const write = this.snapshotQueue.catch(() => undefined).then(() => this.writeSnapshot());
638
+ this.snapshotQueue = write.catch(() => undefined);
639
+ return await write;
640
+ }
641
+ async commitSnapshotWithRevision(revision, options = {}) {
642
+ const write = this.snapshotQueue
643
+ .catch(() => undefined)
644
+ .then(() => this.writeSnapshotWithRevision(revision, options));
645
+ this.snapshotQueue = write.catch(() => undefined);
646
+ return await write;
647
+ }
648
+ async writeSnapshot() {
649
+ this.assertActive();
650
+ const draft = this.buildSnapshotDraft();
651
+ if (this.committedSessionId !== draft.sessionId) {
652
+ await this.store.deleteSession(this.committedSessionId, {
653
+ ...(this.persistedSnapshotRevision !== undefined
654
+ ? { expectedRevision: this.persistedSnapshotRevision }
655
+ : {}),
656
+ });
657
+ this.committedSessionId = draft.sessionId;
658
+ this.committedSnapshot = undefined;
659
+ this.persistedSnapshotRevision = undefined;
660
+ }
661
+ const snapshot = {
662
+ ...draft,
663
+ revision: this.nextSnapshotRevision(draft),
664
+ };
665
+ await this.store.commitSessionSnapshot(snapshot, {
666
+ expectedRevision: this.persistedSnapshotRevision ?? 0,
667
+ });
668
+ this.persistedSnapshotRevision = snapshot.revision;
669
+ return cloneSessionProtocolSnapshot(this.updateCommittedSnapshotAfterWrite(snapshot));
670
+ }
671
+ async writeSnapshotWithRevision(revision, options = {}) {
672
+ this.assertActive();
673
+ const draft = this.buildSnapshotDraft();
674
+ const persist = options.persist ?? true;
675
+ if (this.committedSessionId !== draft.sessionId) {
676
+ await this.store.deleteSession(this.committedSessionId, {
677
+ ...(this.persistedSnapshotRevision !== undefined
678
+ ? { expectedRevision: this.persistedSnapshotRevision }
679
+ : {}),
680
+ });
681
+ this.committedSessionId = draft.sessionId;
682
+ this.committedSnapshot = undefined;
683
+ this.persistedSnapshotRevision = undefined;
684
+ }
685
+ const snapshot = {
686
+ ...draft,
687
+ revision,
688
+ };
689
+ if (persist) {
690
+ await this.store.commitSessionSnapshot(snapshot, {
691
+ expectedRevision: this.persistedSnapshotRevision ?? 0,
692
+ });
693
+ this.persistedSnapshotRevision = snapshot.revision;
694
+ }
695
+ return cloneSessionProtocolSnapshot(this.updateCommittedSnapshotAfterWrite(snapshot));
696
+ }
697
+ updateCommittedSnapshotAfterWrite(snapshot) {
698
+ if (!this.committedSnapshot || this.committedSnapshot.revision <= snapshot.revision) {
699
+ this.committedSnapshot = cloneSessionProtocolSnapshot(snapshot);
700
+ }
701
+ return this.committedSnapshot;
702
+ }
703
+ buildSnapshotDraft() {
704
+ const messages = this.buildProtocolMessages();
705
+ return {
706
+ sessionId: this.session.sessionId,
707
+ lifecycle: this.runtime.isTurnRunning ? "running" : "idle",
708
+ settings: {
709
+ personaId: this.runtime.persona.id,
710
+ riskLevel: this.runtime.currentRiskLevel,
711
+ ...(this.runtime.persona.settings.reasoning !== undefined
712
+ ? { reasoning: this.runtime.persona.settings.reasoning }
713
+ : {}),
714
+ ...(this.runtime.persona.settings.serviceTier !== undefined
715
+ ? { serviceTier: this.runtime.persona.settings.serviceTier }
716
+ : {}),
717
+ },
718
+ bootstrap: {
719
+ model: modelSnapshotFromModel(this.runtime.persona.model),
720
+ prompt: {
721
+ environmentTag: this.runtime.promptComposition.environmentTag,
722
+ subagentPrompts: cloneSubagentPrompts(this.runtime.promptComposition.subagentPrompts),
723
+ },
724
+ },
725
+ catalog: structuredClone(this.catalog),
726
+ executionEnvironment: this.executionEnvironment.snapshot(),
727
+ messages,
728
+ timeline: this.buildTimeline(messages),
729
+ tools: Object.fromEntries(this.tools),
730
+ agents: Object.fromEntries(this.agents),
731
+ facets: Object.fromEntries(this.facets),
732
+ };
733
+ }
734
+ buildProtocolMessages() {
735
+ const systemMessage = {
736
+ id: "system",
737
+ state: "committed",
738
+ modelVisible: true,
739
+ message: {
740
+ role: "system",
741
+ content: this.runtime.promptComposition.baseSystemPrompt,
742
+ timestamp: 0,
743
+ },
744
+ };
745
+ const historyMessages = this.session.historyEntries.map((entry) => ({
746
+ id: entry.id,
747
+ state: this.messageStates.get(entry.id) ?? "committed",
748
+ modelVisible: true,
749
+ message: entry.message,
750
+ }));
751
+ return [
752
+ systemMessage,
753
+ ...historyMessages,
754
+ ...(this.draftAssistantMessage ? [structuredClone(this.draftAssistantMessage)] : []),
755
+ ];
756
+ }
757
+ buildTimeline(messages) {
758
+ const messageItems = messages
759
+ .filter((message) => this.shouldIncludeMessageInTimeline(message.id))
760
+ .map((message) => ({
761
+ type: "message",
762
+ id: `timeline-${message.id}`,
763
+ messageId: message.id,
764
+ }));
765
+ return [...messageItems, ...structuredClone(this.timelineExtras)];
766
+ }
767
+ shouldIncludeMessageInTimeline(messageId) {
768
+ if (messageId === "system") {
769
+ return false;
770
+ }
771
+ if (!this.restoredTimelineMessageIds || !this.restoredMessageIds) {
772
+ return true;
773
+ }
774
+ return (this.restoredTimelineMessageIds.has(messageId) || !this.restoredMessageIds.has(messageId));
775
+ }
776
+ restoreProtocolState(snapshot) {
777
+ if (!snapshot) {
778
+ return;
779
+ }
780
+ this.restoredMessageIds = new Set(snapshot.messages.map((message) => message.id));
781
+ this.restoredTimelineMessageIds = new Set(snapshot.timeline.filter((item) => item.type === "message").map((item) => item.messageId));
782
+ this.timelineExtras.splice(0, this.timelineExtras.length, ...snapshot.timeline
783
+ .filter((item) => item.type !== "message")
784
+ .map((item) => structuredClone(item)));
785
+ this.tools.clear();
786
+ for (const [id, tool] of Object.entries(snapshot.tools)) {
787
+ this.tools.set(id, structuredClone(tool));
788
+ }
789
+ this.agents.clear();
790
+ for (const [id, agent] of Object.entries(snapshot.agents)) {
791
+ this.agents.set(id, structuredClone(agent));
792
+ }
793
+ this.facets.clear();
794
+ for (const [id, facet] of Object.entries(snapshot.facets)) {
795
+ this.facets.set(id, structuredClone(facet));
796
+ }
797
+ this.draftAssistantMessage = snapshot.messages.find((message) => message.state === "draft");
798
+ this.messageStates.clear();
799
+ for (const message of snapshot.messages) {
800
+ if (message.state !== "committed" && message.state !== "draft") {
801
+ this.messageStates.set(message.id, message.state);
802
+ }
803
+ }
804
+ }
805
+ assertActive() {
806
+ if (this.disposed) {
807
+ throw new Error(`session is shut down: ${this.committedSessionId}`);
808
+ }
809
+ }
810
+ nextSnapshotRevision(next) {
811
+ if (!this.committedSnapshot) {
812
+ return 1;
813
+ }
814
+ if (this.forceNextSnapshotRevision) {
815
+ this.forceNextSnapshotRevision = false;
816
+ return this.committedSnapshot.revision + 1;
817
+ }
818
+ const { revision, ...current } = this.committedSnapshot;
819
+ return JSON.stringify(current) === JSON.stringify(next) ? revision : revision + 1;
820
+ }
821
+ enqueueRuntimeEvent(event) {
822
+ const write = this.runtimeEventQueue
823
+ .catch(() => undefined)
824
+ .then(() => this.recordRuntimeEvent(event));
825
+ this.runtimeEventQueue = write.catch(() => undefined);
826
+ return write;
827
+ }
828
+ async recordRuntimeEvent(event) {
829
+ switch (event.type) {
830
+ case "assistant_start": {
831
+ this.draftAssistantMessage = {
832
+ id: event.historyEntryId,
833
+ state: "draft",
834
+ modelVisible: false,
835
+ message: { role: "assistant", content: [], timestamp: Date.now() },
836
+ };
837
+ const timelineItem = timelineItemForMessage(this.draftAssistantMessage.id);
838
+ await this.emitPatch("assistant-stream", [
839
+ {
840
+ type: "message.append",
841
+ message: structuredClone(this.draftAssistantMessage),
842
+ timelineItem,
843
+ },
844
+ { type: "lifecycle.set", lifecycle: "running" },
845
+ ], { persist: false });
846
+ return;
847
+ }
848
+ case "assistant_partial": {
849
+ const previousDraft = this.draftAssistantMessage;
850
+ const nextDraft = {
851
+ id: event.historyEntryId,
852
+ state: "draft",
853
+ modelVisible: false,
854
+ message: {
855
+ role: "assistant",
856
+ content: [
857
+ ...(event.snapshot.hasAnyThinking
858
+ ? [
859
+ {
860
+ type: "thinking",
861
+ thinking: event.snapshot.thinking,
862
+ },
863
+ ]
864
+ : []),
865
+ ...(event.snapshot.hasTextStarted
866
+ ? [{ type: "text", text: event.snapshot.text }]
867
+ : []),
868
+ ],
869
+ timestamp: Date.now(),
870
+ },
871
+ };
872
+ const changes = this.buildAssistantPartialChanges(previousDraft, nextDraft);
873
+ if (changes.length === 0) {
874
+ return;
875
+ }
876
+ this.draftAssistantMessage = nextDraft;
877
+ await this.emitPatch("assistant-stream", changes, {
878
+ persist: false,
879
+ });
880
+ return;
881
+ }
882
+ case "assistant_final": {
883
+ this.draftAssistantMessage = undefined;
884
+ this.messageStates.delete(event.historyEntryId);
885
+ this.seedToolRunsFromAssistantMessage(event.historyEntryId, event.message);
886
+ await this.emitPatch("assistant-message", [
887
+ {
888
+ type: "message.replace",
889
+ message: {
890
+ id: event.historyEntryId,
891
+ state: "committed",
892
+ modelVisible: true,
893
+ message: event.message,
894
+ },
895
+ },
896
+ ...this.toolChangesForAssistantMessage(event.historyEntryId, event.message),
897
+ ]);
898
+ return;
899
+ }
900
+ case "tool_result": {
901
+ const existing = this.tools.get(event.message.toolCallId);
902
+ if (existing) {
903
+ const nextTool = {
904
+ ...existing,
905
+ status: event.message.isError ? "failed" : "succeeded",
906
+ finishedAt: event.message.timestamp,
907
+ resultMessageId: event.historyEntryId,
908
+ };
909
+ this.tools.set(nextTool.id, nextTool);
910
+ await this.emitPatch("tool-result", [
911
+ {
912
+ type: "message.append",
913
+ message: {
914
+ id: event.historyEntryId,
915
+ state: "committed",
916
+ modelVisible: true,
917
+ message: event.message,
918
+ },
919
+ timelineItem: timelineItemForMessage(event.historyEntryId),
920
+ },
921
+ { type: "tool.set", tool: structuredClone(nextTool) },
922
+ ]);
923
+ return;
924
+ }
925
+ await this.emitPatch("tool-result", [
926
+ {
927
+ type: "message.append",
928
+ message: {
929
+ id: event.historyEntryId,
930
+ state: "committed",
931
+ modelVisible: true,
932
+ message: event.message,
933
+ },
934
+ timelineItem: timelineItemForMessage(event.historyEntryId),
935
+ },
936
+ ]);
937
+ return;
938
+ }
939
+ case "notice": {
940
+ const item = {
941
+ type: "notice",
942
+ id: `notice-${Date.now()}-${this.timelineExtras.length}`,
943
+ notice: {
944
+ severity: event.severity,
945
+ text: event.text,
946
+ timestamp: Date.now(),
947
+ },
948
+ };
949
+ this.timelineExtras.push(item);
950
+ await this.emitPatch("notice", [{ type: "timeline.append", item: structuredClone(item) }]);
951
+ return;
952
+ }
953
+ case "compaction_start": {
954
+ const item = {
955
+ type: "operation",
956
+ id: `operation-auto-compaction-${Date.now()}`,
957
+ operation: {
958
+ kind: "auto-compaction",
959
+ status: "running",
960
+ startedAt: Date.now(),
961
+ },
962
+ };
963
+ this.timelineExtras.push(item);
964
+ await this.emitPatch("maintenance", [
965
+ { type: "timeline.append", item: structuredClone(item) },
966
+ ]);
967
+ return;
968
+ }
969
+ case "compaction_end":
970
+ await this.emitSnapshotReset("maintenance", await this.commitSnapshot());
971
+ return;
972
+ case "tool_ui":
973
+ await this.recordToolUiEvent(event.uiEvent);
974
+ return;
975
+ case "subagent_ui":
976
+ await this.recordSubagentUiEvent(event.event, event.originHistoryEntryId);
977
+ return;
978
+ }
979
+ }
980
+ async recordTurnRuntimeEvent(event) {
981
+ await this.enqueueRuntimeEvent(event);
982
+ }
983
+ async recordToolUiEvent(event) {
984
+ const toolCallId = event.toolCallId;
985
+ const facetId = `tool-ui-${toolCallId}`;
986
+ const existing = this.facets.get(facetId);
987
+ const events = Array.isArray(existing?.data.events) ? existing.data.events : [];
988
+ const facet = {
989
+ id: facetId,
990
+ subject: { type: "tool", id: toolCallId },
991
+ kind: "tau.tool-ui-events",
992
+ version: 1,
993
+ data: { events: [...events, structuredClone(event)] },
994
+ };
995
+ this.facets.set(facet.id, facet);
996
+ const existingTool = this.tools.get(toolCallId);
997
+ const toolChange = existingTool
998
+ ? {
999
+ type: "tool.set",
1000
+ tool: this.updateToolRunFromUiEvent(existingTool, event),
1001
+ }
1002
+ : undefined;
1003
+ if (toolChange) {
1004
+ this.tools.set(toolChange.tool.id, toolChange.tool);
1005
+ }
1006
+ await this.emitPatch("tool-run", [
1007
+ ...(toolChange ? [toolChange] : []),
1008
+ { type: "facet.set", facet: structuredClone(facet) },
1009
+ ]);
1010
+ }
1011
+ updateToolRunFromUiEvent(tool, event) {
1012
+ const next = {
1013
+ ...tool,
1014
+ facetIds: tool.facetIds.includes(`tool-ui-${tool.toolCallId}`)
1015
+ ? tool.facetIds
1016
+ : [...tool.facetIds, `tool-ui-${tool.toolCallId}`],
1017
+ };
1018
+ if (event.type.endsWith("_started") || event.type === "tool_call_queued") {
1019
+ next.status = event.type === "tool_call_queued" ? "queued" : "running";
1020
+ next.startedAt ??= Date.now();
1021
+ }
1022
+ else if (event.type.endsWith("_blocked")) {
1023
+ next.status = "blocked";
1024
+ next.finishedAt = Date.now();
1025
+ }
1026
+ else if (event.type.endsWith("_finished")) {
1027
+ next.status = "status" in event && event.status === "error" ? "failed" : "succeeded";
1028
+ next.finishedAt = Date.now();
1029
+ }
1030
+ else if (event.type === "bash_aborted") {
1031
+ next.status = "cancelled";
1032
+ next.finishedAt = Date.now();
1033
+ }
1034
+ return next;
1035
+ }
1036
+ async recordSubagentUiEvent(event, originHistoryEntryId) {
1037
+ const existing = "id" in event ? this.agents.get(event.id) : this.agents.get(event.state.id);
1038
+ const agent = agentRunFromSubagentEvent(event, originHistoryEntryId, existing);
1039
+ if (!agent) {
1040
+ return;
1041
+ }
1042
+ this.agents.set(agent.id, agent);
1043
+ await this.emitPatch("agent-run", [{ type: "agent.set", agent: structuredClone(agent) }]);
1044
+ }
1045
+ buildAssistantPartialChanges(previousDraft, nextDraft) {
1046
+ if (!previousDraft) {
1047
+ return [
1048
+ {
1049
+ type: "message.append",
1050
+ message: structuredClone(nextDraft),
1051
+ timelineItem: timelineItemForMessage(nextDraft.id),
1052
+ },
1053
+ ];
1054
+ }
1055
+ const appendChange = buildAssistantContentAppendChange(previousDraft, nextDraft);
1056
+ if (appendChange) {
1057
+ return [appendChange];
1058
+ }
1059
+ if (assistantDraftContentEquals(previousDraft, nextDraft)) {
1060
+ return [];
1061
+ }
1062
+ return [{ type: "message.replace", message: structuredClone(nextDraft) }];
1063
+ }
1064
+ async emitPatch(reason, changes, options = {}) {
1065
+ if (changes.length === 0) {
1066
+ return;
1067
+ }
1068
+ if (!this.committedSnapshot) {
1069
+ const snapshot = await this.commitSnapshot();
1070
+ this.emitSnapshotReset(reason, snapshot);
1071
+ return;
1072
+ }
1073
+ const fromRevision = this.committedSnapshot.revision;
1074
+ const toRevision = fromRevision + 1;
1075
+ const delta = createSessionProtocolDeltaMessage({
1076
+ sessionId: this.committedSnapshot.sessionId,
1077
+ fromRevision,
1078
+ toRevision,
1079
+ reason,
1080
+ delta: { type: "snapshot.patch", changes },
1081
+ });
1082
+ if (options.persist === false) {
1083
+ this.committedSnapshot = applySessionProtocolDelta(this.committedSnapshot, delta);
1084
+ this.emitDelta(delta);
1085
+ return;
1086
+ }
1087
+ const snapshot = await this.commitSnapshotWithRevision(toRevision, options);
1088
+ this.emitDelta(createSessionProtocolDeltaMessage({
1089
+ sessionId: delta.sessionId,
1090
+ fromRevision,
1091
+ toRevision: snapshot.revision,
1092
+ reason,
1093
+ delta: { type: "snapshot.patch", changes },
1094
+ }));
1095
+ }
1096
+ emitSnapshotReset(reason, snapshot) {
1097
+ this.emitDelta(createSessionProtocolDeltaMessage({
1098
+ sessionId: snapshot.sessionId,
1099
+ fromRevision: null,
1100
+ toRevision: snapshot.revision,
1101
+ reason,
1102
+ delta: { type: "snapshot.reset", snapshot },
1103
+ }));
1104
+ }
1105
+ async emitSnapshotResetIfChanged(reason) {
1106
+ const previousRevision = this.committedSnapshot?.revision;
1107
+ const snapshot = await this.commitSnapshot();
1108
+ if (snapshot.revision !== previousRevision) {
1109
+ this.emitSnapshotReset(reason, snapshot);
1110
+ }
1111
+ }
1112
+ emitDelta(delta) {
1113
+ for (const listener of [...this.deltaListeners]) {
1114
+ try {
1115
+ listener(delta);
1116
+ }
1117
+ catch {
1118
+ // Delta observers must not be able to fail the hosted session turn.
1119
+ }
1120
+ }
1121
+ }
1122
+ emitEphemeral(message) {
1123
+ for (const listener of [...this.ephemeralListeners]) {
1124
+ try {
1125
+ listener(message);
1126
+ }
1127
+ catch {
1128
+ // Ephemeral observers must not be able to fail hosted session work.
1129
+ }
1130
+ }
1131
+ }
1132
+ seedToolRunsFromAssistantMessage(messageId, message) {
1133
+ const toolCalls = message.content
1134
+ .map((content, index) => ({ content, index }))
1135
+ .filter((item) => item.content.type === "toolCall");
1136
+ for (const { content, index } of toolCalls) {
1137
+ if (this.tools.has(content.id)) {
1138
+ continue;
1139
+ }
1140
+ this.tools.set(content.id, {
1141
+ id: content.id,
1142
+ toolCallId: content.id,
1143
+ toolName: content.name,
1144
+ call: {
1145
+ messageId,
1146
+ contentIndex: index,
1147
+ },
1148
+ status: "queued",
1149
+ facetIds: [],
1150
+ });
1151
+ }
1152
+ }
1153
+ toolChangesForAssistantMessage(_messageId, message) {
1154
+ return message.content
1155
+ .map((content, index) => ({ content, index }))
1156
+ .filter((item) => item.content.type === "toolCall")
1157
+ .map(({ content }) => {
1158
+ const tool = this.tools.get(content.id);
1159
+ if (!tool) {
1160
+ throw new Error(`missing protocol tool run for tool call '${content.id}'`);
1161
+ }
1162
+ return { type: "tool.set", tool: structuredClone(tool) };
1163
+ });
1164
+ }
1165
+ async interruptDraftAssistantMessage() {
1166
+ const draft = this.draftAssistantMessage;
1167
+ if (draft?.message.role !== "assistant") {
1168
+ return;
1169
+ }
1170
+ const interruptedMessage = createInterruptedAssistantMessage(draft, this.runtime.persona.model);
1171
+ this.session.addMessage(interruptedMessage, { historyEntryId: draft.id });
1172
+ this.messageStates.set(draft.id, "interrupted");
1173
+ this.draftAssistantMessage = undefined;
1174
+ await this.emitPatch("assistant-stream", [
1175
+ {
1176
+ type: "message.replace",
1177
+ message: {
1178
+ id: draft.id,
1179
+ state: "interrupted",
1180
+ modelVisible: true,
1181
+ message: interruptedMessage,
1182
+ },
1183
+ },
1184
+ { type: "lifecycle.set", lifecycle: "idle" },
1185
+ ]);
1186
+ }
1187
+ async cleanupFailedTurn() {
1188
+ if (this.draftAssistantMessage) {
1189
+ await this.interruptDraftAssistantMessage();
1190
+ return;
1191
+ }
1192
+ await this.emitSnapshotResetIfChanged("assistant-message");
1193
+ }
1194
+ }
1195
+ function cloneSessionProtocolSnapshot(snapshot) {
1196
+ return structuredClone(snapshot);
1197
+ }
1198
+ function buildAssistantContentAppendChange(previousDraft, nextDraft) {
1199
+ if (previousDraft.id !== nextDraft.id ||
1200
+ previousDraft.message.role !== "assistant" ||
1201
+ nextDraft.message.role !== "assistant") {
1202
+ return undefined;
1203
+ }
1204
+ const previousText = getAssistantDraftBlockText(previousDraft, "text");
1205
+ const nextText = getAssistantDraftBlockText(nextDraft, "text");
1206
+ const previousThinking = getAssistantDraftBlockText(previousDraft, "thinking");
1207
+ const nextThinking = getAssistantDraftBlockText(nextDraft, "thinking");
1208
+ if (!nextText.startsWith(previousText) || !nextThinking.startsWith(previousThinking)) {
1209
+ return undefined;
1210
+ }
1211
+ const text = nextText.slice(previousText.length);
1212
+ const thinking = nextThinking.slice(previousThinking.length);
1213
+ if (!text && !thinking) {
1214
+ return undefined;
1215
+ }
1216
+ return {
1217
+ type: "message.content.append",
1218
+ messageId: nextDraft.id,
1219
+ ...(text ? { text } : {}),
1220
+ ...(thinking ? { thinking } : {}),
1221
+ timestamp: nextDraft.message.timestamp,
1222
+ };
1223
+ }
1224
+ function assistantDraftContentEquals(previousDraft, nextDraft) {
1225
+ return (previousDraft.id === nextDraft.id &&
1226
+ getAssistantDraftBlockText(previousDraft, "text") ===
1227
+ getAssistantDraftBlockText(nextDraft, "text") &&
1228
+ getAssistantDraftBlockText(previousDraft, "thinking") ===
1229
+ getAssistantDraftBlockText(nextDraft, "thinking"));
1230
+ }
1231
+ function getAssistantDraftBlockText(message, type) {
1232
+ if (message.message.role !== "assistant") {
1233
+ return "";
1234
+ }
1235
+ if (type === "text") {
1236
+ return message.message.content.find((item) => item.type === "text")?.text ?? "";
1237
+ }
1238
+ return message.message.content.find((item) => item.type === "thinking")?.thinking ?? "";
1239
+ }
1240
+ function normalizeRecoveredSnapshot(snapshot) {
1241
+ const recovered = cloneSessionProtocolSnapshot(snapshot);
1242
+ let changed = recovered.lifecycle !== "idle";
1243
+ recovered.lifecycle = "idle";
1244
+ recovered.messages = recovered.messages.map((message) => {
1245
+ if (message.state !== "draft" || message.message.role !== "assistant") {
1246
+ return message;
1247
+ }
1248
+ changed = true;
1249
+ return {
1250
+ id: message.id,
1251
+ state: "interrupted",
1252
+ modelVisible: true,
1253
+ message: createInterruptedAssistantMessageFromModelSnapshot(message, recovered.bootstrap.model),
1254
+ };
1255
+ });
1256
+ return { snapshot: recovered, changed };
1257
+ }
1258
+ function promptCompositionFromSnapshot(snapshot) {
1259
+ const systemMessage = snapshot.messages[0];
1260
+ if (systemMessage?.message.role !== "system") {
1261
+ throw new Error(`session snapshot '${snapshot.sessionId}' is missing its system message`);
1262
+ }
1263
+ return {
1264
+ baseSystemPrompt: systemMessage.message.content,
1265
+ environmentTag: snapshot.bootstrap.prompt.environmentTag,
1266
+ subagentPrompts: cloneSubagentPrompts(snapshot.bootstrap.prompt.subagentPrompts),
1267
+ };
1268
+ }
1269
+ function cloneSubagentPrompts(subagentPrompts) {
1270
+ return { ...subagentPrompts };
1271
+ }
1272
+ function timelineItemForMessage(messageId) {
1273
+ return {
1274
+ type: "message",
1275
+ id: `timeline-${messageId}`,
1276
+ messageId,
1277
+ };
1278
+ }
1279
+ function isCoreMessage(message) {
1280
+ switch (message.role) {
1281
+ case "user":
1282
+ return typeof message.content === "string" || Array.isArray(message.content);
1283
+ case "assistant":
1284
+ return (Array.isArray(message.content) &&
1285
+ "api" in message &&
1286
+ typeof message.provider === "string" &&
1287
+ typeof message.model === "string" &&
1288
+ "usage" in message &&
1289
+ typeof message.stopReason === "string");
1290
+ case "toolResult":
1291
+ return (typeof message.toolCallId === "string" &&
1292
+ typeof message.toolName === "string" &&
1293
+ typeof message.isError === "boolean" &&
1294
+ Array.isArray(message.content));
1295
+ default:
1296
+ return false;
1297
+ }
1298
+ }
1299
+ function createInterruptedAssistantMessage(draft, model) {
1300
+ if (draft.message.role !== "assistant") {
1301
+ throw new Error(`cannot interrupt non-assistant message '${draft.id}'`);
1302
+ }
1303
+ const timestamp = draft.message.timestamp;
1304
+ return {
1305
+ role: "assistant",
1306
+ content: structuredClone(draft.message.content),
1307
+ api: model.api,
1308
+ provider: model.provider,
1309
+ model: model.id,
1310
+ usage: {
1311
+ input: 0,
1312
+ output: 0,
1313
+ cacheRead: 0,
1314
+ cacheWrite: 0,
1315
+ totalTokens: 0,
1316
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
1317
+ },
1318
+ stopReason: "aborted",
1319
+ timestamp,
1320
+ };
1321
+ }
1322
+ function createInterruptedAssistantMessageFromModelSnapshot(draft, model) {
1323
+ if (draft.message.role !== "assistant") {
1324
+ throw new Error(`cannot interrupt non-assistant message '${draft.id}'`);
1325
+ }
1326
+ return {
1327
+ role: "assistant",
1328
+ content: structuredClone(draft.message.content),
1329
+ api: model.api,
1330
+ provider: model.provider,
1331
+ model: model.id,
1332
+ usage: {
1333
+ input: 0,
1334
+ output: 0,
1335
+ cacheRead: 0,
1336
+ cacheWrite: 0,
1337
+ totalTokens: 0,
1338
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
1339
+ },
1340
+ stopReason: "aborted",
1341
+ timestamp: draft.message.timestamp,
1342
+ };
1343
+ }
1344
+ function agentRunFromSubagentEvent(event, originHistoryEntryId, existing) {
1345
+ const state = event.type === "subagent_spawned" || event.type === "subagent_finished"
1346
+ ? event.state
1347
+ : undefined;
1348
+ if (event.type === "subagent_progress" && existing) {
1349
+ return {
1350
+ ...existing,
1351
+ costTotal: event.costTotal,
1352
+ turns: event.turns,
1353
+ toolCalls: event.toolCalls,
1354
+ usage: { ...event.usage },
1355
+ progress: event.text,
1356
+ };
1357
+ }
1358
+ if (event.type === "subagent_abort_requested" && existing) {
1359
+ return { ...existing, abortRequested: true };
1360
+ }
1361
+ if (event.type === "subagent_emit_output" && existing) {
1362
+ return { ...existing, progress: event.text };
1363
+ }
1364
+ if (!state) {
1365
+ return undefined;
1366
+ }
1367
+ return {
1368
+ id: state.id,
1369
+ name: state.name,
1370
+ title: state.title,
1371
+ status: state.status === "success"
1372
+ ? "succeeded"
1373
+ : state.status === "error"
1374
+ ? "failed"
1375
+ : state.status === "aborted"
1376
+ ? "cancelled"
1377
+ : "running",
1378
+ originMessageId: originHistoryEntryId,
1379
+ ...(state.modelLabel !== undefined ? { modelLabel: state.modelLabel } : {}),
1380
+ costTotal: state.costTotal,
1381
+ turns: state.turns,
1382
+ toolCalls: state.toolCalls,
1383
+ usage: { ...state.usage },
1384
+ startedAt: state.startedAt,
1385
+ ...(state.finishedAt !== undefined ? { finishedAt: state.finishedAt } : {}),
1386
+ abortRequested: state.abortRequested,
1387
+ ...(state.finalText !== undefined ? { finalText: state.finalText } : {}),
1388
+ ...(state.error !== undefined ? { error: state.error } : {}),
1389
+ };
1390
+ }
1391
+ function cloneResolvedBootstrap(bootstrap) {
1392
+ return {
1393
+ persona: clonePersona(bootstrap.persona),
1394
+ riskLevel: bootstrap.riskLevel,
1395
+ discoveredSkills: structuredClone(bootstrap.discoveredSkills),
1396
+ personas: bootstrap.personas.map(clonePersona),
1397
+ prompts: structuredClone(bootstrap.prompts),
1398
+ ...(bootstrap.config !== undefined ? { config: structuredClone(bootstrap.config) } : {}),
1399
+ };
1400
+ }
1401
+ function createContentCatalogSnapshot(bootstrap) {
1402
+ return {
1403
+ personas: bootstrap.personas.map(personaSnapshotFromPersona),
1404
+ prompts: bootstrap.prompts.map((prompt) => ({
1405
+ id: prompt.id,
1406
+ ...(prompt.label !== undefined ? { label: prompt.label } : {}),
1407
+ ...(prompt.description !== undefined ? { description: prompt.description } : {}),
1408
+ })),
1409
+ skills: bootstrap.discoveredSkills.map((skill) => ({
1410
+ name: skill.name,
1411
+ description: skill.description,
1412
+ path: skill.path,
1413
+ })),
1414
+ };
1415
+ }
1416
+ function clonePersona(persona) {
1417
+ return structuredClone(persona);
1418
+ }
1419
+ function personaSnapshotFromPersona(persona) {
1420
+ return {
1421
+ id: persona.id,
1422
+ label: persona.label,
1423
+ ...(persona.description !== undefined ? { description: persona.description } : {}),
1424
+ ...(persona.allowedReasoningLevels
1425
+ ? { allowedReasoningLevels: [...persona.allowedReasoningLevels] }
1426
+ : {}),
1427
+ ...(persona.subagents ? { subagents: subagentSnapshotsFromConfig(persona.subagents) } : {}),
1428
+ ...(persona.tools ? { tools: [...persona.tools] } : {}),
1429
+ skills: Array.isArray(persona.skills) ? [...persona.skills] : persona.skills,
1430
+ source: persona.source,
1431
+ };
1432
+ }
1433
+ function modelSnapshotFromModel(model) {
1434
+ const thinkingLevelMap = model.thinkingLevelMap
1435
+ ? Object.fromEntries(Object.entries(model.thinkingLevelMap).filter((entry) => entry[1] !== undefined))
1436
+ : undefined;
1437
+ return {
1438
+ id: model.id,
1439
+ name: model.name,
1440
+ api: model.api,
1441
+ provider: model.provider,
1442
+ baseUrl: model.baseUrl,
1443
+ reasoning: model.reasoning,
1444
+ ...(thinkingLevelMap && Object.keys(thinkingLevelMap).length > 0 ? { thinkingLevelMap } : {}),
1445
+ input: [...model.input],
1446
+ cost: { ...model.cost },
1447
+ contextWindow: model.contextWindow,
1448
+ maxTokens: model.maxTokens,
1449
+ ...(model.compat !== undefined ? { compat: structuredClone(model.compat) } : {}),
1450
+ };
1451
+ }
1452
+ function subagentSnapshotsFromConfig(subagents) {
1453
+ const snapshots = {};
1454
+ for (const [name, config] of Object.entries(subagents)) {
1455
+ snapshots[name] = {
1456
+ ...(config.description !== undefined ? { description: config.description } : {}),
1457
+ ...(config.tools ? { tools: [...config.tools] } : {}),
1458
+ ...(config.riskLevel !== undefined ? { riskLevel: config.riskLevel } : {}),
1459
+ ...(config.launchModels ? { launchModels: [...config.launchModels] } : {}),
1460
+ };
1461
+ }
1462
+ return snapshots;
1463
+ }
1464
+ //# sourceMappingURL=local_session_host.js.map