@myagentroam/node 0.9.4 → 0.9.6

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 (61) hide show
  1. package/dist/claude-agent-sdk.d.ts +1 -0
  2. package/dist/claude-agent-sdk.js +9 -0
  3. package/dist/codex-app-server.d.ts +1 -0
  4. package/dist/codex-app-server.js +4 -2
  5. package/dist/connector.d.ts +1 -0
  6. package/dist/connector.js +16 -11
  7. package/dist/native-jsonl-reader.d.ts +21 -0
  8. package/dist/native-jsonl-reader.js +89 -0
  9. package/dist/native-session-history.d.ts +20 -3
  10. package/dist/native-session-history.js +245 -38
  11. package/dist/opencode-server.d.ts +1 -0
  12. package/dist/opencode-server.js +1 -0
  13. package/dist/runner/abstract-runner.d.ts +11 -2
  14. package/dist/runner/abstract-runner.js +4 -0
  15. package/dist/runner/claude/managed-run-controller.js +3 -0
  16. package/dist/runner/claude-code-runner.d.ts +2 -1
  17. package/dist/runner/claude-code-runner.js +4 -0
  18. package/dist/runner/codex/managed-run-controller.js +6 -0
  19. package/dist/runner/codex-runner.d.ts +2 -1
  20. package/dist/runner/codex-runner.js +13 -2
  21. package/dist/runner/opencode/managed-run-controller.js +4 -0
  22. package/dist/runner/opencode-runner.d.ts +10 -3
  23. package/dist/runner/opencode-runner.js +87 -14
  24. package/dist/runner/runner-registry.js +16 -1
  25. package/dist/runtime-command-detector.js +1 -6
  26. package/dist/runtime-state.js +1 -0
  27. package/dist/service/conversation-history-service.d.ts +6 -5
  28. package/dist/service/conversation-history-service.js +151 -55
  29. package/dist/service/external-session-resume-service.d.ts +2 -0
  30. package/dist/service/external-session-resume-service.js +45 -34
  31. package/dist/service/native-session-projection-service.d.ts +1 -0
  32. package/dist/service/native-session-projection-service.js +4 -0
  33. package/dist/service/native-session-watch-service.d.ts +18 -0
  34. package/dist/service/native-session-watch-service.js +232 -34
  35. package/dist/service/run-workbench-service.d.ts +2 -4
  36. package/dist/service/run-workbench-service.js +2 -7
  37. package/dist/service/session-command-service.js +1 -1
  38. package/dist/service/session-identity-service.d.ts +1 -1
  39. package/dist/service/session-identity-service.js +1 -1
  40. package/dist/service/session-lifecycle-service.js +3 -0
  41. package/dist/service/session-message-service.d.ts +0 -1
  42. package/dist/service/session-message-service.js +15 -23
  43. package/dist/service/skill-directory-service.d.ts +1 -0
  44. package/dist/service/skill-directory-service.js +40 -6
  45. package/dist/service/workspace-domain-state.d.ts +3 -0
  46. package/dist/service/workspace-domain-state.js +1 -0
  47. package/dist/service/workspace-file-service.d.ts +1 -0
  48. package/dist/service/workspace-file-service.js +14 -1
  49. package/dist/service/workspace-queue-workbench-service.d.ts +17 -0
  50. package/dist/service/workspace-queue-workbench-service.js +66 -6
  51. package/dist/service/workspace-watch-service.d.ts +7 -2
  52. package/dist/service/workspace-watch-service.js +32 -6
  53. package/dist/service/workspace-workbench-service.d.ts +14 -0
  54. package/dist/service/workspace-workbench-service.js +215 -16
  55. package/dist/util/personal-instructions.d.ts +2 -0
  56. package/dist/util/personal-instructions.js +31 -0
  57. package/dist/util/runner-native-session-parsers.d.ts +1 -0
  58. package/dist/util/runner-native-session-parsers.js +76 -12
  59. package/dist/workspace.d.ts +14 -8
  60. package/dist/workspace.js +65 -42
  61. package/package.json +2 -2
@@ -1,9 +1,13 @@
1
1
  const RESUME_FAILURE_TTL_MS = 30 * 60_000;
2
2
  export class ExternalSessionResumeService {
3
3
  options;
4
+ transitions = new Set();
4
5
  constructor(options) {
5
6
  this.options = options;
6
7
  }
8
+ transitioning(sessionId) {
9
+ return this.transitions.has(sessionId);
10
+ }
7
11
  async resume(external, force = false) {
8
12
  this.clearFailure(external.id);
9
13
  const priorId = this.options.state.resumedByDirectId.get(external.id);
@@ -15,40 +19,47 @@ export class ExternalSessionResumeService {
15
19
  this.setFailure(external.id, 'RUNNER_UNAVAILABLE');
16
20
  return undefined;
17
21
  }
18
- const result = await runner.resumeExternal(external, {
19
- available: this.options.available(external.runner),
20
- projectionFor: this.options.projectionFor,
21
- managedAliasFor: this.options.managedAliasFor,
22
- currentResumed: (sessionId) => {
23
- const resumedId = this.options.state.resumedByDirectId.get(sessionId);
24
- return resumedId === undefined
25
- ? undefined
26
- : this.options.runtime.getAgentSession(resumedId);
27
- },
28
- promote: (sessionId) => {
29
- this.options.runtime.setNativeControl(sessionId, 'MAR_MANAGED');
30
- return this.options.runtime.getAgentSession(sessionId);
31
- },
32
- ensureConfiguration: this.options.ensureConfiguration,
33
- create: (session) => {
34
- const configuration = this.options.configuration(session);
35
- return this.options.runtime.createAgentSession({
36
- workspaceId: session.workspaceId,
37
- runner: session.runner,
38
- ...(session.externalSessionId === null
39
- ? {}
40
- : { externalSessionId: session.externalSessionId }),
41
- nativeControl: 'MAR_MANAGED',
42
- cwd: session.cwd,
43
- model: configuration.model,
44
- effort: configuration.effort,
45
- access: configuration.access,
46
- ...(session.runnerTitle === null ? {} : { title: session.runnerTitle })
47
- });
48
- },
49
- workspacePath: this.options.workspacePath,
50
- rememberManagedNative: this.options.rememberNative
51
- }, force);
22
+ this.transitions.add(external.id);
23
+ let result;
24
+ try {
25
+ result = await runner.resumeExternal(external, {
26
+ available: this.options.available(external.runner),
27
+ projectionFor: this.options.projectionFor,
28
+ managedAliasFor: this.options.managedAliasFor,
29
+ currentResumed: (sessionId) => {
30
+ const resumedId = this.options.state.resumedByDirectId.get(sessionId);
31
+ return resumedId === undefined
32
+ ? undefined
33
+ : this.options.runtime.getAgentSession(resumedId);
34
+ },
35
+ promote: (sessionId) => {
36
+ this.options.runtime.setNativeControl(sessionId, 'MAR_MANAGED');
37
+ return this.options.runtime.getAgentSession(sessionId);
38
+ },
39
+ ensureConfiguration: this.options.ensureConfiguration,
40
+ create: (session) => {
41
+ const configuration = this.options.configuration(session);
42
+ return this.options.runtime.createAgentSession({
43
+ workspaceId: session.workspaceId,
44
+ runner: session.runner,
45
+ ...(session.externalSessionId === null
46
+ ? {}
47
+ : { externalSessionId: session.externalSessionId }),
48
+ nativeControl: 'MAR_MANAGED',
49
+ cwd: session.cwd,
50
+ model: configuration.model,
51
+ effort: configuration.effort,
52
+ access: configuration.access,
53
+ ...(session.runnerTitle === null ? {} : { title: session.runnerTitle })
54
+ });
55
+ },
56
+ workspacePath: this.options.workspacePath,
57
+ rememberManagedNative: this.options.rememberNative
58
+ }, force);
59
+ }
60
+ finally {
61
+ this.transitions.delete(external.id);
62
+ }
52
63
  if (result.activity !== undefined)
53
64
  this.options.state.externalActivity.set(external.id, result.activity);
54
65
  if (result.session !== undefined) {
@@ -32,6 +32,7 @@ export declare class NativeSessionProjectionService {
32
32
  })[]>;
33
33
  discoverWorkspace(workspaceId: string): Promise<readonly NodeAgentSession[]>;
34
34
  resolve(sessionId: string): Promise<NodeAgentSession | undefined>;
35
+ resolveCurrent(sessionId: string): Promise<NodeAgentSession | undefined>;
35
36
  createMetadataProjection(sessionId: string): Promise<NodeAgentSession | undefined>;
36
37
  resumedConfiguration(session: NodeAgentSession): RunnerDefaultConfiguration;
37
38
  ensureResumedConfiguration(session: NodeAgentSession): NodeAgentSession;
@@ -81,6 +81,10 @@ export class NativeSessionProjectionService {
81
81
  createdAt: 0
82
82
  };
83
83
  }
84
+ async resolveCurrent(sessionId) {
85
+ const direct = await this.resolve(sessionId);
86
+ return direct === undefined ? undefined : (this.projectionForNative(direct) ?? direct);
87
+ }
84
88
  async createMetadataProjection(sessionId) {
85
89
  const direct = await this.resolve(sessionId);
86
90
  if (direct === undefined || direct.externalSessionId === null)
@@ -6,6 +6,7 @@ export interface NativeSessionWatchOptions<TPage extends {
6
6
  }> {
7
7
  readonly resolve: (sessionId: string) => Promise<NodeAgentSession | undefined>;
8
8
  readonly managedActive: (session: NodeAgentSession) => boolean;
9
+ readonly suspended?: (sessionId: string) => boolean;
9
10
  readonly refreshActivity: (session: NodeAgentSession) => Promise<void>;
10
11
  readonly present: (session: NodeAgentSession) => unknown;
11
12
  readonly readPage: (session: NodeAgentSession, limit: number) => Promise<TPage>;
@@ -16,6 +17,9 @@ export interface NativeSessionWatchOptions<TPage extends {
16
17
  readonly projectInitialPage?: (session: NodeAgentSession, page: TPage, unit: 'SEGMENT', limit: number) => unknown;
17
18
  readonly emitSession: (session: unknown) => void;
18
19
  readonly emitPage: (session: NodeAgentSession, page: TPage) => void;
20
+ readonly nodeId?: () => string;
21
+ readonly nodeGeneration?: () => string;
22
+ readonly emitWatchEvent?: (payload: unknown) => void;
19
23
  }
20
24
  export declare class NativeSessionWatchService<TPage extends {
21
25
  readonly turns: readonly unknown[];
@@ -23,9 +27,12 @@ export declare class NativeSessionWatchService<TPage extends {
23
27
  }> {
24
28
  private readonly options;
25
29
  private readonly watches;
30
+ private readonly leases;
31
+ private snapshotSequence;
26
32
  constructor(options: NativeSessionWatchOptions<TPage>);
27
33
  operations(): Readonly<Record<string, NodeOperationHandler>>;
28
34
  private executeWatch;
35
+ private executeUnwatch;
29
36
  watch(session: NodeAgentSession): {
30
37
  readonly expiresAt: number;
31
38
  } | undefined;
@@ -41,4 +48,15 @@ export declare class NativeSessionWatchService<TPage extends {
41
48
  get size(): number;
42
49
  refresh(sessionId: string, limit?: number, emit?: boolean): Promise<TPage | undefined>;
43
50
  private performRefresh;
51
+ private sessionHash;
52
+ private ownerKey;
53
+ private accepted;
54
+ private initializeLease;
55
+ private removeLease;
56
+ private emitObservedPage;
57
+ private failSession;
58
+ private releaseSessionLeases;
59
+ private leaseTimer;
60
+ private latestLeaseExpiry;
61
+ private refreshTimer;
44
62
  }
@@ -7,12 +7,15 @@ const EMITTED_TURN_LIMIT = 10;
7
7
  export class NativeSessionWatchService {
8
8
  options;
9
9
  watches = new Map();
10
+ leases = new Map();
11
+ snapshotSequence = 0;
10
12
  constructor(options) {
11
13
  this.options = options;
12
14
  }
13
15
  operations() {
14
16
  return {
15
- 'session.watch': (data) => this.executeWatch(isPlainRecord(data) ? data : {})
17
+ 'session.watch': (data) => this.executeWatch(isPlainRecord(data) ? data : {}),
18
+ 'session.unwatch': (data) => this.executeUnwatch(isPlainRecord(data) ? data : {})
16
19
  };
17
20
  }
18
21
  async executeWatch(input) {
@@ -20,39 +23,75 @@ export class NativeSessionWatchService {
20
23
  if (input.unit === 'SEGMENT' && projectInitialPage === undefined)
21
24
  throw new Error('SESSION_INVALID');
22
25
  if (typeof input.sessionId !== 'string' ||
26
+ (input.workspaceId !== undefined && typeof input.workspaceId !== 'string') ||
27
+ typeof input.watchId !== 'string' ||
28
+ typeof input.workbenchConnectionId !== 'string' ||
23
29
  (input.mode !== 'initial' && input.mode !== 'renew') ||
24
30
  (input.unit !== undefined && input.unit !== 'SEGMENT'))
25
31
  throw new Error('SESSION_INVALID');
26
- const session = await this.options.resolve(input.sessionId);
27
- if (session === undefined || session.externalSessionId === null)
28
- throw new Error('SESSION_NOT_FOUND');
32
+ const ownerKey = this.ownerKey(input);
29
33
  if (input.mode === 'renew') {
30
- const watch = this.renew(session);
31
- if (watch === undefined)
34
+ const lease = this.leases.get(ownerKey);
35
+ if (lease === undefined ||
36
+ lease.watchId !== input.watchId ||
37
+ lease.sessionId !== input.sessionId ||
38
+ (input.workspaceId !== undefined && lease.workspaceId !== input.workspaceId))
39
+ throw new Error('SESSION_WATCH_NOT_FOUND');
40
+ if (lease.expiresAt <= Date.now()) {
41
+ this.removeLease(ownerKey);
32
42
  throw new Error('SESSION_WATCH_NOT_FOUND');
33
- return { renewed: true };
34
- }
35
- const watch = this.watch(session);
36
- const turnSnapshot = watch === undefined ? undefined : await this.refresh(session.id, INITIAL_TURN_LIMIT, false);
37
- if (watch !== undefined && turnSnapshot === undefined) {
38
- this.stop(session.id);
39
- throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
40
- }
41
- let snapshot = turnSnapshot;
42
- if (turnSnapshot !== undefined && input.unit === 'SEGMENT') {
43
- try {
44
- snapshot = projectInitialPage(session, turnSnapshot, 'SEGMENT', 10);
45
- }
46
- catch (error) {
47
- this.stop(session.id);
48
- throw error;
49
43
  }
44
+ lease.expiresAt = Date.now() + TTL_MS;
45
+ const observer = this.watches.get(lease.sessionId);
46
+ if (observer !== undefined)
47
+ observer.expiresAt = this.latestLeaseExpiry(lease.sessionId);
48
+ clearTimeout(lease.timer);
49
+ lease.timer = this.leaseTimer(ownerKey);
50
+ return this.accepted(lease);
50
51
  }
51
- return {
52
- session: this.options.present(session),
53
- watch: watch === undefined ? null : { expiresAt: watch.expiresAt },
54
- ...(snapshot === undefined ? {} : { snapshot })
52
+ const existing = this.leases.get(ownerKey);
53
+ const unit = input.unit === 'SEGMENT' ? 'SEGMENT' : 'TURN';
54
+ if (existing?.watchId === input.watchId &&
55
+ existing.sessionId === input.sessionId &&
56
+ (input.workspaceId === undefined || existing.workspaceId === input.workspaceId) &&
57
+ existing.unit === unit &&
58
+ existing.expiresAt > Date.now()) {
59
+ existing.expiresAt = Date.now() + TTL_MS;
60
+ clearTimeout(existing.timer);
61
+ existing.timer = this.leaseTimer(ownerKey);
62
+ return this.accepted(existing);
63
+ }
64
+ this.removeLease(ownerKey);
65
+ const lease = {
66
+ ownerKey,
67
+ sessionHash: this.sessionHash(input),
68
+ workbenchConnectionId: input.workbenchConnectionId,
69
+ watchId: input.watchId,
70
+ sessionId: input.sessionId,
71
+ unit,
72
+ nodeGeneration: this.options.nodeGeneration?.() ?? 'local-test-generation',
73
+ ...(typeof input.workspaceId === 'string' ? { workspaceId: input.workspaceId } : {}),
74
+ expiresAt: Date.now() + TTL_MS,
75
+ timer: undefined
55
76
  };
77
+ lease.timer = this.leaseTimer(ownerKey);
78
+ this.leases.set(ownerKey, lease);
79
+ const observer = this.watches.get(lease.sessionId);
80
+ if (observer !== undefined)
81
+ observer.expiresAt = this.latestLeaseExpiry(lease.sessionId);
82
+ void this.initializeLease(lease, projectInitialPage);
83
+ return this.accepted(lease);
84
+ }
85
+ executeUnwatch(input) {
86
+ if (typeof input.sessionId !== 'string' ||
87
+ typeof input.watchId !== 'string' ||
88
+ typeof input.workbenchConnectionId !== 'string')
89
+ throw new Error('SESSION_INVALID');
90
+ const ownerKey = this.ownerKey(input);
91
+ const lease = this.leases.get(ownerKey);
92
+ if (lease?.watchId === input.watchId && lease.sessionId === input.sessionId)
93
+ this.removeLease(ownerKey);
94
+ return { stopped: true };
56
95
  }
57
96
  watch(session) {
58
97
  if (this.options.managedActive(session)) {
@@ -75,8 +114,7 @@ export class NativeSessionWatchService {
75
114
  failures: 0,
76
115
  timer: undefined
77
116
  };
78
- watch.timer = setInterval(() => void this.refresh(session.id), INTERVAL_MS);
79
- watch.timer.unref();
117
+ watch.timer = this.refreshTimer(session.id);
80
118
  this.watches.set(session.id, watch);
81
119
  return { expiresAt: watch.expiresAt };
82
120
  }
@@ -97,13 +135,16 @@ export class NativeSessionWatchService {
97
135
  const watch = this.watches.get(sessionId);
98
136
  if (watch === undefined)
99
137
  return;
100
- clearInterval(watch.timer);
138
+ clearTimeout(watch.timer);
101
139
  this.watches.delete(sessionId);
102
140
  }
103
141
  clear() {
104
142
  for (const watch of this.watches.values())
105
- clearInterval(watch.timer);
143
+ clearTimeout(watch.timer);
106
144
  this.watches.clear();
145
+ for (const lease of this.leases.values())
146
+ clearTimeout(lease.timer);
147
+ this.leases.clear();
107
148
  }
108
149
  has(sessionId) {
109
150
  return this.watches.has(sessionId);
@@ -130,20 +171,27 @@ export class NativeSessionWatchService {
130
171
  return await refresh;
131
172
  }
132
173
  finally {
133
- if (this.watches.get(sessionId) === watch)
174
+ if (this.watches.get(sessionId) === watch) {
134
175
  watch.refresh = undefined;
176
+ clearTimeout(watch.timer);
177
+ watch.timer = this.refreshTimer(sessionId);
178
+ }
135
179
  }
136
180
  }
137
181
  async performRefresh(sessionId, watch, limit, emit) {
138
182
  try {
183
+ if (this.options.suspended?.(sessionId) === true)
184
+ return watch.page;
139
185
  const session = await this.options.resolve(sessionId);
140
186
  if (session === undefined ||
141
187
  session.externalSessionId === null ||
142
188
  this.options.managedActive(session)) {
143
- this.stop(sessionId);
189
+ this.releaseSessionLeases(sessionId);
144
190
  return undefined;
145
191
  }
146
192
  await this.options.refreshActivity(session);
193
+ if (this.options.suspended?.(sessionId) === true)
194
+ return watch.page;
147
195
  const presented = this.options.present(session);
148
196
  const sessionSignature = JSON.stringify(presented);
149
197
  if (sessionSignature !== watch.sessionSignature) {
@@ -172,14 +220,164 @@ export class NativeSessionWatchService {
172
220
  watch.signature = signature;
173
221
  watch.page = accepted;
174
222
  if (emit)
175
- this.options.emitPage(session, accepted);
223
+ this.emitObservedPage(session, accepted);
176
224
  return emit ? accepted : page;
177
225
  }
178
226
  catch {
179
227
  watch.failures += 1;
180
228
  if (watch.failures >= MAX_FAILURES)
181
- this.stop(sessionId);
229
+ this.failSession(sessionId);
182
230
  return undefined;
183
231
  }
184
232
  }
233
+ sessionHash(input) {
234
+ return typeof input.__workspaceWatchSessionHash === 'string'
235
+ ? input.__workspaceWatchSessionHash
236
+ : 'local-test';
237
+ }
238
+ ownerKey(input) {
239
+ return `${this.sessionHash(input)}:${input.workbenchConnectionId}`;
240
+ }
241
+ accepted(lease) {
242
+ return {
243
+ accepted: true,
244
+ watchId: lease.watchId,
245
+ nodeGeneration: lease.nodeGeneration,
246
+ expiresAt: lease.expiresAt
247
+ };
248
+ }
249
+ async initializeLease(lease, projectInitialPage) {
250
+ try {
251
+ const session = await this.options.resolve(lease.sessionId);
252
+ if (session === undefined || session.externalSessionId === null)
253
+ throw new Error('SESSION_NOT_FOUND');
254
+ if (lease.workspaceId !== undefined && session.workspaceId !== lease.workspaceId)
255
+ throw new Error('SESSION_WORKSPACE_MISMATCH');
256
+ if (this.leases.get(lease.ownerKey) !== lease)
257
+ return;
258
+ lease.workspaceId = session.workspaceId;
259
+ const watch = this.watch(session);
260
+ const page = watch === undefined ? undefined : await this.refresh(session.id, INITIAL_TURN_LIMIT, false);
261
+ if (page === undefined)
262
+ throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
263
+ const payload = lease.unit === 'SEGMENT' ? projectInitialPage(session, page, 'SEGMENT', 10) : page;
264
+ if (this.leases.get(lease.ownerKey) !== lease)
265
+ return;
266
+ this.options.emitWatchEvent?.({
267
+ type: 'watch.session.snapshot',
268
+ workbenchConnectionId: lease.workbenchConnectionId,
269
+ watchId: lease.watchId,
270
+ nodeGeneration: lease.nodeGeneration,
271
+ nodeId: this.options.nodeId?.() ?? 'local-test-node',
272
+ workspaceId: session.workspaceId,
273
+ sessionId: session.id,
274
+ revision: 1,
275
+ snapshotSequence: ++this.snapshotSequence,
276
+ expiresAt: lease.expiresAt,
277
+ payload
278
+ });
279
+ }
280
+ catch (error) {
281
+ if (this.leases.get(lease.ownerKey) !== lease)
282
+ return;
283
+ this.options.emitWatchEvent?.({
284
+ type: 'watch.failed',
285
+ workbenchConnectionId: lease.workbenchConnectionId,
286
+ watchType: 'session',
287
+ watchId: lease.watchId,
288
+ nodeGeneration: lease.nodeGeneration,
289
+ nodeId: this.options.nodeId?.() ?? 'local-test-node',
290
+ workspaceId: lease.workspaceId ?? 'unknown',
291
+ sessionId: lease.sessionId,
292
+ code: error instanceof Error && /^[A-Z][A-Z0-9_]+$/.test(error.message)
293
+ ? error.message
294
+ : 'SESSION_WATCH_SNAPSHOT_FAILED',
295
+ message: 'Session watch snapshot failed.'
296
+ });
297
+ this.removeLease(lease.ownerKey);
298
+ }
299
+ }
300
+ removeLease(ownerKey) {
301
+ const lease = this.leases.get(ownerKey);
302
+ if (lease === undefined)
303
+ return;
304
+ clearTimeout(lease.timer);
305
+ this.leases.delete(ownerKey);
306
+ const observer = this.watches.get(lease.sessionId);
307
+ const nextExpiry = this.latestLeaseExpiry(lease.sessionId);
308
+ if (nextExpiry === 0)
309
+ this.stop(lease.sessionId);
310
+ else if (observer !== undefined)
311
+ observer.expiresAt = nextExpiry;
312
+ }
313
+ emitObservedPage(session, page) {
314
+ const leases = [...this.leases.values()].filter((lease) => lease.sessionId === session.id);
315
+ if (leases.length === 0) {
316
+ this.options.emitPage(session, page);
317
+ return;
318
+ }
319
+ for (const lease of leases) {
320
+ const payload = lease.unit === 'SEGMENT'
321
+ ? this.options.projectInitialPage?.(session, page, 'SEGMENT', 10)
322
+ : page;
323
+ if (payload === undefined)
324
+ continue;
325
+ this.options.emitWatchEvent?.({
326
+ type: 'watch.session.snapshot',
327
+ workbenchConnectionId: lease.workbenchConnectionId,
328
+ watchId: lease.watchId,
329
+ nodeGeneration: lease.nodeGeneration,
330
+ nodeId: this.options.nodeId?.() ?? 'local-test-node',
331
+ workspaceId: session.workspaceId,
332
+ sessionId: session.id,
333
+ revision: 1,
334
+ snapshotSequence: ++this.snapshotSequence,
335
+ expiresAt: lease.expiresAt,
336
+ payload
337
+ });
338
+ }
339
+ }
340
+ failSession(sessionId) {
341
+ for (const lease of [...this.leases.values()]) {
342
+ if (lease.sessionId !== sessionId)
343
+ continue;
344
+ this.options.emitWatchEvent?.({
345
+ type: 'watch.failed',
346
+ workbenchConnectionId: lease.workbenchConnectionId,
347
+ watchType: 'session',
348
+ watchId: lease.watchId,
349
+ nodeGeneration: lease.nodeGeneration,
350
+ nodeId: this.options.nodeId?.() ?? 'local-test-node',
351
+ workspaceId: lease.workspaceId ?? 'unknown',
352
+ sessionId,
353
+ code: 'NATIVE_TRANSCRIPT_UNAVAILABLE',
354
+ message: 'Session watch snapshot failed.'
355
+ });
356
+ this.removeLease(lease.ownerKey);
357
+ }
358
+ this.stop(sessionId);
359
+ }
360
+ releaseSessionLeases(sessionId) {
361
+ for (const lease of [...this.leases.values()])
362
+ if (lease.sessionId === sessionId)
363
+ this.removeLease(lease.ownerKey);
364
+ this.stop(sessionId);
365
+ }
366
+ leaseTimer(ownerKey) {
367
+ const timer = setTimeout(() => this.removeLease(ownerKey), TTL_MS);
368
+ timer.unref();
369
+ return timer;
370
+ }
371
+ latestLeaseExpiry(sessionId) {
372
+ let latest = 0;
373
+ for (const lease of this.leases.values())
374
+ if (lease.sessionId === sessionId)
375
+ latest = Math.max(latest, lease.expiresAt);
376
+ return latest;
377
+ }
378
+ refreshTimer(sessionId) {
379
+ const timer = setTimeout(() => void this.refresh(sessionId), INTERVAL_MS);
380
+ timer.unref();
381
+ return timer;
382
+ }
185
383
  }
@@ -4,10 +4,8 @@ export interface RunWorkbenchServiceOptions {
4
4
  readonly runtime: NodeRuntimeState;
5
5
  readonly readImage: (runId: string, imageIndex: number) => Promise<unknown | undefined>;
6
6
  readonly respondUserInput: (runId: string, requestId: string, answers: Record<string, unknown>) => void;
7
- readonly deleteQueuedStart: (runId: string) => void;
8
- readonly clearImages: (runId: string) => void;
9
- readonly cleanupAttachments: (runId: string) => void;
10
- readonly emitQueue: (workspaceId: string, sessionId: string) => void;
7
+ readonly cancelQueued: (runId: string) => void;
8
+ readonly pauseQueue: (sessionId: string) => void;
11
9
  readonly emitRun: (runId: string, eventType: string, payload: unknown, status?: 'CANCELLED') => void;
12
10
  readonly control: (operation: string, payload: Record<string, unknown>) => void;
13
11
  readonly markInterrupted: (runId: string) => void;
@@ -55,11 +55,7 @@ export class RunWorkbenchService {
55
55
  if (operation === 'run.cancel' &&
56
56
  run.status === 'QUEUED' &&
57
57
  this.options.runtime.isQueuedRun(run.id)) {
58
- this.options.runtime.deleteQueuedRun(run.id);
59
- this.options.deleteQueuedStart(run.id);
60
- this.options.clearImages(run.id);
61
- this.options.cleanupAttachments(run.id);
62
- this.options.emitQueue(run.workspaceId, run.sessionId);
58
+ this.options.cancelQueued(run.id);
63
59
  return { cancelled: true };
64
60
  }
65
61
  const next = this.options.runtime.transitionRun(run.id, operation === 'run.cancel' && run.status === 'QUEUED' ? 'CANCELLED' : 'CANCELLING');
@@ -68,8 +64,7 @@ export class RunWorkbenchService {
68
64
  return { run: next };
69
65
  }
70
66
  if (operation === 'run.interrupt') {
71
- this.options.runtime.pauseSessionQueue(run.sessionId);
72
- this.options.emitQueue(run.workspaceId, run.sessionId);
67
+ this.options.pauseQueue(run.sessionId);
73
68
  this.options.markInterrupted(run.id);
74
69
  }
75
70
  this.options.control(operation, { runId: run.id });
@@ -36,7 +36,7 @@ export class SessionCommandService {
36
36
  input.input.length > 20_000)
37
37
  throw new Error('RUNNER_COMMAND_INVALID');
38
38
  const resolved = this.options.runtime.getAgentSession(input.sessionId) ??
39
- (await this.options.projection.resolve(input.sessionId));
39
+ (await this.options.projection.resolveCurrent(input.sessionId));
40
40
  if (resolved === undefined)
41
41
  throw new Error('SESSION_NOT_FOUND');
42
42
  const session = resolved.nativeControl === 'EXTERNAL' ? await this.options.resume.resume(resolved) : resolved;
@@ -8,7 +8,7 @@ export interface SessionIdentityServiceOptions {
8
8
  readonly migrateRunnerState: (previousId: string, nextId: string) => void;
9
9
  readonly emitSession: (session: NodeAgentSession) => void;
10
10
  readonly emitRun: (run: unknown) => void;
11
- readonly emitQueue: (workspaceId: string, sessionId: string) => void;
11
+ readonly emitQueue: (sessionId: string) => void;
12
12
  readonly emitTurn: (turn: unknown) => void;
13
13
  }
14
14
  export declare class SessionIdentityService {
@@ -29,7 +29,7 @@ export class SessionIdentityService {
29
29
  this.options.emitSession(session);
30
30
  for (const run of this.options.runtime.listRunsForSession(nativeSessionId))
31
31
  this.options.emitRun(run);
32
- this.options.emitQueue(session.workspaceId, nativeSessionId);
32
+ this.options.emitQueue(nativeSessionId);
33
33
  for (const turn of this.options.runtime.listConversationTurns({ sessionId: nativeSessionId })
34
34
  .turns)
35
35
  if (turn.status !== 'QUEUED')
@@ -204,6 +204,9 @@ export class SessionLifecycleService {
204
204
  ...(input.secretEnvironment === undefined
205
205
  ? {}
206
206
  : { secretEnvironment: input.secretEnvironment }),
207
+ ...(input.personalInstructions === undefined
208
+ ? {}
209
+ : { personalInstructions: input.personalInstructions }),
207
210
  ...(input.attachmentIds === undefined ? {} : { attachmentIds: input.attachmentIds })
208
211
  }));
209
212
  const nativeSessionId = await identity;
@@ -18,7 +18,6 @@ interface SessionMessageServiceOptions {
18
18
  readonly resolve: (sessionId: string) => Promise<NodeAgentSession>;
19
19
  readonly present: (session: NodeAgentSession) => unknown;
20
20
  readonly emitSession: (session: NodeAgentSession) => void;
21
- readonly emitQueue: (workspaceId: string, sessionId: string) => void;
22
21
  readonly channelToken: (sessionId: string) => string | undefined;
23
22
  }
24
23
  export declare class SessionMessageService {