@myagentroam/node 0.9.2 → 0.9.4

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 (81) hide show
  1. package/dist/codex-app-server.d.ts +24 -0
  2. package/dist/codex-app-server.js +100 -5
  3. package/dist/connector.js +18 -15
  4. package/dist/control-outbound-scheduler.d.ts +4 -1
  5. package/dist/control-outbound-scheduler.js +39 -6
  6. package/dist/database.d.ts +2 -19
  7. package/dist/database.js +1 -62
  8. package/dist/event-buffer.d.ts +2 -5
  9. package/dist/event-buffer.js +3 -12
  10. package/dist/native-session-history.js +33 -12
  11. package/dist/runner/claude/managed-run-controller.d.ts +0 -1
  12. package/dist/runner/claude/managed-run-controller.js +17 -12
  13. package/dist/runner/codex/conversation-parser.d.ts +1 -1
  14. package/dist/runner/codex/conversation-parser.js +3 -0
  15. package/dist/runner/codex/managed-run-controller.d.ts +11 -2
  16. package/dist/runner/codex/managed-run-controller.js +79 -24
  17. package/dist/runner/codex-runner.d.ts +3 -0
  18. package/dist/runner/codex-runner.js +42 -3
  19. package/dist/runner/opencode/conversation-parser.js +1 -1
  20. package/dist/runner/opencode/managed-run-controller.d.ts +7 -2
  21. package/dist/runner/opencode/managed-run-controller.js +72 -14
  22. package/dist/runner/opencode-runner.d.ts +3 -0
  23. package/dist/runner/opencode-runner.js +25 -0
  24. package/dist/runner-profiles.js +10 -6
  25. package/dist/runtime-state.d.ts +7 -12
  26. package/dist/runtime-state.js +43 -53
  27. package/dist/service/attachment-domain-state.d.ts +2 -0
  28. package/dist/service/attachment-domain-state.js +2 -0
  29. package/dist/service/conversation-history-service.d.ts +8 -0
  30. package/dist/service/conversation-history-service.js +181 -9
  31. package/dist/service/conversation-segment-service.js +17 -10
  32. package/dist/service/external-session-resume-service.js +9 -0
  33. package/dist/service/fake-managed-run-service.d.ts +0 -1
  34. package/dist/service/fake-managed-run-service.js +0 -2
  35. package/dist/service/native-session-projection-service.js +5 -0
  36. package/dist/service/native-session-watch-service.d.ts +8 -2
  37. package/dist/service/native-session-watch-service.js +28 -10
  38. package/dist/service/node-connection-lifecycle-service.d.ts +0 -2
  39. package/dist/service/node-connection-lifecycle-service.js +0 -1
  40. package/dist/service/node-control-channel.js +1 -1
  41. package/dist/service/node-control-message-service.d.ts +0 -1
  42. package/dist/service/node-control-message-service.js +9 -6
  43. package/dist/service/node-request-service.js +2 -1
  44. package/dist/service/node-workspace-coordinator.d.ts +1 -4
  45. package/dist/service/node-workspace-coordinator.js +2 -12
  46. package/dist/service/run-attachment-service.js +30 -6
  47. package/dist/service/run-event-service.d.ts +1 -0
  48. package/dist/service/run-event-service.js +80 -0
  49. package/dist/service/run-workbench-service.d.ts +1 -1
  50. package/dist/service/run-workbench-service.js +1 -5
  51. package/dist/service/runner-interaction-service.d.ts +1 -2
  52. package/dist/service/runner-interaction-service.js +1 -4
  53. package/dist/service/session-context-service.d.ts +1 -0
  54. package/dist/service/session-context-service.js +14 -0
  55. package/dist/service/session-domain-state.d.ts +1 -0
  56. package/dist/service/session-domain-state.js +1 -0
  57. package/dist/service/session-lifecycle-service.js +4 -12
  58. package/dist/service/session-message-service.d.ts +1 -0
  59. package/dist/service/session-message-service.js +109 -85
  60. package/dist/service/skill-directory-service.d.ts +3 -1
  61. package/dist/service/skill-directory-service.js +28 -4
  62. package/dist/service/skill-node-operation-service.d.ts +1 -1
  63. package/dist/service/skill-node-operation-service.js +4 -2
  64. package/dist/service/workbench-event-service.d.ts +1 -13
  65. package/dist/service/workbench-event-service.js +2 -36
  66. package/dist/service/workbench-manifest-service.d.ts +0 -1
  67. package/dist/service/workbench-manifest-service.js +0 -1
  68. package/dist/service/workspace-change-service.js +1 -1
  69. package/dist/service/workspace-domain-state.d.ts +1 -0
  70. package/dist/service/workspace-queue-workbench-service.d.ts +13 -2
  71. package/dist/service/workspace-queue-workbench-service.js +8 -2
  72. package/dist/service/workspace-upload-service.d.ts +2 -0
  73. package/dist/service/workspace-upload-service.js +28 -6
  74. package/dist/service/workspace-workbench-service.d.ts +0 -3
  75. package/dist/service/workspace-workbench-service.js +0 -17
  76. package/dist/util/runner-error.d.ts +6 -0
  77. package/dist/util/runner-error.js +52 -0
  78. package/dist/util/runner-native-session-parsers.d.ts +2 -1
  79. package/dist/util/runner-native-session-parsers.js +63 -15
  80. package/dist/workspace.js +33 -8
  81. package/package.json +2 -2
@@ -51,6 +51,18 @@ export interface CodexRunConfiguration {
51
51
  readonly collaborationMode?: 'default' | 'plan';
52
52
  readonly mcpServers?: unknown;
53
53
  }
54
+ interface CodexRpcErrorPayload {
55
+ readonly code?: unknown;
56
+ readonly message?: unknown;
57
+ readonly data?: unknown;
58
+ }
59
+ export declare class CodexAppServerRpcError extends Error {
60
+ readonly code: unknown;
61
+ readonly rpcMessage: string;
62
+ readonly data: unknown;
63
+ constructor(payload: CodexRpcErrorPayload);
64
+ }
65
+ export declare function isCodexRolloutMissingError(error: unknown): boolean;
54
66
  /** Public App Server turn inputs supported by the Workbench Composer. */
55
67
  export type CodexComposerInput = {
56
68
  readonly type: 'text';
@@ -71,15 +83,26 @@ export declare class CodexAppServerClient {
71
83
  private socketPath;
72
84
  private nextId;
73
85
  private readonly pending;
86
+ private readonly pendingDrainWaiters;
74
87
  private readonly notifications;
75
88
  private environment;
89
+ private starting;
90
+ private restarting;
91
+ private lifecycleRevision;
76
92
  constructor(options?: CodexAppServerOptions);
77
93
  configureEnvironment(environment: Readonly<Record<string, string>>): void;
78
94
  clearEnvironment(): void;
79
95
  start(): Promise<void>;
96
+ restart(): Promise<void>;
97
+ private startManaged;
98
+ private restartManaged;
80
99
  stop(): void;
100
+ private stopManaged;
81
101
  onNotification(listener: (notification: JsonRpcNotification) => void): () => void;
82
102
  request(method: string, params: unknown): Promise<unknown>;
103
+ private requestDirect;
104
+ private waitForPendingRequests;
105
+ private resolvePendingDrain;
83
106
  notify(method: string, params: unknown): void;
84
107
  respond(requestId: string | number, result: unknown): void;
85
108
  startThread(cwd: string, configuration?: CodexRunConfiguration): Promise<unknown>;
@@ -106,3 +129,4 @@ export declare class CodexAppServerClient {
106
129
  export declare function probeCodex(command?: string): CodexProbe;
107
130
  export declare function probeCodexAsync(command?: string): Promise<CodexProbe>;
108
131
  export declare function isSupportedCodexVersion(version: string): boolean;
132
+ export {};
@@ -36,6 +36,25 @@ export function codexSessionControl(input) {
36
36
  capabilities: ['history.read', 'turn.start']
37
37
  };
38
38
  }
39
+ export class CodexAppServerRpcError extends Error {
40
+ code;
41
+ rpcMessage;
42
+ data;
43
+ constructor(payload) {
44
+ const rpcMessage = typeof payload.message === 'string' ? payload.message : 'Codex App Server request failed';
45
+ super(`CODEX_APP_SERVER_RPC_ERROR:${JSON.stringify(payload)}`);
46
+ this.name = 'CodexAppServerRpcError';
47
+ this.code = payload.code;
48
+ this.rpcMessage = rpcMessage;
49
+ this.data = payload.data;
50
+ }
51
+ }
52
+ export function isCodexRolloutMissingError(error) {
53
+ return (error instanceof CodexAppServerRpcError &&
54
+ error.code === -32600 &&
55
+ (/^no rollout found for thread id\b/i.test(error.rpcMessage) ||
56
+ /^thread not loaded:\s*/i.test(error.rpcMessage)));
57
+ }
39
58
  /**
40
59
  * Local JSON-RPC client for a Node-supervised Codex App Server. Linux uses a
41
60
  * private Unix socket by default; Windows uses stdio. Neither is exposed to
@@ -48,8 +67,12 @@ export class CodexAppServerClient {
48
67
  socketPath;
49
68
  nextId = 1;
50
69
  pending = new Map();
70
+ pendingDrainWaiters = new Set();
51
71
  notifications = new Set();
52
72
  environment = {};
73
+ starting;
74
+ restarting;
75
+ lifecycleRevision = 0;
53
76
  constructor(options = {}) {
54
77
  this.options = options;
55
78
  }
@@ -63,9 +86,35 @@ export class CodexAppServerClient {
63
86
  throw new Error('CODEX_APP_SERVER_ALREADY_STARTED');
64
87
  this.environment = {};
65
88
  }
66
- async start() {
89
+ start() {
90
+ if (this.restarting !== undefined)
91
+ return this.restarting;
92
+ if (this.starting !== undefined)
93
+ return this.starting;
67
94
  if (this.child !== undefined)
68
- return;
95
+ return Promise.resolve();
96
+ const starting = this.startManaged();
97
+ this.starting = starting;
98
+ const clearStarting = () => {
99
+ if (this.starting === starting)
100
+ this.starting = undefined;
101
+ };
102
+ void starting.then(clearStarting, clearStarting);
103
+ return starting;
104
+ }
105
+ restart() {
106
+ if (this.restarting !== undefined)
107
+ return this.restarting;
108
+ const restarting = this.restartManaged(this.lifecycleRevision);
109
+ this.restarting = restarting;
110
+ const clearRestarting = () => {
111
+ if (this.restarting === restarting)
112
+ this.restarting = undefined;
113
+ };
114
+ void restarting.then(clearRestarting, clearRestarting);
115
+ return restarting;
116
+ }
117
+ async startManaged() {
69
118
  const command = this.options.command ?? 'codex';
70
119
  const mode = this.transportMode();
71
120
  this.socketPath =
@@ -105,18 +154,39 @@ export class CodexAppServerClient {
105
154
  this.transport = socket ?? stdioTransport(child);
106
155
  const lines = createInterface({ input: socket ?? child.stdout });
107
156
  lines.on('line', (line) => this.handleLine(line));
108
- child.once('error', (error) => this.failAll(new Error(`CODEX_APP_SERVER_START_FAILED:${error.message}`)));
157
+ const managedChild = child;
158
+ child.once('error', (error) => {
159
+ if (this.child !== managedChild)
160
+ return;
161
+ this.failAll(new Error(`CODEX_APP_SERVER_START_FAILED:${error.message}`));
162
+ });
109
163
  child.once('exit', () => {
164
+ if (this.child !== managedChild)
165
+ return;
110
166
  this.child = undefined;
111
167
  this.failAll(new Error('CODEX_APP_SERVER_EXITED'));
112
168
  });
113
- await this.request('initialize', {
169
+ await this.requestDirect('initialize', {
114
170
  clientInfo: { name: 'codex_vscode', title: 'MyAgentRoam', version: '0.1.4' },
115
171
  capabilities: { experimentalApi: true }
116
172
  });
117
173
  this.notify('initialized', {});
118
174
  }
175
+ async restartManaged(lifecycleRevision) {
176
+ await Promise.resolve();
177
+ if (this.starting !== undefined)
178
+ await this.starting;
179
+ await this.waitForPendingRequests();
180
+ if (this.lifecycleRevision !== lifecycleRevision)
181
+ throw new Error('CODEX_APP_SERVER_STOPPED');
182
+ this.stopManaged();
183
+ await this.startManaged();
184
+ }
119
185
  stop() {
186
+ this.lifecycleRevision += 1;
187
+ this.stopManaged();
188
+ }
189
+ stopManaged() {
120
190
  this.transport?.destroy();
121
191
  this.transport = undefined;
122
192
  this.child?.kill();
@@ -131,6 +201,13 @@ export class CodexAppServerClient {
131
201
  return () => this.notifications.delete(listener);
132
202
  }
133
203
  async request(method, params) {
204
+ if (this.restarting !== undefined)
205
+ await this.restarting;
206
+ else if (this.starting !== undefined)
207
+ await this.starting;
208
+ return this.requestDirect(method, params);
209
+ }
210
+ requestDirect(method, params) {
134
211
  const transport = this.transport;
135
212
  if (transport === undefined || !transport.writable)
136
213
  throw new Error('CODEX_APP_SERVER_UNAVAILABLE');
@@ -139,6 +216,7 @@ export class CodexAppServerClient {
139
216
  const result = new Promise((resolve, reject) => {
140
217
  const timer = setTimeout(() => {
141
218
  this.pending.delete(id);
219
+ this.resolvePendingDrain();
142
220
  reject(new Error('CODEX_APP_SERVER_TIMEOUT'));
143
221
  }, timeoutMs);
144
222
  this.pending.set(id, { resolve, reject, timer });
@@ -146,6 +224,18 @@ export class CodexAppServerClient {
146
224
  transport.write(`${JSON.stringify({ method, id, params })}\n`);
147
225
  return result;
148
226
  }
227
+ waitForPendingRequests() {
228
+ if (this.pending.size === 0)
229
+ return Promise.resolve();
230
+ return new Promise((resolve) => this.pendingDrainWaiters.add(resolve));
231
+ }
232
+ resolvePendingDrain() {
233
+ if (this.pending.size !== 0)
234
+ return;
235
+ for (const resolve of this.pendingDrainWaiters)
236
+ resolve();
237
+ this.pendingDrainWaiters.clear();
238
+ }
149
239
  notify(method, params) {
150
240
  const transport = this.transport;
151
241
  if (transport === undefined || !transport.writable)
@@ -233,8 +323,9 @@ export class CodexAppServerClient {
233
323
  return;
234
324
  clearTimeout(pending.timer);
235
325
  this.pending.delete(message.id);
326
+ this.resolvePendingDrain();
236
327
  if (message.error !== undefined) {
237
- pending.reject(new Error(`CODEX_APP_SERVER_RPC_ERROR:${JSON.stringify(message.error)}`));
328
+ pending.reject(new CodexAppServerRpcError(isRecord(message.error) ? message.error : { message: String(message.error) }));
238
329
  }
239
330
  else {
240
331
  pending.resolve(message.result);
@@ -252,6 +343,7 @@ export class CodexAppServerClient {
252
343
  pending.reject(error);
253
344
  this.pending.delete(id);
254
345
  }
346
+ this.resolvePendingDrain();
255
347
  }
256
348
  transportMode() {
257
349
  const requested = this.options.transport ?? 'auto';
@@ -371,6 +463,9 @@ function defaultSpawn(command, args, options) {
371
463
  ...(options.env === undefined ? {} : { env: options.env })
372
464
  });
373
465
  }
466
+ function isRecord(value) {
467
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
468
+ }
374
469
  function stdioTransport(child) {
375
470
  return {
376
471
  get writable() {
package/dist/connector.js CHANGED
@@ -43,7 +43,7 @@ import { SessionQueryService } from './service/session-query-service.js';
43
43
  import { WorkspaceQueueWorkbenchService } from './service/workspace-queue-workbench-service.js';
44
44
  import { SessionIdentityService } from './service/session-identity-service.js';
45
45
  import { NativeSessionProjectionService } from './service/native-session-projection-service.js';
46
- import { ConversationHistoryService, hasManagedActiveRun } from './service/conversation-history-service.js';
46
+ import { ConversationHistoryService, convergeConversationWatchPage, hasManagedActiveRun } from './service/conversation-history-service.js';
47
47
  import { ConversationSegmentService, conversationSegmentIdentityForItem, conversationSegments } from './service/conversation-segment-service.js';
48
48
  import { RunnerInteractionService } from './service/runner-interaction-service.js';
49
49
  import { SessionLifecycleService } from './service/session-lifecycle-service.js';
@@ -197,9 +197,13 @@ export class NodeConnector {
197
197
  refreshActivity: (session) => this.conversationHistoryService.refreshExternalActivity(session),
198
198
  present: (session) => this.sessionPresentationService.present(session),
199
199
  readPage: (session, limit) => this.conversationHistoryService.read(session, { limit }),
200
+ convergePage: convergeConversationWatchPage,
200
201
  projectInitialPage: (session, page, _unit, limit) => this.conversationSegmentService.projectInitial(session, page, limit),
201
202
  emitSession: (session) => this.emitWorkbenchEvent('session', { session }),
202
- emitTurn: (turn) => this.emitConversationTurn(turn)
203
+ emitPage: (session, page) => this.emitWorkbenchEvent('conversation', {
204
+ sessionId: session.id,
205
+ snapshot: this.conversationSegmentService.projectInitial(session, page, 10)
206
+ })
203
207
  });
204
208
  constructor(options = {}) {
205
209
  this.config = options.config;
@@ -253,7 +257,7 @@ export class NodeConnector {
253
257
  interrupted: (runId) => this.runState.interruptRequested.has(runId),
254
258
  parseAttachments: parseRunnerImageAttachments,
255
259
  materialize: (runId, attachments) => this.runAttachmentService.materializeCodex(runId, attachments),
256
- completeCancelled: (runId, cwd) => this.runnerInteractionService.completeCancelledRun(runId, cwd),
260
+ completeCancelled: (runId) => this.runnerInteractionService.completeCancelledRun(runId),
257
261
  promoteSession: (sessionId, nativeSessionId) => this.sessionIdentityService.promote(sessionId, nativeSessionId),
258
262
  send: (type, payload) => this.send(type, payload),
259
263
  emit: (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status),
@@ -262,7 +266,6 @@ export class NodeConnector {
262
266
  publishApproval: (input) => this.runEventBridge.publishApproval(input),
263
267
  emitItem: (runId, item) => this.emitConversationItem(runId, item),
264
268
  appendImages: (runId, images) => this.runAttachmentService.append(runId, images.filter(isRunnerImageAttachment)),
265
- captureSnapshot: (runId, cwd) => this.workspaceCoordinator.captureRunSnapshot(runId, cwd),
266
269
  syncTitle: (sessionId, nativeSessionId, cwd) => this.managedRunnerSessions.refreshTitle(sessionId, nativeSessionId, cwd),
267
270
  commandState: (sessionId, commandId) => this.commandStates.get(sessionId, commandId),
268
271
  setCommandState: (sessionId, state) => this.commandStates.set(sessionId, state),
@@ -285,10 +288,16 @@ export class NodeConnector {
285
288
  emit: (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status),
286
289
  emitItem: (runId, item) => this.emitConversationItem(runId, item),
287
290
  adopt: (input) => this.runtime.adoptRun(input),
291
+ run: (runId) => {
292
+ const run = this.runtime.getRun(runId);
293
+ if (run === undefined)
294
+ throw new Error('RUN_NOT_FOUND');
295
+ return run;
296
+ },
297
+ hasActiveSessionLease: (sessionId) => this.runtime.hasActiveSessionLease(sessionId),
288
298
  promote: (sessionId, nativeSessionId) => this.sessionIdentityService.promote(sessionId, nativeSessionId),
289
299
  rememberNative: (nativeSessionId, sessionId) => this.managedRunnerSessions.remember(nativeSessionId, sessionId),
290
300
  send: (type, payload) => this.send(type, payload),
291
- captureSnapshot: (runId, cwd) => this.workspaceCoordinator.captureRunSnapshot(runId, cwd),
292
301
  createApproval: (input) => this.runtime.createApproval(input),
293
302
  expireApproval: (approvalId) => {
294
303
  this.runtime.expireApproval(approvalId);
@@ -308,8 +317,7 @@ export class NodeConnector {
308
317
  run: (runId) => this.runtime.getRun(runId),
309
318
  hasActiveSessionLease: (sessionId) => this.runtime.hasActiveSessionLease(sessionId),
310
319
  session: (sessionId) => this.runtime.getAgentSession(sessionId),
311
- completeCancelled: (runId, cwd) => this.runnerInteractionService.completeCancelledRun(runId, cwd),
312
- captureSnapshot: (runId, cwd) => this.workspaceCoordinator.captureRunSnapshot(runId, cwd),
320
+ completeCancelled: (runId) => this.runnerInteractionService.completeCancelledRun(runId),
313
321
  requestUserInput: (runId, input, toolUseId) => this.runnerInteractionService.requestClaudeUserInput(runId, input, toolUseId),
314
322
  requestApproval: (runId, toolName, input) => this.runnerInteractionService.requestClaudeApproval(runId, toolName, input),
315
323
  rememberNative: (nativeSessionId, sessionId) => this.managedRunnerSessions.remember(nativeSessionId, sessionId),
@@ -340,7 +348,6 @@ export class NodeConnector {
340
348
  runners: this.runners,
341
349
  emitRun: (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status),
342
350
  emitItem: (runId, item) => this.emitConversationItem(runId, item),
343
- captureSnapshot: (runId, cwd) => this.workspaceCoordinator.captureRunSnapshot(runId, cwd),
344
351
  approvalTimeoutMs: this.claudeApprovalTimeoutMs,
345
352
  publishApproval: (input) => this.runEventBridge.publishApproval(input)
346
353
  });
@@ -391,6 +398,7 @@ export class NodeConnector {
391
398
  respondUserInput: (runId, requestId, answers) => this.runnerInteractionService.respondUserInput(runId, requestId, answers),
392
399
  deleteQueuedStart: (runId) => this.workspaceQueueWorkbenchService.remove(runId),
393
400
  clearImages: (runId) => this.runAttachmentService.clear(runId),
401
+ cleanupAttachments: (runId) => this.runAttachmentService.cleanup(runId),
394
402
  emitQueue: (workspaceId, sessionId) => this.runEventBridge.emitQueue(workspaceId, sessionId),
395
403
  emitRun: (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status),
396
404
  control: (operation, payload) => this.controlMessages.handle(JSON.stringify(createEnvelope(operation, payload))),
@@ -411,6 +419,7 @@ export class NodeConnector {
411
419
  runtime: this.runtime,
412
420
  emit: (workspaceId, sessionId) => this.runEventBridge.emitQueue(workspaceId, sessionId),
413
421
  clearImages: (runId) => this.runAttachmentService.clear(runId),
422
+ cleanupAttachments: (runId) => this.runAttachmentService.cleanup(runId),
414
423
  emitRun: (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status),
415
424
  emitConversation: (turn) => this.emitConversationTurn(turn),
416
425
  control: (operation, payload) => this.controlMessages.handle(JSON.stringify(createEnvelope(operation, payload))),
@@ -565,7 +574,6 @@ export class NodeConnector {
565
574
  state: this.runState,
566
575
  emitRun: (runId, type, payload, status) => this.runEventBridge.emitRun(runId, type, payload, status),
567
576
  publishApproval: (input) => this.runEventBridge.publishApproval(input),
568
- captureSnapshot: (runId, cwd) => this.workspaceCoordinator.captureRunSnapshot(runId, cwd),
569
577
  disconnect: () => this.controlChannel.close(4010, 'FAKE_RUNNER_DISCONNECT')
570
578
  });
571
579
  this.fakeRunner?.bindControl({
@@ -620,14 +628,13 @@ export class NodeConnector {
620
628
  close: (code, reason) => this.controlChannel.close(code, reason),
621
629
  inspectWorkspace: (envelope) => this.workspaceCoordinator.inspect(envelope),
622
630
  handleNodeRequest: (envelope) => this.nodeRequests.handle(envelope),
623
- acknowledgeWorkbenchEvent: (replyTo) => this.workbenchEventService.acknowledge(replyTo),
624
631
  reconcileRuns: (envelope) => this.runEventBridge.reconcile(envelope),
625
632
  runners: this.runners,
626
633
  deliverSessionChannelMessage: (envelope) => this.runnerChannelMessages.handle(envelope),
627
634
  completeUncontrolledCancellation: (runId) => {
628
635
  const run = this.runtime.getRun(runId);
629
636
  if (run?.status === 'CANCELLING')
630
- this.runnerInteractionService.completeCancelledRun(runId, undefined);
637
+ this.runnerInteractionService.completeCancelledRun(runId);
631
638
  },
632
639
  send: (type, payload, replyTo) => this.send(type, payload, replyTo)
633
640
  });
@@ -640,7 +647,6 @@ export class NodeConnector {
640
647
  this.workspaceCoordinator = new NodeWorkspaceCoordinator({
641
648
  config: () => this.config,
642
649
  database: () => this.database,
643
- runtime: this.runtime,
644
650
  watches: this.workspaceWatchService,
645
651
  sessions: this.workspaceSessionService,
646
652
  changes: this.workspaceChangeService,
@@ -672,7 +678,6 @@ export class NodeConnector {
672
678
  }),
673
679
  control: this.controlChannel,
674
680
  terminal: this.terminalChannel,
675
- events: this.workbenchEventService,
676
681
  metrics: this.metrics,
677
682
  heartbeatMs: HEARTBEAT_MS,
678
683
  refreshMs: CAPABILITY_REFRESH_MS
@@ -701,7 +706,6 @@ export class NodeConnector {
701
706
  });
702
707
  this.workspaceWorkbenchService = new WorkspaceWorkbenchService({
703
708
  requireWorkspace: (workspaceId) => this.workspaceCoordinator.require(workspaceId),
704
- runtime: this.runtime,
705
709
  watch: this.workspaceWatchService,
706
710
  files: this.workspaceFileService,
707
711
  uploads: this.workspaceUploadService,
@@ -715,7 +719,6 @@ export class NodeConnector {
715
719
  this.operationRouter.registerAll(this.workspaceWorkbenchService.operations());
716
720
  this.operationRouter.registerAll(this.runWorkbenchService.operations());
717
721
  this.operationRouter.registerAll(this.nativeSessionWatchService.operations());
718
- this.operationRouter.registerAll(this.workbenchEventService.operations());
719
722
  this.operationRouter.registerAll(this.sessionQueryService.operations());
720
723
  this.operationRouter.registerAll(this.sessionLifecycleService.operations());
721
724
  this.operationRouter.registerAll(this.sessionMessageService.operations());
@@ -7,20 +7,23 @@ interface WritableControlSocket {
7
7
  /** Keeps control responses responsive while reliable Workbench events are busy. */
8
8
  export declare class ControlOutboundScheduler {
9
9
  private readonly socket;
10
+ private readonly onExpired;
10
11
  private readonly high;
11
12
  private readonly normal;
12
13
  private scheduled;
13
14
  private retryTimer;
15
+ private expiryTimer;
14
16
  private backpressureStartedAt;
15
17
  private backpressureLogged;
16
18
  private maximumHighDepth;
17
19
  private maximumNormalDepth;
18
- constructor(socket: () => WritableControlSocket | undefined);
20
+ constructor(socket: () => WritableControlSocket | undefined, onExpired?: () => void);
19
21
  enqueue(envelope: NodeEnvelope, priority: 'high' | 'normal'): void;
20
22
  reset(): void;
21
23
  private schedule;
22
24
  private flush;
23
25
  private waitForBackpressure;
24
26
  private finishBackpressure;
27
+ private scheduleExpiry;
25
28
  }
26
29
  export {};
@@ -4,19 +4,23 @@ const MAX_BYTES_PER_TURN = 256 * 1024;
4
4
  const SOCKET_NORMAL_DIRECT_WATER_BYTES = 256 * 1024;
5
5
  const SOCKET_HIGH_WATER_BYTES = 1024 * 1024;
6
6
  const BACKPRESSURE_RETRY_MS = 5;
7
+ const OUTBOUND_QUEUE_TTL_MS = 30_000;
7
8
  /** Keeps control responses responsive while reliable Workbench events are busy. */
8
9
  export class ControlOutboundScheduler {
9
10
  socket;
11
+ onExpired;
10
12
  high = [];
11
13
  normal = [];
12
14
  scheduled = false;
13
15
  retryTimer;
16
+ expiryTimer;
14
17
  backpressureStartedAt;
15
18
  backpressureLogged = false;
16
19
  maximumHighDepth = 0;
17
20
  maximumNormalDepth = 0;
18
- constructor(socket) {
21
+ constructor(socket, onExpired = () => undefined) {
19
22
  this.socket = socket;
23
+ this.onExpired = onExpired;
20
24
  }
21
25
  enqueue(envelope, priority) {
22
26
  const socket = this.socket();
@@ -29,9 +33,13 @@ export class ControlOutboundScheduler {
29
33
  socket.send(JSON.stringify(envelope));
30
34
  return;
31
35
  }
32
- (priority === 'high' ? this.high : this.normal).push(envelope);
36
+ (priority === 'high' ? this.high : this.normal).push({
37
+ envelope,
38
+ expiresAt: Date.now() + OUTBOUND_QUEUE_TTL_MS
39
+ });
33
40
  this.maximumHighDepth = Math.max(this.maximumHighDepth, this.high.length);
34
41
  this.maximumNormalDepth = Math.max(this.maximumNormalDepth, this.normal.length);
42
+ this.scheduleExpiry();
35
43
  this.schedule();
36
44
  }
37
45
  reset() {
@@ -41,6 +49,9 @@ export class ControlOutboundScheduler {
41
49
  if (this.retryTimer !== undefined)
42
50
  clearTimeout(this.retryTimer);
43
51
  this.retryTimer = undefined;
52
+ if (this.expiryTimer !== undefined)
53
+ clearTimeout(this.expiryTimer);
54
+ this.expiryTimer = undefined;
44
55
  this.finishBackpressure('reset');
45
56
  }
46
57
  schedule() {
@@ -62,8 +73,8 @@ export class ControlOutboundScheduler {
62
73
  let bytes = 0;
63
74
  while (frames < MAX_FRAMES_PER_TURN) {
64
75
  const queue = this.high.length > 0 ? this.high : this.normal;
65
- const envelope = queue[0];
66
- if (envelope === undefined)
76
+ const queued = queue[0];
77
+ if (queued === undefined)
67
78
  break;
68
79
  // Reliable Workbench events may be replayed in a large burst after a
69
80
  // reconnect. Stop normal traffic early so a later HTTP/control response
@@ -73,7 +84,7 @@ export class ControlOutboundScheduler {
73
84
  this.waitForBackpressure(socket, 'normal');
74
85
  return;
75
86
  }
76
- const serialized = JSON.stringify(envelope);
87
+ const serialized = JSON.stringify(queued.envelope);
77
88
  const size = Buffer.byteLength(serialized);
78
89
  if (frames > 0 && bytes + size > MAX_BYTES_PER_TURN)
79
90
  break;
@@ -86,8 +97,12 @@ export class ControlOutboundScheduler {
86
97
  }
87
98
  if (this.high.length > 0 || this.normal.length > 0)
88
99
  this.schedule();
89
- else
100
+ else {
101
+ if (this.expiryTimer !== undefined)
102
+ clearTimeout(this.expiryTimer);
103
+ this.expiryTimer = undefined;
90
104
  this.finishBackpressure('drained');
105
+ }
91
106
  }
92
107
  waitForBackpressure(socket, level) {
93
108
  this.backpressureStartedAt ??= Date.now();
@@ -118,4 +133,22 @@ export class ControlOutboundScheduler {
118
133
  this.maximumHighDepth = 0;
119
134
  this.maximumNormalDepth = 0;
120
135
  }
136
+ scheduleExpiry() {
137
+ if (this.expiryTimer !== undefined)
138
+ return;
139
+ const expiresAt = Math.min(this.high[0]?.expiresAt ?? Number.POSITIVE_INFINITY, this.normal[0]?.expiresAt ?? Number.POSITIVE_INFINITY);
140
+ if (!Number.isFinite(expiresAt))
141
+ return;
142
+ this.expiryTimer = setTimeout(() => {
143
+ this.expiryTimer = undefined;
144
+ if ((this.high[0]?.expiresAt ?? Number.POSITIVE_INFINITY) > Date.now() &&
145
+ (this.normal[0]?.expiresAt ?? Number.POSITIVE_INFINITY) > Date.now()) {
146
+ this.scheduleExpiry();
147
+ return;
148
+ }
149
+ this.reset();
150
+ this.onExpired();
151
+ }, Math.max(0, expiresAt - Date.now()));
152
+ this.expiryTimer.unref();
153
+ }
121
154
  }
@@ -67,6 +67,8 @@ export interface NodeAgentRun {
67
67
  readonly recoveryState: RunRecoveryState;
68
68
  readonly version: number;
69
69
  readonly clientMessageId: string;
70
+ /** Server-authenticated user that submitted this Run. Internal lifecycle metadata only. */
71
+ readonly initiatedByUserId?: number;
70
72
  readonly createdAt: number;
71
73
  readonly updatedAt: number;
72
74
  }
@@ -80,13 +82,6 @@ export interface NodeRunEvent {
80
82
  readonly status?: AgentRunStatus;
81
83
  readonly createdAt: number;
82
84
  }
83
- export interface WorkspaceActivityEvent {
84
- readonly id: number;
85
- readonly workspaceId: string;
86
- readonly eventType: string;
87
- readonly payload: unknown;
88
- readonly createdAt: number;
89
- }
90
85
  export interface ConversationPage {
91
86
  readonly turns: readonly NodeConversationTurn[];
92
87
  readonly nextCursor: string | null;
@@ -132,10 +127,6 @@ export interface NodeMessageAttachmentInput {
132
127
  readonly mime: string;
133
128
  readonly dataBase64: string;
134
129
  }
135
- export interface WorkspaceActivityPage {
136
- readonly events: readonly WorkspaceActivityEvent[];
137
- readonly nextCursor: string | null;
138
- }
139
130
  export interface NodeApproval {
140
131
  readonly id: string;
141
132
  readonly runId: string;
@@ -288,11 +279,6 @@ export declare class NodeDatabase {
288
279
  readonly cursor?: string;
289
280
  readonly limit?: number;
290
281
  }): ConversationItemPage;
291
- listWorkspaceActivity(input: {
292
- readonly workspaceId: string;
293
- readonly cursor?: string;
294
- readonly limit?: number;
295
- }): WorkspaceActivityPage;
296
282
  createApproval(input: {
297
283
  readonly runId: string;
298
284
  readonly approvalId: string;
@@ -307,13 +293,10 @@ export declare class NodeDatabase {
307
293
  decideApproval(id: string, decision: 'APPROVED' | 'REJECTED'): NodeApproval;
308
294
  listNonterminalRuns(): readonly NodeAgentRun[];
309
295
  lastRunSequence(runId: string): number;
310
- saveSnapshot(runId: string, snapshot: unknown): void;
311
- getSnapshot(runId: string): unknown | undefined;
312
296
  markActiveRunsSuspect(): void;
313
297
  hasActiveWorkspaceLease(workspaceId: string): boolean;
314
298
  reconcileRun(runId: string, active: boolean): void;
315
299
  private releaseLease;
316
- private recordWorkspaceActivity;
317
300
  private recordConversationItemChange;
318
301
  private projectApproval;
319
302
  private readConversationTurn;