@myagentroam/node 0.9.125 → 0.9.126

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.
@@ -139,6 +139,36 @@ export class CodexAppServerClient {
139
139
  this.lifecycleRevision += 1;
140
140
  this.stopManaged();
141
141
  }
142
+ async stopAndWait() {
143
+ const child = this.child;
144
+ if (child === undefined) {
145
+ this.stop();
146
+ return;
147
+ }
148
+ const exited = new Promise((resolve, reject) => {
149
+ if (child.exitCode !== null || child.signalCode !== null)
150
+ resolve();
151
+ else {
152
+ let forcedTimeout;
153
+ const onExit = () => {
154
+ clearTimeout(timeout);
155
+ if (forcedTimeout !== undefined)
156
+ clearTimeout(forcedTimeout);
157
+ resolve();
158
+ };
159
+ const timeout = setTimeout(() => {
160
+ child.kill('SIGKILL');
161
+ forcedTimeout = setTimeout(() => {
162
+ child.off('exit', onExit);
163
+ reject(new Error('CODEX_APP_SERVER_STOP_TIMEOUT'));
164
+ }, 2_000);
165
+ }, 2_000);
166
+ child.once('exit', onExit);
167
+ }
168
+ });
169
+ this.stop();
170
+ await exited;
171
+ }
142
172
  stopManaged() {
143
173
  this.transport?.destroy();
144
174
  this.transport = undefined;
@@ -3,7 +3,6 @@ import { AbstractRunner } from './abstract-runner.js';
3
3
  import { declaredRunnerProfiles } from '../runner-profiles.js';
4
4
  import { homedir } from 'node:os';
5
5
  import { resolve } from 'node:path';
6
- import { createHash } from 'node:crypto';
7
6
  import { discoverAllNativeSessions } from '../native-session-history.js';
8
7
  import { readCodexNativeContextUsage } from '../native-session-history.js';
9
8
  import { codexThreadCwd, deduplicateDirectSessions, extractCodexThreads, isWithinWorkspace, latestNativeActivity } from './codex/conversation-parser.js';
@@ -20,7 +19,6 @@ export class CodexRunner extends AbstractRunner {
20
19
  discoveryScope = 'ALL_WORKSPACES';
21
20
  execution = new CodexExecutionState();
22
21
  fastEnabled = false;
23
- environmentClients = new Map();
24
22
  runClients = new Map();
25
23
  notificationListeners = new Set();
26
24
  clientNotificationDisposers = new Map();
@@ -318,13 +316,14 @@ export class CodexRunner extends AbstractRunner {
318
316
  return this.profileCache;
319
317
  }
320
318
  async resumeExternal(external, context) {
319
+ const threadId = external.externalSessionId;
321
320
  if (external.runner !== this.name ||
322
321
  external.nativeControl !== 'EXTERNAL' ||
323
- external.externalSessionId === null ||
322
+ threadId === null ||
324
323
  !context.available)
325
324
  return { failureCode: 'RUNNER_UNAVAILABLE' };
326
325
  try {
327
- await this.resumeThread(external.externalSessionId, external.cwd);
326
+ await this.withTemporaryClient((client) => client.resumeThread(threadId, external.cwd));
328
327
  const projection = context.projectionFor(external);
329
328
  if (projection !== undefined) {
330
329
  const promoted = context.promote(projection.id);
@@ -437,7 +436,7 @@ export class CodexRunner extends AbstractRunner {
437
436
  onNotification(listener) {
438
437
  this.notificationListeners.add(listener);
439
438
  this.attachNotifications(this.client);
440
- for (const { client } of this.environmentClients.values())
439
+ for (const client of this.runClients.values())
441
440
  this.attachNotifications(client);
442
441
  return () => this.notificationListeners.delete(listener);
443
442
  }
@@ -459,8 +458,7 @@ export class CodexRunner extends AbstractRunner {
459
458
  if (!isCodexRolloutMissingError(error))
460
459
  return false;
461
460
  const client = this.clientFor(runId);
462
- const entry = [...this.environmentClients.values()].find((value) => value.client === client);
463
- if (entry !== undefined && [...entry.runIds].some((activeRunId) => activeRunId !== runId))
461
+ if ([...this.runClients].some(([activeRunId, activeClient]) => activeRunId !== runId && activeClient === client))
464
462
  return false;
465
463
  if ([...this.execution.runs].some(([activeRunId]) => activeRunId !== runId && this.runClients.get(activeRunId) === client))
466
464
  return false;
@@ -469,20 +467,12 @@ export class CodexRunner extends AbstractRunner {
469
467
  return true;
470
468
  }
471
469
  prepareEnvironment(environment, runId) {
472
- const fingerprint = createHash('sha256')
473
- .update(JSON.stringify(Object.entries(environment).sort(([left], [right]) => left.localeCompare(right))))
474
- .digest('base64url');
475
- const existing = this.environmentClients.get(fingerprint);
476
- if (existing !== undefined) {
477
- existing.runIds.add(runId);
478
- this.runClients.set(runId, existing.client);
470
+ if (this.runClients.has(runId))
479
471
  return;
480
- }
481
472
  const client = this.runClientFactory();
482
473
  if (Object.keys(environment).length > 0)
483
474
  client.configureEnvironment(environment);
484
475
  this.attachNotifications(client);
485
- this.environmentClients.set(fingerprint, { client, runIds: new Set([runId]) });
486
476
  this.runClients.set(runId, client);
487
477
  }
488
478
  releaseEnvironment(runId) {
@@ -490,19 +480,10 @@ export class CodexRunner extends AbstractRunner {
490
480
  if (client === undefined)
491
481
  return;
492
482
  this.runClients.delete(runId);
493
- for (const [fingerprint, entry] of this.environmentClients) {
494
- if (entry.client !== client)
495
- continue;
496
- entry.runIds.delete(runId);
497
- if (entry.runIds.size === 0) {
498
- this.environmentClients.delete(fingerprint);
499
- if (client !== this.client) {
500
- this.detachNotifications(client);
501
- client.stop();
502
- client.clearEnvironment();
503
- }
504
- }
505
- return;
483
+ if (client !== this.client && ![...this.runClients.values()].includes(client)) {
484
+ this.detachNotifications(client);
485
+ client.stop();
486
+ client.clearEnvironment();
506
487
  }
507
488
  }
508
489
  stop() {
@@ -510,13 +491,12 @@ export class CodexRunner extends AbstractRunner {
510
491
  for (const profileClient of this.profileClients)
511
492
  profileClient.stop();
512
493
  this.profileClients.clear();
513
- for (const { client } of this.environmentClients.values())
494
+ for (const client of new Set(this.runClients.values()))
514
495
  if (client !== this.client) {
515
496
  this.detachNotifications(client);
516
497
  client.stop();
517
498
  client.clearEnvironment();
518
499
  }
519
- this.environmentClients.clear();
520
500
  this.runClients.clear();
521
501
  this.client.stop();
522
502
  if (this.profileCacheTimer !== undefined)
@@ -530,6 +510,17 @@ export class CodexRunner extends AbstractRunner {
530
510
  clientFor(runId) {
531
511
  return runId === undefined ? this.client : (this.runClients.get(runId) ?? this.client);
532
512
  }
513
+ async withTemporaryClient(operation) {
514
+ const client = this.runClientFactory();
515
+ try {
516
+ await client.start();
517
+ return await operation(client);
518
+ }
519
+ finally {
520
+ if (client !== this.client)
521
+ await client.stopAndWait();
522
+ }
523
+ }
533
524
  attachNotifications(client) {
534
525
  if (this.clientNotificationDisposers.has(client))
535
526
  return;
@@ -558,16 +549,18 @@ export class CodexRunner extends AbstractRunner {
558
549
  return this.client.forkThread(threadId, cwd, lastTurnId, configuration);
559
550
  }
560
551
  async forkNative(session, boundary, title) {
561
- if (session.externalSessionId === null)
552
+ const threadId = session.externalSessionId;
553
+ if (threadId === null)
562
554
  throw new Error('SESSION_REWIND_UNAVAILABLE');
563
- await this.start();
564
555
  const configuration = { model: session.model, effort: session.effort, access: session.access };
565
- const result = boundary === null
566
- ? await this.startThread(session.cwd, configuration)
567
- : await this.forkThread(session.externalSessionId, session.cwd, boundary, configuration);
568
- const threadId = extractId(result, 'thread');
569
- await this.setThreadName(threadId, title);
570
- return threadId;
556
+ return this.withTemporaryClient(async (client) => {
557
+ const result = boundary === null
558
+ ? await client.startThread(session.cwd, configuration)
559
+ : await client.forkThread(threadId, session.cwd, boundary, configuration);
560
+ const forkedThreadId = extractId(result, 'thread');
561
+ await client.setThreadName(forkedThreadId, title);
562
+ return forkedThreadId;
563
+ });
571
564
  }
572
565
  resumeThread(threadId, cwd, configuration, runId) {
573
566
  return this.clientFor(runId).resumeThread(threadId, cwd, configuration);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.9.125",
3
+ "version": "0.9.126",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -30,9 +30,9 @@
30
30
  "npm": "11.12.1",
31
31
  "ws": "^8.21.3",
32
32
  "zod": "4.4.3",
33
- "@myagentroam/agent": "0.9.125",
34
- "@myagentroam/code-navigation": "0.9.125",
35
- "@myagentroam/protocol": "0.9.125"
33
+ "@myagentroam/agent": "0.9.126",
34
+ "@myagentroam/protocol": "0.9.126",
35
+ "@myagentroam/code-navigation": "0.9.126"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/ws": "^8.18.1"