@orion-agents/orion-code 0.2.2 → 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 (149) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +73 -14
  3. package/README.zh-CN.md +63 -14
  4. package/bin/orion +15 -4
  5. package/dist/cli.js +51 -172
  6. package/dist/cli.js.map +1 -1
  7. package/dist/commands/model-command-handlers.d.ts +2 -2
  8. package/dist/commands/model-command-handlers.d.ts.map +1 -1
  9. package/dist/commands/model-command-handlers.js +113 -32
  10. package/dist/commands/model-command-handlers.js.map +1 -1
  11. package/dist/commands/session-command-handlers.d.ts.map +1 -1
  12. package/dist/commands/session-command-handlers.js +106 -15
  13. package/dist/commands/session-command-handlers.js.map +1 -1
  14. package/dist/commands/types.d.ts +13 -3
  15. package/dist/commands/types.d.ts.map +1 -1
  16. package/dist/commands/types.js +1 -1
  17. package/dist/commands/types.js.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/runtime/agent-loop.d.ts.map +1 -1
  21. package/dist/runtime/agent-loop.js +3 -3
  22. package/dist/runtime/agent-loop.js.map +1 -1
  23. package/dist/runtime/agent-runtime-controller.d.ts +32 -1
  24. package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
  25. package/dist/runtime/agent-runtime-controller.js +242 -27
  26. package/dist/runtime/agent-runtime-controller.js.map +1 -1
  27. package/dist/runtime/agent-runtime-runner.d.ts +2 -1
  28. package/dist/runtime/agent-runtime-runner.d.ts.map +1 -1
  29. package/dist/runtime/legacy-thread-materializer.d.ts +18 -0
  30. package/dist/runtime/legacy-thread-materializer.d.ts.map +1 -1
  31. package/dist/runtime/legacy-thread-materializer.js +27 -12
  32. package/dist/runtime/legacy-thread-materializer.js.map +1 -1
  33. package/dist/runtime/orion-runtime-v1.d.ts +2 -0
  34. package/dist/runtime/orion-runtime-v1.d.ts.map +1 -1
  35. package/dist/runtime/orion-runtime-v1.js +7 -1
  36. package/dist/runtime/orion-runtime-v1.js.map +1 -1
  37. package/dist/runtime/orion-session-runner.d.ts +7 -2
  38. package/dist/runtime/orion-session-runner.d.ts.map +1 -1
  39. package/dist/runtime/orion-session-runner.js +16 -6
  40. package/dist/runtime/orion-session-runner.js.map +1 -1
  41. package/dist/runtime/product-bootstrap.d.ts +22 -0
  42. package/dist/runtime/product-bootstrap.d.ts.map +1 -0
  43. package/dist/runtime/product-bootstrap.js +374 -0
  44. package/dist/runtime/product-bootstrap.js.map +1 -0
  45. package/dist/runtime/product-orion-runtime.d.ts +2 -1
  46. package/dist/runtime/product-orion-runtime.d.ts.map +1 -1
  47. package/dist/runtime/product-orion-runtime.js +83 -13
  48. package/dist/runtime/product-orion-runtime.js.map +1 -1
  49. package/dist/runtime/protocol/runtime-protocol-v1.d.ts +1 -1
  50. package/dist/runtime/protocol/runtime-protocol-v1.js +1 -1
  51. package/dist/runtime/protocol/runtime-protocol-v1.js.map +1 -1
  52. package/dist/runtime/release-receipts.d.ts +63 -3
  53. package/dist/runtime/release-receipts.d.ts.map +1 -1
  54. package/dist/runtime/release-receipts.js +297 -7
  55. package/dist/runtime/release-receipts.js.map +1 -1
  56. package/dist/runtime/step-snapshot.d.ts.map +1 -1
  57. package/dist/runtime/step-snapshot.js +22 -2
  58. package/dist/runtime/step-snapshot.js.map +1 -1
  59. package/dist/runtime/subagents/runtime-integration.d.ts +1 -1
  60. package/dist/runtime/subagents/runtime-integration.d.ts.map +1 -1
  61. package/dist/runtime/subagents/runtime-integration.js +5 -2
  62. package/dist/runtime/subagents/runtime-integration.js.map +1 -1
  63. package/dist/runtime/thread-event-store.d.ts +27 -0
  64. package/dist/runtime/thread-event-store.d.ts.map +1 -1
  65. package/dist/runtime/thread-event-store.js +119 -17
  66. package/dist/runtime/thread-event-store.js.map +1 -1
  67. package/dist/runtime/thread-projection.d.ts +10 -0
  68. package/dist/runtime/thread-projection.d.ts.map +1 -1
  69. package/dist/runtime/thread-projection.js +40 -0
  70. package/dist/runtime/thread-projection.js.map +1 -1
  71. package/dist/runtime/thread-runtime.d.ts +3 -1
  72. package/dist/runtime/thread-runtime.d.ts.map +1 -1
  73. package/dist/runtime/thread-runtime.js +6 -0
  74. package/dist/runtime/thread-runtime.js.map +1 -1
  75. package/dist/runtime/thread-session-view.d.ts +43 -0
  76. package/dist/runtime/thread-session-view.d.ts.map +1 -1
  77. package/dist/runtime/thread-session-view.js +85 -22
  78. package/dist/runtime/thread-session-view.js.map +1 -1
  79. package/dist/runtime/thread-ui-adapter.d.ts +2 -0
  80. package/dist/runtime/thread-ui-adapter.d.ts.map +1 -1
  81. package/dist/runtime/thread-ui-adapter.js +126 -4
  82. package/dist/runtime/thread-ui-adapter.js.map +1 -1
  83. package/dist/runtime/ui-events.d.ts +35 -0
  84. package/dist/runtime/ui-events.d.ts.map +1 -1
  85. package/dist/runtime/ui-events.js +1 -0
  86. package/dist/runtime/ui-events.js.map +1 -1
  87. package/dist/runtime/ui-view-model.d.ts +2 -0
  88. package/dist/runtime/ui-view-model.d.ts.map +1 -1
  89. package/dist/runtime/ui-view-model.js +7 -3
  90. package/dist/runtime/ui-view-model.js.map +1 -1
  91. package/dist/services/global-config.d.ts +25 -0
  92. package/dist/services/global-config.d.ts.map +1 -1
  93. package/dist/services/global-config.js +373 -5
  94. package/dist/services/global-config.js.map +1 -1
  95. package/dist/services/session-storage.d.ts +59 -0
  96. package/dist/services/session-storage.d.ts.map +1 -1
  97. package/dist/services/session-storage.js +303 -101
  98. package/dist/services/session-storage.js.map +1 -1
  99. package/dist/services/settings-coordinator.d.ts +174 -0
  100. package/dist/services/settings-coordinator.d.ts.map +1 -0
  101. package/dist/services/settings-coordinator.js +605 -0
  102. package/dist/services/settings-coordinator.js.map +1 -0
  103. package/dist/services/settings-document-repository.d.ts +138 -0
  104. package/dist/services/settings-document-repository.d.ts.map +1 -0
  105. package/dist/services/settings-document-repository.js +551 -0
  106. package/dist/services/settings-document-repository.js.map +1 -0
  107. package/dist/terminal-ui/launch.d.ts.map +1 -1
  108. package/dist/terminal-ui/launch.js +5 -0
  109. package/dist/terminal-ui/launch.js.map +1 -1
  110. package/dist/tui-ui/state.d.ts.map +1 -1
  111. package/dist/tui-ui/state.js +5 -0
  112. package/dist/tui-ui/state.js.map +1 -1
  113. package/dist/web/event-hub.d.ts +39 -0
  114. package/dist/web/event-hub.d.ts.map +1 -0
  115. package/dist/web/event-hub.js +263 -0
  116. package/dist/web/event-hub.js.map +1 -0
  117. package/dist/web/index.d.ts +6 -0
  118. package/dist/web/index.d.ts.map +1 -0
  119. package/dist/web/index.js +28 -0
  120. package/dist/web/index.js.map +1 -0
  121. package/dist/web/launch.d.ts +9 -0
  122. package/dist/web/launch.d.ts.map +1 -0
  123. package/dist/web/launch.js +64 -0
  124. package/dist/web/launch.js.map +1 -0
  125. package/dist/web/protocol.d.ts +346 -0
  126. package/dist/web/protocol.d.ts.map +1 -0
  127. package/dist/web/protocol.js +348 -0
  128. package/dist/web/protocol.js.map +1 -0
  129. package/dist/web/server.d.ts +20 -0
  130. package/dist/web/server.d.ts.map +1 -0
  131. package/dist/web/server.js +498 -0
  132. package/dist/web/server.js.map +1 -0
  133. package/dist/web/workbench-controller.d.ts +63 -0
  134. package/dist/web/workbench-controller.d.ts.map +1 -0
  135. package/dist/web/workbench-controller.js +774 -0
  136. package/dist/web/workbench-controller.js.map +1 -0
  137. package/dist/web-client/assets/index-BAEO71Gp.css +1 -0
  138. package/dist/web-client/assets/index-DSkFy7gM.js +13 -0
  139. package/dist/web-client/index.html +16 -0
  140. package/docs/architecture/v0.3.0-web-api.yaml +1309 -0
  141. package/docs/migration/v0.2.2-to-v0.3.0-settings.md +114 -0
  142. package/docs/migration/v0.2.2-to-v0.3.0.md +55 -0
  143. package/docs/orion.example.json +9 -2
  144. package/docs/plan/v0.3.0-node-runtime-compatibility-plan.md +57 -0
  145. package/docs/plan/v0.3.0-settings-integration-plan.md +740 -0
  146. package/docs/plan/v0.3.0-web-workbench-plan.md +325 -0
  147. package/docs/readme.md +12 -1
  148. package/npm-shrinkwrap.json +1779 -426
  149. package/package.json +33 -9
@@ -0,0 +1,774 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebWorkbenchController = exports.WebWorkbenchError = void 0;
4
+ exports.pageItems = pageItems;
5
+ const fs_1 = require("fs");
6
+ const path_1 = require("path");
7
+ const agent_runtime_controller_1 = require("../runtime/agent-runtime-controller");
8
+ const mcp_1 = require("../runtime/mcp");
9
+ const product_bootstrap_1 = require("../runtime/product-bootstrap");
10
+ const canonical_1 = require("../runtime/protocol/canonical");
11
+ const thread_session_view_1 = require("../runtime/thread-session-view");
12
+ const tool_detail_repository_1 = require("../runtime/tool-detail-repository");
13
+ const turn_commit_1 = require("../runtime/turn-commit");
14
+ const global_config_1 = require("../services/global-config");
15
+ const settings_coordinator_1 = require("../services/settings-coordinator");
16
+ const session_storage_1 = require("../services/session-storage");
17
+ const event_hub_1 = require("./event-hub");
18
+ const protocol_1 = require("./protocol");
19
+ class WebWorkbenchError extends Error {
20
+ constructor(status, message, code = status === 409 ? 'request_conflict' : 'web_workbench_error') {
21
+ super(message);
22
+ this.status = status;
23
+ this.code = code;
24
+ this.name = 'WebWorkbenchError';
25
+ }
26
+ }
27
+ exports.WebWorkbenchError = WebWorkbenchError;
28
+ const SESSION_VIEW_CACHE_BYTES = 16 * 1024 * 1024;
29
+ /** Owns the sole runtime/controller pair exposed through the local Web host. */
30
+ class WebWorkbenchController {
31
+ constructor(options) {
32
+ this.mutationResults = new Map();
33
+ this.sessionViews = new Map();
34
+ this.sessionViewBytes = 0;
35
+ this.toolDetails = new tool_detail_repository_1.FileToolDetailRepository();
36
+ this.latestStatus = 'Ready';
37
+ this.closed = false;
38
+ this.workspaceValue = canonicalDirectory(options.cwd);
39
+ this.eventHub = options.eventHub ?? new event_hub_1.WebEventHub();
40
+ this.createRuntime =
41
+ options.createRuntime ??
42
+ (cwd => (0, product_bootstrap_1.createProductUiRuntime)({
43
+ cwd,
44
+ shutdownReason: 'Orion Web Workbench shutdown',
45
+ onActiveSessionRuntime: (runtime, sessionId, activation) => this.observeThreadRuntime(runtime, sessionId, activation),
46
+ onSettingsInvalidated: event => {
47
+ this.eventHub.emit({
48
+ type: 'settings_invalidated',
49
+ revision: event.revision,
50
+ reason: event.reason,
51
+ state: event.state,
52
+ }, false);
53
+ },
54
+ }));
55
+ }
56
+ static async create(options) {
57
+ const workbench = new WebWorkbenchController(options);
58
+ await workbench.installRuntime(workbench.workspaceValue);
59
+ return workbench;
60
+ }
61
+ get workspace() {
62
+ return this.workspaceValue;
63
+ }
64
+ get runtime() {
65
+ return this.runtimeValue;
66
+ }
67
+ get controller() {
68
+ return this.controllerValue;
69
+ }
70
+ bootstrap(nonce) {
71
+ return Object.freeze({
72
+ apiVersion: protocol_1.WEB_API_VERSION,
73
+ productVersion: this.runtimeValue.version,
74
+ nonce,
75
+ workspace: this.workspaceValue,
76
+ configured: this.runtimeValue.isConfigured,
77
+ activeSessionId: this.runtimeValue.getSession()?.id ?? null,
78
+ settings: this.settings(),
79
+ capabilities: {
80
+ goal: true,
81
+ plan: true,
82
+ skills: true,
83
+ mcp: true,
84
+ diagnostics: true,
85
+ },
86
+ });
87
+ }
88
+ settings() {
89
+ this.assertSettingsAvailable();
90
+ const describe = this.runtimeValue.describeSettings;
91
+ if (!describe) {
92
+ throw new WebWorkbenchError(503, 'The product Settings coordinator is unavailable.', 'settings_document_unavailable');
93
+ }
94
+ try {
95
+ return describe();
96
+ }
97
+ catch (error) {
98
+ throw mapSettingsError(error);
99
+ }
100
+ }
101
+ listWorkspaces() {
102
+ const paths = new Set([this.workspaceValue]);
103
+ const counts = new Map();
104
+ for (const session of (0, session_storage_1.listSessions)()) {
105
+ try {
106
+ const path = canonicalDirectory(session.projectPath);
107
+ paths.add(path);
108
+ counts.set(path, (counts.get(path) ?? 0) + 1);
109
+ }
110
+ catch {
111
+ // Stale projects remain in session history but are not selectable workspaces.
112
+ }
113
+ }
114
+ return Object.freeze([...paths].sort().map(path => Object.freeze({
115
+ path,
116
+ label: (0, path_1.basename)(path) || path,
117
+ active: path === this.workspaceValue,
118
+ sessionCount: counts.get(path) ?? 0,
119
+ })));
120
+ }
121
+ listSessions() {
122
+ return Object.freeze((0, session_storage_1.listProjectSessions)(this.workspaceValue).map(protocol_1.projectSessionSummary));
123
+ }
124
+ sessionSnapshot(sessionId, cursor, pageSize = 50, tail = false) {
125
+ const session = requireSession(sessionId);
126
+ if (canonicalDirectory(session.projectPath) !== this.workspaceValue) {
127
+ throw new WebWorkbenchError(409, 'Session belongs to another workspace.');
128
+ }
129
+ const active = this.runtimeValue.getSession()?.id === session.id;
130
+ const activeProjection = this.activeOrionRuntime?.sessionId === session.id
131
+ ? this.activeOrionRuntime.runtime.thread.getProjection()
132
+ : undefined;
133
+ let view;
134
+ try {
135
+ view = this.loadSessionView(session, activeProjection);
136
+ }
137
+ catch (error) {
138
+ if (!activeProjection)
139
+ throw error;
140
+ view = undefined;
141
+ }
142
+ const transcriptSource = view
143
+ ? view.transcriptMessages
144
+ : activeProjection
145
+ ? Object.values(activeProjection.items)
146
+ .filter(item => item.kind === 'message' &&
147
+ item.status !== 'started' &&
148
+ ['system', 'user', 'assistant', 'tool'].includes(item.role ?? ''))
149
+ .sort((left, right) => left.startedSeq - right.startedSeq)
150
+ .map(item => ({
151
+ role: item.role,
152
+ content: item.content ?? item.summary ?? item.error ?? '',
153
+ timestamp: item.terminalSeq ?? item.startedSeq,
154
+ }))
155
+ : (0, session_storage_1.readSessionMessages)(session.id).map(message => ({
156
+ role: message.role,
157
+ content: message.content,
158
+ timestamp: message.timestamp,
159
+ ...(message.toolCallId ? { toolCallId: message.toolCallId } : {}),
160
+ ...(message.tool_calls ? { tool_calls: message.tool_calls } : {}),
161
+ ...(message.modelVisibleContent
162
+ ? { modelVisibleContent: message.modelVisibleContent }
163
+ : {}),
164
+ ...(message.appliedSkills ? { appliedSkills: message.appliedSkills } : {}),
165
+ }));
166
+ const tailPage = tail ? pageTranscriptTail(transcriptSource, cursor, pageSize) : undefined;
167
+ const transcriptPage = tailPage ?? pageItems(transcriptSource, cursor, pageSize);
168
+ const transcriptOffset = tailPage?.offset ?? (cursor ? decodePageCursor(cursor) : 0);
169
+ const transcript = Object.freeze({
170
+ items: Object.freeze(transcriptPage.items.map((message, index) => ({
171
+ ...message,
172
+ id: `${session.id}:message:${transcriptOffset + index + 1}`,
173
+ }))),
174
+ nextCursor: transcriptPage.nextCursor,
175
+ });
176
+ const commitProjections = view
177
+ ? uniqueCommitProjections([view.latestPlanTurnCommit, view.latestTurnCommit])
178
+ : activeProjection
179
+ ? latestAuthorityCommits(activeProjection)
180
+ : [];
181
+ const commits = commitProjections.map(commit => (0, turn_commit_1.parseTurnCommitV1)(commit.receipt));
182
+ const latestCommit = commits.at(-1);
183
+ const planCommit = [...commits].reverse().find(commit => commit.planReceipt);
184
+ const plan = planCommit?.planReceipt
185
+ ? pickPlanReceipt((0, turn_commit_1.parsePlanReceiptV1)(planCommit.planReceipt))
186
+ : null;
187
+ const state = this.runtimeValue.store.getSnapshot();
188
+ const pendingPermissions = active
189
+ ? this.controllerValue.getPendingPermissions().map(request => ({
190
+ id: request.id,
191
+ toolName: request.name,
192
+ ...(request.reason ? { reason: request.reason } : {}),
193
+ sanitizedArguments: request.args,
194
+ allowedScopes: ['once', 'project', 'global'],
195
+ }))
196
+ : [];
197
+ const projectionStatus = activeProjection?.status ?? (view ? 'idle' : 'legacy');
198
+ const goal = latestCommit?.goalState && latestCommit.goalStateDigest
199
+ ? {
200
+ authority: 'turn_commit',
201
+ digest: latestCommit.goalStateDigest,
202
+ state: JSON.parse(latestCommit.goalState),
203
+ }
204
+ : null;
205
+ return Object.freeze({
206
+ apiVersion: protocol_1.WEB_API_VERSION,
207
+ session: (0, protocol_1.projectSessionSummary)(session),
208
+ threadId: activeProjection?.threadId ?? view?.threadId ?? null,
209
+ threadCursor: activeProjection?.cursor ?? view?.cursor ?? 0,
210
+ eventCursor: this.eventHub.snapshot().latest,
211
+ ...(activeProjection?.digest || view?.projectionDigest
212
+ ? { projectionDigest: activeProjection?.digest ?? view?.projectionDigest }
213
+ : {}),
214
+ threadStatus: projectionStatus,
215
+ ...(activeProjection?.activeTurnId ? { activeTurnId: activeProjection.activeTurnId } : {}),
216
+ transcript,
217
+ runtime: {
218
+ active,
219
+ processing: active ? state.isProcessing : false,
220
+ agentMode: state.agentMode,
221
+ permissionMode: state.permissionMode,
222
+ status: this.latestStatus,
223
+ followups: active ? Object.freeze([...this.controllerValue.getFollowupQueue()]) : [],
224
+ followupLimit: 16,
225
+ contextUsage: active ? state.contextUsage : null,
226
+ tokenUsage: active ? state.tokenUsage : null,
227
+ },
228
+ pendingApprovals: Object.freeze(pendingPermissions),
229
+ goal,
230
+ plan,
231
+ recoveryDiagnostics: view?.diagnostics ?? [],
232
+ });
233
+ }
234
+ async skills() {
235
+ const descriptors = (await this.runtimeValue.inspectSkills?.()) ?? [];
236
+ return Object.freeze(descriptors.map(descriptor => Object.freeze({
237
+ id: descriptor.id,
238
+ name: descriptor.name,
239
+ description: descriptor.description,
240
+ providerId: descriptor.providerId,
241
+ sourceScope: descriptor.sourceScope,
242
+ modelInvocable: descriptor.modelInvocable,
243
+ userInvocable: descriptor.userInvocable,
244
+ requestedCapabilities: Object.freeze([...descriptor.requestedCapabilities]),
245
+ digest: descriptor.digest,
246
+ })));
247
+ }
248
+ mcp() {
249
+ const descriptors = this.runtimeValue.inspectMcp?.() ?? [];
250
+ let runtimeServers = new Map();
251
+ try {
252
+ runtimeServers = new Map((this.activeOrionRuntime?.runtime.diagnostics().mcp.servers ?? []).map(server => [
253
+ server.serverId,
254
+ server,
255
+ ]));
256
+ }
257
+ catch {
258
+ // A concurrent session transition may close the previous diagnostics graph.
259
+ }
260
+ return Object.freeze(descriptors.map(descriptor => {
261
+ const runtime = runtimeServers.get(descriptor.id);
262
+ return Object.freeze({
263
+ id: descriptor.id,
264
+ name: descriptor.name,
265
+ ...(descriptor.description ? { description: descriptor.description } : {}),
266
+ transport: descriptor.transport,
267
+ tags: Object.freeze([...(descriptor.tags ?? [])]),
268
+ disabled: descriptor.disabled ?? false,
269
+ state: runtime?.state ?? 'dormant',
270
+ generation: runtime?.generation ?? 0,
271
+ toolCount: runtime?.toolCount ?? 0,
272
+ activeCallCount: runtime?.activeCallCount ?? 0,
273
+ ...(runtime?.failure ? { failure: runtime.failure } : {}),
274
+ });
275
+ }));
276
+ }
277
+ async listToolDetails() {
278
+ return Object.freeze(await this.toolDetails.list(this.workspaceValue));
279
+ }
280
+ async readToolDetail(callId, offsetBytes, limitBytes) {
281
+ const entry = (await this.toolDetails.list(this.workspaceValue)).find(detail => detail.callId === callId || detail.artifactId === callId);
282
+ if (!entry?.artifactId)
283
+ throw new WebWorkbenchError(404, 'Tool detail was not found.');
284
+ return this.toolDetails.read({
285
+ callId: entry.callId,
286
+ sequence: entry.sequence,
287
+ artifactId: entry.artifactId,
288
+ outputBytes: entry.outputBytes,
289
+ }, { offsetBytes, limitBytes }, this.workspaceValue);
290
+ }
291
+ async createSession(name) {
292
+ this.assertReadyForTransition('create a session');
293
+ // A new Session inherits the durable default; an override on the currently
294
+ // active Session must never leak into the next Session.
295
+ const session = (0, session_storage_1.createSession)(this.workspaceValue, this.settings().sections.defaults.model.effectiveValue);
296
+ (0, global_config_1.incrementSessionCount)();
297
+ const named = name?.trim() ? ((0, session_storage_1.renameSession)(session.id, name.trim()) ?? session) : session;
298
+ this.runtimeValue.setSession(named);
299
+ await this.runtimeValue.rebindSessionRuntime?.();
300
+ this.eventHub.emitRuntime({ type: 'transcript_clear' });
301
+ this.emitState();
302
+ return (0, protocol_1.projectSessionSummary)(named);
303
+ }
304
+ async activateSession(sessionId) {
305
+ this.assertReadyForTransition('switch sessions');
306
+ const session = requireSession(sessionId);
307
+ if (canonicalDirectory(session.projectPath) !== this.workspaceValue) {
308
+ throw new WebWorkbenchError(409, 'Session belongs to another workspace.');
309
+ }
310
+ const result = this.controllerValue.handle({
311
+ type: 'select_session',
312
+ sessionId: session.id,
313
+ source: 'programmatic',
314
+ });
315
+ await this.controllerValue.waitForIdle();
316
+ if (this.runtimeValue.getSession()?.id !== session.id) {
317
+ throw new WebWorkbenchError(409, 'Session activation was rejected by the runtime.');
318
+ }
319
+ return { requestId: `activate:${session.id}`, result: result.type };
320
+ }
321
+ renameSession(sessionId, name) {
322
+ const session = requireSession(sessionId);
323
+ if (canonicalDirectory(session.projectPath) !== this.workspaceValue) {
324
+ throw new WebWorkbenchError(409, 'Session belongs to another workspace.');
325
+ }
326
+ if (name.length > 120)
327
+ throw new WebWorkbenchError(400, 'Session name is too long.');
328
+ const updated = (0, session_storage_1.renameSession)(session.id, name);
329
+ if (!updated)
330
+ throw new WebWorkbenchError(404, 'Session no longer exists.');
331
+ return (0, protocol_1.projectSessionSummary)(updated);
332
+ }
333
+ async switchWorkspace(path) {
334
+ if (this.transition)
335
+ throw new WebWorkbenchError(409, 'A workspace transition is running.');
336
+ const next = canonicalDirectory(path);
337
+ if (next === this.workspaceValue)
338
+ return;
339
+ this.assertReadyForTransition('switch workspaces');
340
+ const previousRuntime = this.runtimeValue;
341
+ const previousController = this.controllerValue;
342
+ const previousWorkspace = this.workspaceValue;
343
+ const transition = (async () => {
344
+ await previousController.stopActiveTurn();
345
+ await previousController.waitForIdle();
346
+ await previousRuntime.shutdown();
347
+ try {
348
+ await this.installRuntime(next);
349
+ }
350
+ catch (error) {
351
+ try {
352
+ await this.installRuntime(previousWorkspace);
353
+ }
354
+ catch {
355
+ throw new WebWorkbenchError(503, 'Workspace activation failed and the previous workspace could not be restored.');
356
+ }
357
+ throw error;
358
+ }
359
+ const describe = this.runtimeValue.describeSettings;
360
+ if (!describe) {
361
+ throw new WebWorkbenchError(503, 'The product Settings coordinator is unavailable.', 'settings_document_unavailable');
362
+ }
363
+ const settings = describe();
364
+ this.eventHub.emit({
365
+ type: 'settings_invalidated',
366
+ revision: settings.revision,
367
+ reason: 'workspace-change',
368
+ state: settings.state === 'ready' || settings.state === 'read-only' ? 'ready' : 'invalid',
369
+ }, false);
370
+ })().finally(() => {
371
+ if (this.transition === transition)
372
+ this.transition = undefined;
373
+ });
374
+ this.transition = transition;
375
+ return transition;
376
+ }
377
+ dispatch(raw) {
378
+ const command = (0, protocol_1.parseWebCommand)(raw);
379
+ return this.executeMutation(command.requestId, 'command', command, () => {
380
+ const runtimeResult = this.controllerValue.handle((0, protocol_1.toAgentRuntimeInput)(command));
381
+ return Object.freeze({
382
+ requestId: command.requestId,
383
+ result: runtimeResult.type,
384
+ detail: JSON.stringify(runtimeResult),
385
+ });
386
+ });
387
+ }
388
+ async updateSettings(input) {
389
+ this.assertSettingsAvailable();
390
+ const update = this.runtimeValue.updateSettings;
391
+ if (!update) {
392
+ throw new WebWorkbenchError(503, 'The product Settings coordinator is unavailable.', 'settings_document_unavailable');
393
+ }
394
+ try {
395
+ const result = await update({
396
+ requestId: input.requestId,
397
+ expectedRevision: input.expectedRevision,
398
+ operations: input.operations,
399
+ });
400
+ return Object.freeze({
401
+ revision: result.revision,
402
+ appliedKeys: result.appliedKeys,
403
+ settings: result.document,
404
+ });
405
+ }
406
+ catch (error) {
407
+ throw mapSettingsError(error);
408
+ }
409
+ }
410
+ async openSettingsDocument() {
411
+ this.assertSettingsAvailable();
412
+ const coordinator = this.runtimeValue.settingsCoordinator;
413
+ if (!coordinator) {
414
+ throw new WebWorkbenchError(503, 'The product Settings coordinator is unavailable.', 'settings_document_unavailable');
415
+ }
416
+ try {
417
+ await coordinator.openDocument();
418
+ return true;
419
+ }
420
+ catch (error) {
421
+ throw mapSettingsError(error);
422
+ }
423
+ }
424
+ executeMutation(requestId, operation, payload, action) {
425
+ if (!requestId.trim() || requestId.length > 128) {
426
+ throw new WebWorkbenchError(400, 'requestId must be a non-empty string up to 128 characters.');
427
+ }
428
+ const fingerprint = (0, canonical_1.digestRuntimeValue)({ operation, payload });
429
+ const cached = this.mutationResults.get(requestId);
430
+ if (cached) {
431
+ if (cached.fingerprint !== fingerprint) {
432
+ throw new WebWorkbenchError(409, 'requestId was already used for another mutation.', 'request_id_conflict');
433
+ }
434
+ return cached.result;
435
+ }
436
+ const result = Promise.resolve().then(action);
437
+ this.mutationResults.set(requestId, { fingerprint, result });
438
+ while (this.mutationResults.size > 512) {
439
+ const oldest = this.mutationResults.keys().next().value;
440
+ if (!oldest)
441
+ break;
442
+ this.mutationResults.delete(oldest);
443
+ }
444
+ return result;
445
+ }
446
+ async diagnostics() {
447
+ const state = this.runtimeValue.store.getSnapshot();
448
+ const mcp = (0, mcp_1.loadFirstPartyMcpConfigurationV1)();
449
+ const activeSessionId = this.runtimeValue.getSession()?.id ?? null;
450
+ // Harness diagnostics may need the lazy Session runtime. A read-only Web
451
+ // baseline must not manufacture a Session merely by opening the page.
452
+ const harness = activeSessionId
453
+ ? ((await this.runtimeValue.getHarnessDiagnostics?.()) ?? null)
454
+ : null;
455
+ return {
456
+ workspace: this.workspaceValue,
457
+ activeSessionId,
458
+ configured: this.runtimeValue.isConfigured,
459
+ processing: state.isProcessing,
460
+ agentMode: state.agentMode,
461
+ permissionMode: state.permissionMode,
462
+ contextUsage: state.contextUsage,
463
+ tokenUsage: state.tokenUsage,
464
+ plan: state.currentPlan,
465
+ todos: state.todos,
466
+ skills: {
467
+ configuredPaths: this.runtimeValue.config.skills?.paths ?? [],
468
+ loadedFromPrompt: state.skillsContent.length > 0,
469
+ },
470
+ mcp: { servers: mcpServerIds(mcp) },
471
+ harness,
472
+ eventStream: this.eventHub.snapshot(),
473
+ };
474
+ }
475
+ async shutdown() {
476
+ if (this.closed)
477
+ return;
478
+ this.closed = true;
479
+ await this.controllerValue.stopActiveTurn();
480
+ await this.controllerValue.waitForIdle();
481
+ await this.runtimeValue.shutdown();
482
+ this.eventHub.close();
483
+ }
484
+ async installRuntime(workspace) {
485
+ const runtime = await this.createRuntime(workspace);
486
+ this.activeOrionRuntime = undefined;
487
+ this.sessionViews.clear();
488
+ this.sessionViewBytes = 0;
489
+ this.latestStatus = 'Ready';
490
+ this.workspaceValue = workspace;
491
+ this.runtimeValue = runtime;
492
+ const eventSink = {
493
+ emit: (event) => {
494
+ if (event.type === 'status_changed')
495
+ this.latestStatus = event.message;
496
+ const activeSessionId = this.runtimeValue.getSession()?.id;
497
+ const activeThread = activeSessionId && this.activeOrionRuntime?.sessionId === activeSessionId
498
+ ? this.activeOrionRuntime.runtime.thread.getProjection().threadId
499
+ : undefined;
500
+ const envelope = this.eventHub.emitRuntime(event, {
501
+ ...(activeSessionId ? { sessionId: activeSessionId } : {}),
502
+ ...(activeThread ? { threadId: activeThread } : {}),
503
+ });
504
+ if (event.type === 'session_restored')
505
+ this.emitState();
506
+ return event.type === 'transcript_append' ? `web-entry-${envelope.cursor}` : undefined;
507
+ },
508
+ };
509
+ this.controllerValue = new agent_runtime_controller_1.AgentRuntimeController({
510
+ runtime,
511
+ eventSink,
512
+ uiRenderer: 'web',
513
+ // Thread events are authoritative for Web transcript projection. Echoing here
514
+ // would duplicate every submitted user item when the Thread observer catches up.
515
+ echoSubmittedInput: false,
516
+ useRuntimeToolPermissions: true,
517
+ uiCapabilities: {
518
+ structuredPickers: true,
519
+ inlineProgress: true,
520
+ suppressLegacyTokenMeta: true,
521
+ extraAssistantSpacing: true,
522
+ suppressAbortNotice: true,
523
+ },
524
+ });
525
+ this.emitState();
526
+ }
527
+ emitState() {
528
+ this.eventHub.emit({
529
+ type: 'workbench_state',
530
+ workspace: this.workspaceValue,
531
+ activeSessionId: this.runtimeValue.getSession()?.id ?? null,
532
+ });
533
+ }
534
+ observeThreadRuntime(runtime, sessionId, activation) {
535
+ let replaying = true;
536
+ const pending = [];
537
+ const unsubscribe = runtime.thread.observeCommittedEvents(events => {
538
+ if (replaying)
539
+ pending.push(...events);
540
+ else
541
+ for (const event of events)
542
+ this.eventHub.emitThread(event, sessionId);
543
+ });
544
+ try {
545
+ const edge = runtime.thread.getProjection().cursor;
546
+ // Historical transcript state comes from the cursor-bound snapshot.
547
+ // Publishing the entire Thread through the live SSE hub on every switch
548
+ // made activation O(history) and evicted the reconnect window.
549
+ replaying = false;
550
+ for (const event of pending
551
+ .filter(event => event.seq > edge)
552
+ .sort((left, right) => left.seq - right.seq)) {
553
+ this.eventHub.emitThread(event, sessionId);
554
+ }
555
+ if (activation?.sessionId === sessionId && activation.view) {
556
+ this.rememberSessionView(sessionId, activation.view);
557
+ }
558
+ this.activeOrionRuntime = { runtime, sessionId };
559
+ }
560
+ catch (error) {
561
+ unsubscribe();
562
+ throw error;
563
+ }
564
+ return () => {
565
+ unsubscribe();
566
+ if (this.activeOrionRuntime?.runtime === runtime)
567
+ this.activeOrionRuntime = undefined;
568
+ };
569
+ }
570
+ loadSessionView(session, activeProjection) {
571
+ const expectedFingerprint = activeProjection
572
+ ? threadViewFingerprint(activeProjection)
573
+ : session.threadReadModel
574
+ ? [
575
+ session.threadReadModel.threadId,
576
+ session.threadReadModel.cursor,
577
+ session.threadReadModel.projectionDigest,
578
+ ].join(':')
579
+ : undefined;
580
+ const cached = expectedFingerprint ? this.sessionViews.get(session.id) : undefined;
581
+ if (cached && expectedFingerprint && cached.fingerprint === expectedFingerprint) {
582
+ this.sessionViews.delete(session.id);
583
+ this.sessionViews.set(session.id, cached);
584
+ return cached.view;
585
+ }
586
+ const view = (0, thread_session_view_1.loadThreadSessionViewV1)(this.workspaceValue, session.id);
587
+ if (!view)
588
+ return undefined;
589
+ return this.rememberSessionView(session.id, view);
590
+ }
591
+ rememberSessionView(sessionId, view) {
592
+ const previous = this.sessionViews.get(sessionId);
593
+ if (previous)
594
+ this.sessionViewBytes -= previous.bytes;
595
+ const entry = {
596
+ fingerprint: [view.threadId, view.cursor, view.projectionDigest].join(':'),
597
+ view,
598
+ bytes: estimateSessionViewBytes(view),
599
+ };
600
+ this.sessionViews.delete(sessionId);
601
+ this.sessionViews.set(sessionId, entry);
602
+ this.sessionViewBytes += entry.bytes;
603
+ while (this.sessionViewBytes > SESSION_VIEW_CACHE_BYTES && this.sessionViews.size > 1) {
604
+ const oldest = this.sessionViews.keys().next().value;
605
+ if (!oldest)
606
+ break;
607
+ const evicted = this.sessionViews.get(oldest);
608
+ this.sessionViews.delete(oldest);
609
+ this.sessionViewBytes -= evicted?.bytes ?? 0;
610
+ }
611
+ return view;
612
+ }
613
+ assertReadyForTransition(operation) {
614
+ if (this.closed)
615
+ throw new WebWorkbenchError(503, 'Web Workbench is closed.');
616
+ if (this.transition) {
617
+ throw new WebWorkbenchError(409, 'A workspace transition is running.', 'runtime_busy');
618
+ }
619
+ if (this.controllerValue.hasActiveTurn()) {
620
+ throw new WebWorkbenchError(409, `Cannot ${operation} while a turn is active.`, 'runtime_busy');
621
+ }
622
+ }
623
+ assertSettingsAvailable() {
624
+ if (this.closed) {
625
+ throw new WebWorkbenchError(503, 'Web Workbench is closed.', 'settings_document_unavailable');
626
+ }
627
+ if (this.transition) {
628
+ throw new WebWorkbenchError(409, 'A workspace transition is running.', 'runtime_busy');
629
+ }
630
+ }
631
+ }
632
+ exports.WebWorkbenchController = WebWorkbenchController;
633
+ function mapSettingsError(error) {
634
+ if (error instanceof WebWorkbenchError)
635
+ return error;
636
+ if (error instanceof settings_coordinator_1.SettingsCoordinatorError) {
637
+ return new WebWorkbenchError(error.status, error.message, error.code);
638
+ }
639
+ return new WebWorkbenchError(503, 'The product Settings coordinator is unavailable.', 'settings_document_unavailable');
640
+ }
641
+ function pickPlanReceipt(receipt) {
642
+ return Object.freeze({
643
+ body: receipt.plan,
644
+ returnMode: receipt.returnMode,
645
+ digest: receipt.digest,
646
+ });
647
+ }
648
+ function threadViewFingerprint(projection) {
649
+ return [projection.threadId, projection.cursor, projection.digest].join(':');
650
+ }
651
+ function uniqueCommitProjections(commits) {
652
+ return [...new Map(commits.filter(Boolean).map(commit => [commit.seq, commit])).values()].sort((left, right) => left.seq - right.seq);
653
+ }
654
+ function latestAuthorityCommits(projection) {
655
+ const commits = Object.values(projection.turns)
656
+ .flatMap(turn => (turn.commit ? [turn.commit] : []))
657
+ .sort((left, right) => left.seq - right.seq);
658
+ const latest = commits.at(-1);
659
+ const latestPlan = [...commits].reverse().find(commit => {
660
+ try {
661
+ return Boolean((0, turn_commit_1.parseTurnCommitV1)(commit.receipt).planReceipt);
662
+ }
663
+ catch {
664
+ return false;
665
+ }
666
+ });
667
+ return uniqueCommitProjections([latestPlan, latest]);
668
+ }
669
+ function estimateSessionViewBytes(view) {
670
+ let bytes = 1024;
671
+ for (const message of view.transcriptMessages) {
672
+ bytes += Buffer.byteLength(message.content, 'utf8') + 256;
673
+ if (message.modelVisibleContent) {
674
+ bytes += Buffer.byteLength(message.modelVisibleContent, 'utf8');
675
+ }
676
+ }
677
+ for (const message of view.modelHistory) {
678
+ bytes += Buffer.byteLength(JSON.stringify(message), 'utf8') + 128;
679
+ }
680
+ for (const commit of uniqueCommitProjections([
681
+ view.latestPlanTurnCommit,
682
+ view.latestTurnCommit,
683
+ ])) {
684
+ bytes += Buffer.byteLength(commit.receipt, 'utf8');
685
+ }
686
+ return bytes;
687
+ }
688
+ function pageItems(items, cursor, pageSize = 50) {
689
+ if (!Number.isSafeInteger(pageSize) || pageSize < 1 || pageSize > 100) {
690
+ throw new WebWorkbenchError(400, 'pageSize must be an integer from 1 through 100.');
691
+ }
692
+ const offset = cursor ? decodePageCursor(cursor) : 0;
693
+ if (offset > items.length)
694
+ throw new WebWorkbenchError(400, 'Page cursor is out of range.');
695
+ const page = items.slice(offset, offset + pageSize);
696
+ const nextOffset = offset + page.length;
697
+ return Object.freeze({
698
+ items: Object.freeze(page),
699
+ nextCursor: nextOffset < items.length ? encodePageCursor(nextOffset) : null,
700
+ });
701
+ }
702
+ function pageTranscriptTail(items, cursor, pageSize = 50) {
703
+ if (!Number.isSafeInteger(pageSize) || pageSize < 1 || pageSize > 100) {
704
+ throw new WebWorkbenchError(400, 'pageSize must be an integer from 1 through 100.');
705
+ }
706
+ const end = cursor ? decodePageCursor(cursor) : items.length;
707
+ if (end > items.length)
708
+ throw new WebWorkbenchError(400, 'Page cursor is out of range.');
709
+ const offset = Math.max(0, end - pageSize);
710
+ return Object.freeze({
711
+ items: Object.freeze(items.slice(offset, end)),
712
+ nextCursor: offset > 0 ? encodePageCursor(offset) : null,
713
+ offset,
714
+ });
715
+ }
716
+ function encodePageCursor(offset) {
717
+ return Buffer.from(JSON.stringify({ version: 1, offset })).toString('base64url');
718
+ }
719
+ function decodePageCursor(cursor) {
720
+ if (!cursor || cursor.length > 512)
721
+ throw new WebWorkbenchError(400, 'Page cursor is invalid.');
722
+ try {
723
+ const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
724
+ if (!parsed ||
725
+ typeof parsed !== 'object' ||
726
+ parsed.version !== 1 ||
727
+ !Number.isSafeInteger(parsed.offset) ||
728
+ (parsed.offset ?? -1) < 0) {
729
+ throw new Error('invalid cursor');
730
+ }
731
+ return parsed.offset;
732
+ }
733
+ catch {
734
+ throw new WebWorkbenchError(400, 'Page cursor is invalid.');
735
+ }
736
+ }
737
+ function canonicalDirectory(path) {
738
+ const candidate = (0, path_1.resolve)(path);
739
+ let canonical;
740
+ try {
741
+ canonical = (0, fs_1.realpathSync)(candidate);
742
+ }
743
+ catch {
744
+ throw new WebWorkbenchError(400, 'Workspace path does not exist.');
745
+ }
746
+ try {
747
+ if (!(0, fs_1.statSync)(canonical).isDirectory()) {
748
+ throw new WebWorkbenchError(400, 'Workspace path must be a directory.');
749
+ }
750
+ }
751
+ catch (error) {
752
+ if (error instanceof WebWorkbenchError)
753
+ throw error;
754
+ throw new WebWorkbenchError(400, 'Workspace path is not readable.');
755
+ }
756
+ return canonical;
757
+ }
758
+ function requireSession(sessionId) {
759
+ if (!sessionId.trim())
760
+ throw new WebWorkbenchError(400, 'Session id is required.');
761
+ const session = (0, session_storage_1.loadSessionMeta)(sessionId);
762
+ if (!session)
763
+ throw new WebWorkbenchError(404, 'Session was not found.');
764
+ return session;
765
+ }
766
+ function mcpServerIds(config) {
767
+ const ids = new Set();
768
+ for (const envelope of [config.mcpServers, config.servers, config.orion?.mcp?.servers]) {
769
+ for (const id of Object.keys(envelope ?? {}))
770
+ ids.add(id);
771
+ }
772
+ return [...ids].sort();
773
+ }
774
+ //# sourceMappingURL=workbench-controller.js.map