@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.
- package/dist/codex-app-server.d.ts +24 -0
- package/dist/codex-app-server.js +100 -5
- package/dist/connector.js +18 -15
- package/dist/control-outbound-scheduler.d.ts +4 -1
- package/dist/control-outbound-scheduler.js +39 -6
- package/dist/database.d.ts +2 -19
- package/dist/database.js +1 -62
- package/dist/event-buffer.d.ts +2 -5
- package/dist/event-buffer.js +3 -12
- package/dist/native-session-history.js +33 -12
- package/dist/runner/claude/managed-run-controller.d.ts +0 -1
- package/dist/runner/claude/managed-run-controller.js +17 -12
- package/dist/runner/codex/conversation-parser.d.ts +1 -1
- package/dist/runner/codex/conversation-parser.js +3 -0
- package/dist/runner/codex/managed-run-controller.d.ts +11 -2
- package/dist/runner/codex/managed-run-controller.js +79 -24
- package/dist/runner/codex-runner.d.ts +3 -0
- package/dist/runner/codex-runner.js +42 -3
- package/dist/runner/opencode/conversation-parser.js +1 -1
- package/dist/runner/opencode/managed-run-controller.d.ts +7 -2
- package/dist/runner/opencode/managed-run-controller.js +72 -14
- package/dist/runner/opencode-runner.d.ts +3 -0
- package/dist/runner/opencode-runner.js +25 -0
- package/dist/runner-profiles.js +10 -6
- package/dist/runtime-state.d.ts +7 -12
- package/dist/runtime-state.js +43 -53
- package/dist/service/attachment-domain-state.d.ts +2 -0
- package/dist/service/attachment-domain-state.js +2 -0
- package/dist/service/conversation-history-service.d.ts +8 -0
- package/dist/service/conversation-history-service.js +181 -9
- package/dist/service/conversation-segment-service.js +17 -10
- package/dist/service/external-session-resume-service.js +9 -0
- package/dist/service/fake-managed-run-service.d.ts +0 -1
- package/dist/service/fake-managed-run-service.js +0 -2
- package/dist/service/native-session-projection-service.js +5 -0
- package/dist/service/native-session-watch-service.d.ts +8 -2
- package/dist/service/native-session-watch-service.js +28 -10
- package/dist/service/node-connection-lifecycle-service.d.ts +0 -2
- package/dist/service/node-connection-lifecycle-service.js +0 -1
- package/dist/service/node-control-channel.js +1 -1
- package/dist/service/node-control-message-service.d.ts +0 -1
- package/dist/service/node-control-message-service.js +9 -6
- package/dist/service/node-request-service.js +2 -1
- package/dist/service/node-workspace-coordinator.d.ts +1 -4
- package/dist/service/node-workspace-coordinator.js +2 -12
- package/dist/service/run-attachment-service.js +30 -6
- package/dist/service/run-event-service.d.ts +1 -0
- package/dist/service/run-event-service.js +80 -0
- package/dist/service/run-workbench-service.d.ts +1 -1
- package/dist/service/run-workbench-service.js +1 -5
- package/dist/service/runner-interaction-service.d.ts +1 -2
- package/dist/service/runner-interaction-service.js +1 -4
- package/dist/service/session-context-service.d.ts +1 -0
- package/dist/service/session-context-service.js +14 -0
- package/dist/service/session-domain-state.d.ts +1 -0
- package/dist/service/session-domain-state.js +1 -0
- package/dist/service/session-lifecycle-service.js +4 -12
- package/dist/service/session-message-service.d.ts +1 -0
- package/dist/service/session-message-service.js +109 -85
- package/dist/service/skill-directory-service.d.ts +3 -1
- package/dist/service/skill-directory-service.js +28 -4
- package/dist/service/skill-node-operation-service.d.ts +1 -1
- package/dist/service/skill-node-operation-service.js +4 -2
- package/dist/service/workbench-event-service.d.ts +1 -13
- package/dist/service/workbench-event-service.js +2 -36
- package/dist/service/workbench-manifest-service.d.ts +0 -1
- package/dist/service/workbench-manifest-service.js +0 -1
- package/dist/service/workspace-change-service.js +1 -1
- package/dist/service/workspace-domain-state.d.ts +1 -0
- package/dist/service/workspace-queue-workbench-service.d.ts +13 -2
- package/dist/service/workspace-queue-workbench-service.js +8 -2
- package/dist/service/workspace-upload-service.d.ts +2 -0
- package/dist/service/workspace-upload-service.js +28 -6
- package/dist/service/workspace-workbench-service.d.ts +0 -3
- package/dist/service/workspace-workbench-service.js +0 -17
- package/dist/util/runner-error.d.ts +6 -0
- package/dist/util/runner-error.js +52 -0
- package/dist/util/runner-native-session-parsers.d.ts +2 -1
- package/dist/util/runner-native-session-parsers.js +63 -15
- package/dist/workspace.js +33 -8
- 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 {};
|
package/dist/codex-app-server.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
66
|
-
if (
|
|
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
|
}
|
package/dist/database.d.ts
CHANGED
|
@@ -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;
|