@myagentroam/node 0.9.3 → 0.9.5

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 (66) 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 +25 -0
  4. package/dist/codex-app-server.js +104 -7
  5. package/dist/connector.d.ts +1 -0
  6. package/dist/connector.js +21 -12
  7. package/dist/database.d.ts +2 -0
  8. package/dist/native-session-history.d.ts +3 -2
  9. package/dist/native-session-history.js +199 -30
  10. package/dist/opencode-server.d.ts +1 -0
  11. package/dist/opencode-server.js +1 -0
  12. package/dist/runner/abstract-runner.d.ts +11 -2
  13. package/dist/runner/abstract-runner.js +4 -0
  14. package/dist/runner/claude/managed-run-controller.js +3 -0
  15. package/dist/runner/claude-code-runner.d.ts +2 -1
  16. package/dist/runner/claude-code-runner.js +4 -0
  17. package/dist/runner/codex/managed-run-controller.d.ts +9 -0
  18. package/dist/runner/codex/managed-run-controller.js +63 -17
  19. package/dist/runner/codex-runner.d.ts +3 -1
  20. package/dist/runner/codex-runner.js +23 -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.d.ts +2 -0
  27. package/dist/runtime-state.js +12 -7
  28. package/dist/service/conversation-history-service.d.ts +10 -1
  29. package/dist/service/conversation-history-service.js +272 -42
  30. package/dist/service/conversation-segment-service.js +7 -1
  31. package/dist/service/external-session-resume-service.d.ts +2 -0
  32. package/dist/service/external-session-resume-service.js +45 -34
  33. package/dist/service/native-session-projection-service.d.ts +1 -0
  34. package/dist/service/native-session-projection-service.js +4 -0
  35. package/dist/service/native-session-watch-service.d.ts +26 -2
  36. package/dist/service/native-session-watch-service.js +258 -42
  37. package/dist/service/node-request-service.js +2 -1
  38. package/dist/service/run-event-service.js +16 -5
  39. package/dist/service/run-workbench-service.d.ts +2 -4
  40. package/dist/service/run-workbench-service.js +2 -7
  41. package/dist/service/session-command-service.js +1 -1
  42. package/dist/service/session-identity-service.d.ts +1 -1
  43. package/dist/service/session-identity-service.js +1 -1
  44. package/dist/service/session-lifecycle-service.js +7 -12
  45. package/dist/service/session-message-service.d.ts +1 -1
  46. package/dist/service/session-message-service.js +104 -83
  47. package/dist/service/skill-directory-service.d.ts +1 -0
  48. package/dist/service/skill-directory-service.js +40 -6
  49. package/dist/service/workspace-change-service.js +1 -1
  50. package/dist/service/workspace-domain-state.d.ts +3 -0
  51. package/dist/service/workspace-domain-state.js +1 -0
  52. package/dist/service/workspace-file-service.d.ts +1 -0
  53. package/dist/service/workspace-file-service.js +14 -1
  54. package/dist/service/workspace-queue-workbench-service.d.ts +29 -2
  55. package/dist/service/workspace-queue-workbench-service.js +73 -8
  56. package/dist/service/workspace-watch-service.d.ts +7 -2
  57. package/dist/service/workspace-watch-service.js +32 -6
  58. package/dist/service/workspace-workbench-service.d.ts +14 -0
  59. package/dist/service/workspace-workbench-service.js +215 -16
  60. package/dist/util/personal-instructions.d.ts +2 -0
  61. package/dist/util/personal-instructions.js +31 -0
  62. package/dist/util/runner-native-session-parsers.d.ts +3 -1
  63. package/dist/util/runner-native-session-parsers.js +35 -17
  64. package/dist/workspace.d.ts +14 -8
  65. package/dist/workspace.js +98 -50
  66. package/package.json +2 -2
@@ -1,7 +1,7 @@
1
1
  import type { NodeCapabilities, RunnerDefaultConfiguration, RunnerProfile } from '@myagentroam/protocol';
2
2
  import type { NodeAgentSession, NodeDatabase, NodeWorkspace } from '../database.js';
3
3
  import { OpenCodeServerClient } from '../opencode-server.js';
4
- import { AbstractRunner, type ExternalResumeContext, type ExternalResumeResult, type RunnerDiscoveryContext, type RunnerSessionPresentation, type RunnerSessionPresentationContext } from './abstract-runner.js';
4
+ import { AbstractRunner, type ExternalResumeContext, type ExternalResumeResult, type RunnerDiscoveryContext, type RunnerConversationHistoryPage, type RunnerConversationHistoryReaders, type RunnerSessionPresentation, type RunnerSessionPresentationContext } from './abstract-runner.js';
5
5
  export interface OpenCodeManagedExecution {
6
6
  readonly runId: string;
7
7
  readonly sessionId: string;
@@ -20,6 +20,8 @@ export declare class OpenCodeRunner extends AbstractRunner<'opencode'> {
20
20
  private readonly modelsByWorkspace;
21
21
  private readonly contextLimitsByWorkspace;
22
22
  private readonly profileTimersByWorkspace;
23
+ private readonly profileCache;
24
+ private readonly profileRefreshes;
23
25
  readonly execution: Map<string, OpenCodeManagedExecution>;
24
26
  readonly managedTurns: Map<string, string>;
25
27
  private readonly environmentClients;
@@ -27,10 +29,14 @@ export declare class OpenCodeRunner extends AbstractRunner<'opencode'> {
27
29
  constructor(client?: OpenCodeServerClient, clientFactory?: () => OpenCodeServerClient);
28
30
  profile(capabilities: NodeCapabilities): RunnerProfile;
29
31
  refreshProfile(capabilities: NodeCapabilities, workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): Promise<RunnerProfile>;
32
+ private fetchProfile;
33
+ private profileCacheKey;
34
+ private environmentFingerprint;
30
35
  available(capabilities: NodeCapabilities): boolean;
31
36
  protected profileForValidation(): RunnerProfile;
32
- supportsConfiguration(configuration: RunnerDefaultConfiguration, workspace?: NodeWorkspace): boolean;
33
- defaultConfiguration(workspace?: NodeWorkspace): RunnerDefaultConfiguration;
37
+ supportsConfiguration(configuration: RunnerDefaultConfiguration, workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): boolean;
38
+ defaultConfiguration(workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): RunnerDefaultConfiguration;
39
+ private cachedModels;
34
40
  mcpConfiguration(raw: unknown, secrets: Readonly<Record<string, string>>): unknown;
35
41
  discoverSessions(context: RunnerDiscoveryContext, workspace?: NodeWorkspace): Promise<readonly NodeAgentSession[]>;
36
42
  resumeExternal(external: NodeAgentSession, context: ExternalResumeContext): Promise<ExternalResumeResult>;
@@ -42,6 +48,7 @@ export declare class OpenCodeRunner extends AbstractRunner<'opencode'> {
42
48
  readonly turns: readonly import("../database.js").NodeConversationTurn[];
43
49
  readonly nextCursor: string | null;
44
50
  } | undefined>;
51
+ readConversationHistory(readers: RunnerConversationHistoryReaders): Promise<RunnerConversationHistoryPage | undefined>;
45
52
  managedRunForNativeTurn(nativeTurnId: string): string | undefined;
46
53
  presentSession(session: NodeAgentSession, capabilities: NodeCapabilities, context: RunnerSessionPresentationContext): RunnerSessionPresentation;
47
54
  renameNative(session: NodeAgentSession, input: {
@@ -5,6 +5,7 @@ import { AbstractRunner } from './abstract-runner.js';
5
5
  import { isWithinWorkspace } from './codex/conversation-parser.js';
6
6
  import { createHash } from 'node:crypto';
7
7
  import { openCodeConversationPage } from './opencode/conversation-parser.js';
8
+ import { nodeLog } from '../operational.js';
8
9
  const PROFILE_ENTRY_TTL_MS = 30 * 60_000;
9
10
  export class OpenCodeRunner extends AbstractRunner {
10
11
  client;
@@ -14,6 +15,8 @@ export class OpenCodeRunner extends AbstractRunner {
14
15
  modelsByWorkspace = new Map();
15
16
  contextLimitsByWorkspace = new Map();
16
17
  profileTimersByWorkspace = new Map();
18
+ profileCache = new Map();
19
+ profileRefreshes = new Map();
17
20
  execution = new Map();
18
21
  managedTurns = new Map();
19
22
  environmentClients = new Map();
@@ -35,25 +38,79 @@ export class OpenCodeRunner extends AbstractRunner {
35
38
  if (capabilities.openCode?.available !== true || workspace === undefined) {
36
39
  return this.profile(capabilities);
37
40
  }
41
+ const cacheKey = this.profileCacheKey(workspace.path, environment);
42
+ const now = Date.now();
43
+ for (const [key, entry] of this.profileCache)
44
+ if (entry.expiresAt <= now)
45
+ this.profileCache.delete(key);
46
+ const cached = this.profileCache.get(cacheKey);
47
+ if (cached !== undefined) {
48
+ nodeLog('runner.opencode.profile.cache-hit', {
49
+ workspaceId: workspace.id,
50
+ models: cached.models.length,
51
+ remainingTtlMs: cached.expiresAt - now
52
+ });
53
+ this.modelsByWorkspace.set(workspace.path, cached.models);
54
+ this.contextLimitsByWorkspace.set(workspace.path, cached.contextLimits);
55
+ this.scheduleProfileExpiry(workspace.path);
56
+ return { ...this.profile(capabilities), models: cached.models };
57
+ }
58
+ const current = this.profileRefreshes.get(cacheKey);
59
+ if (current !== undefined) {
60
+ nodeLog('runner.opencode.profile.single-flight-joined', { workspaceId: workspace.id });
61
+ return current;
62
+ }
63
+ const refresh = this.fetchProfile(capabilities, workspace, environment, cacheKey).finally(() => {
64
+ if (this.profileRefreshes.get(cacheKey) === refresh)
65
+ this.profileRefreshes.delete(cacheKey);
66
+ });
67
+ this.profileRefreshes.set(cacheKey, refresh);
68
+ return refresh;
69
+ }
70
+ async fetchProfile(capabilities, workspace, environment, cacheKey) {
38
71
  const client = this.managedClient(environment);
72
+ const startedAt = performance.now();
39
73
  try {
40
74
  const catalog = await client.providers(workspace.path);
41
75
  const models = openCodeProfileModels(catalog);
76
+ const contextLimits = openCodeContextLimits(catalog);
42
77
  this.modelsByWorkspace.set(workspace.path, models);
43
- this.contextLimitsByWorkspace.set(workspace.path, openCodeContextLimits(catalog));
78
+ this.contextLimitsByWorkspace.set(workspace.path, contextLimits);
79
+ this.profileCache.set(cacheKey, {
80
+ expiresAt: Date.now() + PROFILE_ENTRY_TTL_MS,
81
+ workspacePath: workspace.path,
82
+ environmentFingerprint: this.environmentFingerprint(environment),
83
+ models,
84
+ contextLimits
85
+ });
44
86
  this.scheduleProfileExpiry(workspace.path);
87
+ nodeLog('runner.opencode.profile.providers.completed', {
88
+ workspaceId: workspace.id,
89
+ models: models.length,
90
+ durationMs: Math.round(performance.now() - startedAt)
91
+ });
45
92
  return { ...this.profile(capabilities), models };
46
93
  }
47
94
  catch {
48
- this.modelsByWorkspace.delete(workspace.path);
49
- this.contextLimitsByWorkspace.delete(workspace.path);
50
- this.clearProfileTimer(workspace.path);
95
+ nodeLog('runner.opencode.profile.providers.failed', {
96
+ workspaceId: workspace.id,
97
+ durationMs: Math.round(performance.now() - startedAt)
98
+ });
51
99
  }
52
100
  finally {
53
101
  this.releaseManagedClient(client);
54
102
  }
55
103
  return this.profile(capabilities);
56
104
  }
105
+ profileCacheKey(workspacePath, environment) {
106
+ return createHash('sha256')
107
+ .update(JSON.stringify({ workspacePath, environment: this.environmentFingerprint(environment) }))
108
+ .digest('hex');
109
+ }
110
+ environmentFingerprint(environment) {
111
+ const entries = Object.entries(environment).sort(([left], [right]) => left.localeCompare(right));
112
+ return createHash('sha256').update(JSON.stringify(entries)).digest('hex');
113
+ }
57
114
  available(capabilities) {
58
115
  return capabilities.openCode?.available === true;
59
116
  }
@@ -68,22 +125,18 @@ export class OpenCodeRunner extends AbstractRunner {
68
125
  openCode: { available: true }
69
126
  });
70
127
  }
71
- supportsConfiguration(configuration, workspace) {
128
+ supportsConfiguration(configuration, workspace, environment = {}) {
72
129
  if (configuration.runner !== this.name)
73
130
  return false;
74
- const models = workspace === undefined
75
- ? [...this.modelsByWorkspace.values()].flat()
76
- : (this.modelsByWorkspace.get(workspace.path) ?? []);
131
+ const models = this.cachedModels(workspace, environment);
77
132
  const model = models.find((candidate) => candidate.id === configuration.model);
78
133
  return (model !== undefined &&
79
134
  (model.supportedEfforts.length === 0 ||
80
135
  model.supportedEfforts.includes(configuration.effort)) &&
81
136
  this.profileForValidation().accessOptions.some((candidate) => candidate.id === configuration.access));
82
137
  }
83
- defaultConfiguration(workspace) {
84
- const model = workspace === undefined
85
- ? this.modelsByWorkspace.values().next().value?.[0]
86
- : this.modelsByWorkspace.get(workspace.path)?.[0];
138
+ defaultConfiguration(workspace, environment = {}) {
139
+ const model = this.cachedModels(workspace, environment)[0];
87
140
  return {
88
141
  runner: this.name,
89
142
  model: model?.id ?? '',
@@ -91,6 +144,15 @@ export class OpenCodeRunner extends AbstractRunner {
91
144
  access: 'default'
92
145
  };
93
146
  }
147
+ cachedModels(workspace, environment) {
148
+ const fingerprint = this.environmentFingerprint(environment);
149
+ const now = Date.now();
150
+ return [...this.profileCache.values()]
151
+ .filter((entry) => entry.expiresAt > now &&
152
+ entry.environmentFingerprint === fingerprint &&
153
+ (workspace === undefined || entry.workspacePath === workspace.path))
154
+ .flatMap((entry) => entry.models);
155
+ }
94
156
  mcpConfiguration(raw, secrets) {
95
157
  const servers = super.mcpConfiguration(raw, secrets);
96
158
  return Object.fromEntries(servers.map((server) => [
@@ -121,7 +183,9 @@ export class OpenCodeRunner extends AbstractRunner {
121
183
  }
122
184
  for (const value of sessions) {
123
185
  const session = openCodeSession(value);
124
- if (session === undefined || !isWithinWorkspace(session.directory, target.path))
186
+ if (session === undefined ||
187
+ session.parentId !== undefined ||
188
+ !isWithinWorkspace(session.directory, target.path))
125
189
  continue;
126
190
  result.push(context.createProjection({
127
191
  workspace: target,
@@ -177,6 +241,10 @@ export class OpenCodeRunner extends AbstractRunner {
177
241
  return undefined;
178
242
  }
179
243
  }
244
+ async readConversationHistory(readers) {
245
+ const official = await readers.official();
246
+ return official === undefined ? undefined : { ...official, source: 'official' };
247
+ }
180
248
  managedRunForNativeTurn(nativeTurnId) {
181
249
  return this.managedTurns.get(nativeTurnId);
182
250
  }
@@ -286,6 +354,8 @@ export class OpenCodeRunner extends AbstractRunner {
286
354
  for (const timer of this.profileTimersByWorkspace.values())
287
355
  clearTimeout(timer);
288
356
  this.profileTimersByWorkspace.clear();
357
+ this.profileCache.clear();
358
+ this.profileRefreshes.clear();
289
359
  this.modelsByWorkspace.clear();
290
360
  this.contextLimitsByWorkspace.clear();
291
361
  const stopping = [
@@ -370,6 +440,9 @@ function openCodeSession(value) {
370
440
  id: record.id,
371
441
  directory: record.directory,
372
442
  title: record.title,
373
- updatedAt: typeof time?.updated === 'number' ? time.updated : 0
443
+ updatedAt: typeof time?.updated === 'number' ? time.updated : 0,
444
+ ...(typeof record.parentID === 'string' && record.parentID.length > 0
445
+ ? { parentId: record.parentID }
446
+ : {})
374
447
  };
375
448
  }
@@ -1,3 +1,4 @@
1
+ import { nodeLog } from '../operational.js';
1
2
  export class RunnerRegistry {
2
3
  runners = new Map();
3
4
  constructor(runners = []) {
@@ -35,7 +36,21 @@ export class RunnerRegistry {
35
36
  });
36
37
  }
37
38
  async refreshProfiles(capabilities, workspace, environment = {}) {
38
- const profiles = await Promise.all(this.advertised().map((runner) => runner.refreshProfile(capabilities, workspace, environment)));
39
+ const profiles = await Promise.all(this.advertised().map(async (runner) => {
40
+ const startedAt = performance.now();
41
+ try {
42
+ return await runner.refreshProfile(capabilities, workspace, environment);
43
+ }
44
+ finally {
45
+ const durationMs = Math.round(performance.now() - startedAt);
46
+ if (durationMs >= 100)
47
+ nodeLog('runner.profile.refresh.completed', {
48
+ runner: runner.name,
49
+ workspaceId: workspace?.id,
50
+ durationMs
51
+ });
52
+ }
53
+ }));
39
54
  return profiles.flatMap((profile) => (profile === undefined ? [] : [profile]));
40
55
  }
41
56
  supportsConfiguration(configuration, workspace, environment) {
@@ -50,18 +50,13 @@ export function normalizeVersion(id, value) {
50
50
  }
51
51
  async function executeVersion(command, args) {
52
52
  const executable = process.platform === 'win32' ? process.env['ComSpec'] || 'cmd.exe' : command;
53
- const commandArgs = process.platform === 'win32'
54
- ? ['/d', '/s', '/c', [command, ...args].map(quoteWindowsArgument).join(' ')]
55
- : [...args];
53
+ const commandArgs = process.platform === 'win32' ? ['/d', '/s', '/c', [command, ...args].join(' ')] : [...args];
56
54
  return execFileAsync(executable, commandArgs, {
57
55
  timeout: 5_000,
58
56
  maxBuffer: 64 * 1024,
59
57
  windowsHide: true
60
58
  });
61
59
  }
62
- function quoteWindowsArgument(value) {
63
- return `"${value.replaceAll('"', '""')}"`;
64
- }
65
60
  async function resolveCommandPath(command) {
66
61
  try {
67
62
  const resolver = process.platform === 'win32' ? 'where.exe' : 'which';
@@ -86,9 +86,11 @@ export declare class NodeRuntimeState {
86
86
  */
87
87
  setRunnerTitleFromNative(id: string, title: string): NodeAgentSession;
88
88
  createMessageRun(input: {
89
+ readonly runId?: string;
89
90
  readonly sessionId: string;
90
91
  readonly clientMessageId: string;
91
92
  readonly content: string;
93
+ readonly initiatedByUserId?: number;
92
94
  readonly attachments?: readonly NodeMessageAttachmentInput[];
93
95
  }): {
94
96
  readonly run: NodeAgentRun;
@@ -255,7 +255,7 @@ export class NodeRuntimeState {
255
255
  const session = this.requireSession(input.sessionId);
256
256
  const now = this.now();
257
257
  const run = {
258
- id: randomUUID(),
258
+ id: input.runId ?? randomUUID(),
259
259
  sessionId: session.id,
260
260
  nodeId: this.nodeId(),
261
261
  workspaceId: session.workspaceId,
@@ -264,6 +264,9 @@ export class NodeRuntimeState {
264
264
  recoveryState: 'ACTIVE',
265
265
  version: 0,
266
266
  clientMessageId: input.clientMessageId,
267
+ ...(input.initiatedByUserId === undefined
268
+ ? {}
269
+ : { initiatedByUserId: input.initiatedByUserId }),
267
270
  createdAt: now,
268
271
  updatedAt: now
269
272
  };
@@ -388,6 +391,7 @@ export class NodeRuntimeState {
388
391
  ? { ...item, payload: { ...item.payload, text: content } }
389
392
  : item)
390
393
  }));
394
+ this.bumpQueueVersion(run.sessionId);
391
395
  return run;
392
396
  }
393
397
  deleteQueuedRun(runId) {
@@ -471,15 +475,16 @@ export class NodeRuntimeState {
471
475
  }
472
476
  listConversationTurns(input) {
473
477
  this.requireSession(input.sessionId);
474
- // Queued turns have not received `run.started` yet. Their user item still
475
- // has the acceptance timestamp, so use it rather than sorting every
476
- // queued turn as time zero. The insertion index makes same-millisecond
477
- // sends deterministic; callers receive every page from oldest to newest.
478
+ // A managed Turn keeps the user's acceptance time as its stable timeline
479
+ // position. Runner start/completion timestamps must not move it behind a
480
+ // message that was accepted later. Native Turns without a user timestamp
481
+ // fall back to their own lifecycle time.
478
482
  const all = [...(this.turns.get(input.sessionId) ?? [])]
479
483
  .map((turn, index) => ({ turn, index }))
480
484
  .sort((a, b) => {
481
- const timestamp = (value) => value.startedAt ??
482
- value.items.find((item) => item.id === value.userItemId)?.startedAt ??
485
+ const timestamp = (value) => value.items.find((item) => item.id === value.userItemId)?.startedAt ??
486
+ value.startedAt ??
487
+ value.completedAt ??
483
488
  0;
484
489
  return timestamp(a.turn) - timestamp(b.turn) || a.index - b.index;
485
490
  })
@@ -5,6 +5,10 @@ import type { RunnerRegistry } from '../runner/runner-registry.js';
5
5
  import { type ConversationHistoryPage } from '../util/runner-native-session-parsers.js';
6
6
  import type { RunAttachmentService } from './run-attachment-service.js';
7
7
  export declare function hasManagedActiveRun(runtime: NodeRuntimeState, session: NodeAgentSession): boolean;
8
+ export declare function convergeConversationWatchPage(previous: ConversationHistoryPage | undefined, previousObserved: ConversationHistoryPage | undefined, next: ConversationHistoryPage, previousAuthority: unknown): {
9
+ readonly page: ConversationHistoryPage;
10
+ readonly authority: ConversationHistoryPage['source'];
11
+ };
8
12
  interface ConversationHistoryServiceOptions {
9
13
  readonly runtime: NodeRuntimeState;
10
14
  readonly runners: RunnerRegistry;
@@ -16,18 +20,23 @@ interface ConversationHistoryServiceOptions {
16
20
  }
17
21
  export declare class ConversationHistoryService {
18
22
  private readonly options;
23
+ private readonly activeHistoryReads;
19
24
  constructor(options: ConversationHistoryServiceOptions);
20
25
  read(session: NodeAgentSession, input: {
21
26
  readonly cursor?: string;
22
27
  readonly limit?: number;
23
28
  }): Promise<ConversationHistoryPage>;
29
+ private readRunnerHistory;
30
+ private readManagedActive;
31
+ private prefetchActiveHistory;
32
+ private takeActiveHistoryRead;
24
33
  refreshExternalActivity(session: NodeAgentSession): Promise<void>;
25
34
  readNative(session: NodeAgentSession): Promise<import("../native-session-history.js").NativeSessionHistory | undefined>;
26
35
  private attachNativeImages;
27
36
  readOfficial(session: NodeAgentSession, input: {
28
37
  readonly cursor?: string;
29
38
  readonly limit?: number;
30
- }): Promise<import("../runner/abstract-runner.js").RunnerOfficialConversationPage | undefined>;
39
+ }): Promise<import("../runner/abstract-runner.js").RunnerConversationPage | undefined>;
31
40
  private managedRunnerTurn;
32
41
  private page;
33
42
  }