@myagentroam/node 0.9.117 → 0.9.119

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/connector.js CHANGED
@@ -669,7 +669,10 @@ export class NodeConnector {
669
669
  (await this.runAttachmentService.readNative(runId, imageIndex)),
670
670
  respondUserInput: (runId, requestId, answers) => this.runnerInteractionService.respondUserInput(runId, requestId, answers),
671
671
  pauseQueue: (sessionId) => this.workspaceQueueWorkbenchService.pauseSessionQueue(sessionId),
672
- runInterrupted: (sessionId) => this.clearGoalState(sessionId),
672
+ runInterrupted: (sessionId) => {
673
+ const goalExit = this.clearGoalState(sessionId, 'stopped');
674
+ return goalExit === undefined ? undefined : { goalExit };
675
+ },
673
676
  emitRun: (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status),
674
677
  control: (operation, payload) => this.controlMessages.handle(JSON.stringify(createEnvelope(operation, payload))),
675
678
  markInterrupted: (runId) => this.runState.interruptRequested.add(runId)
@@ -930,9 +933,13 @@ export class NodeConnector {
930
933
  };
931
934
  },
932
935
  clearGoal: (session) => {
933
- this.clearGoalState(session.id);
936
+ const goalExit = this.clearGoalState(session.id, 'removed');
934
937
  this.finishIdleGoalRun(session.id, 'SUCCEEDED');
935
- return { session: this.sessionPresentationService.present(session), cleared: true };
938
+ return {
939
+ session: this.sessionPresentationService.present(session),
940
+ cleared: true,
941
+ ...(goalExit === undefined ? {} : { goalExit })
942
+ };
936
943
  }
937
944
  });
938
945
  this.sessionMessageService = new SessionMessageService({
@@ -1680,7 +1687,8 @@ export class NodeConnector {
1680
1687
  return;
1681
1688
  this.runEventBridge.emitRun(run.id, status === 'SUCCEEDED' ? 'run.completed' : 'run.failed', status === 'SUCCEEDED' ? {} : { code: 'GOAL_CONTINUATION_FAILED' }, status);
1682
1689
  }
1683
- clearGoalState(sessionId) {
1690
+ clearGoalState(sessionId, status) {
1691
+ const goalExit = this.goalRuntime.exitSummary(sessionId, status);
1684
1692
  this.retireGoalContinuation(sessionId);
1685
1693
  const removed = this.goalRuntime.clear(sessionId);
1686
1694
  this.continuousRunCoordinator.removeGoalQueue(sessionId);
@@ -1688,12 +1696,13 @@ export class NodeConnector {
1688
1696
  if (commandState !== undefined)
1689
1697
  this.commandStates.clear(sessionId, 'goal');
1690
1698
  if (!removed && commandState === undefined)
1691
- return;
1699
+ return goalExit;
1692
1700
  const session = this.runtime.getAgentSession(sessionId);
1693
1701
  if (session !== undefined)
1694
1702
  this.emitWorkbenchEvent('session', {
1695
1703
  session: this.sessionPresentationService.present(session)
1696
1704
  });
1705
+ return goalExit;
1697
1706
  }
1698
1707
  prepareGoalSession(session) {
1699
1708
  const access = goalUnrestrictedAccess(session.runner);
@@ -12,8 +12,12 @@ export class AbstractRunner {
12
12
  /** Test-only runners are registered explicitly but never advertised. */
13
13
  advertised = true;
14
14
  interceptsRuns = false;
15
- async refreshProfile(capabilities, workspace, environment = {}) {
16
- void [workspace, environment];
15
+ cachedProfile(capabilities, environment = {}) {
16
+ void environment;
17
+ return this.profile(capabilities);
18
+ }
19
+ async refreshProfile(capabilities, workspace, environment = {}, waitForRefresh = false) {
20
+ void [workspace, environment, waitForRefresh];
17
21
  return this.profile(capabilities);
18
22
  }
19
23
  supportsConfiguration(configuration, workspace, environment) {
@@ -14,6 +14,7 @@ const CODEX_MODEL_CACHE_MS = 10 * 60 * 1_000;
14
14
  const CODEX_PROFILE_ENTRY_TTL_MS = 30 * 60 * 1_000;
15
15
  const CODEX_MODEL_FAILURE_RETRY_MS = 30 * 1_000;
16
16
  const CODEX_MODEL_PAGE_SIZE = 100;
17
+ const CODEX_MODEL_MAX_PAGES = 6;
17
18
  export class CodexRunner extends AbstractRunner {
18
19
  client;
19
20
  profileClientFactory;
@@ -30,6 +31,7 @@ export class CodexRunner extends AbstractRunner {
30
31
  profileKeyByEnvironment = new Map();
31
32
  latestProfileCache;
32
33
  selectedProfileKey;
34
+ profileGeneration = 0;
33
35
  constructor(client = new CodexAppServerClient(), profileClientFactory = () => new CodexAppServerClient()) {
34
36
  super();
35
37
  this.client = client;
@@ -43,7 +45,13 @@ export class CodexRunner extends AbstractRunner {
43
45
  }).find((profile) => profile.runner === this.name);
44
46
  return { ...base, models: this.latestProfileCache?.profile.models ?? [] };
45
47
  }
46
- async refreshProfile(capabilities, workspace, environment = {}) {
48
+ cachedProfile(capabilities, environment = {}) {
49
+ const base = this.profile(capabilities);
50
+ const key = codexEnvironmentFingerprint(environment, capabilities.codex.version);
51
+ const cached = this.profileCache.get(key);
52
+ return cached === undefined ? { ...base, models: [] } : cached.profile;
53
+ }
54
+ async refreshProfile(capabilities, workspace, environment = {}, waitForRefresh = false) {
47
55
  void workspace;
48
56
  const base = this.profile(capabilities);
49
57
  if (!capabilities.codex.available)
@@ -56,8 +64,11 @@ export class CodexRunner extends AbstractRunner {
56
64
  if (cached?.successful === true && now < cached.fetchedAt + CODEX_MODEL_CACHE_MS)
57
65
  return this.selectCachedProfile(key, cached);
58
66
  if (cached?.successful === true) {
59
- if (now >= cached.retryAt)
60
- void this.refreshCodexProfile(key, base, environment);
67
+ if (now >= cached.retryAt) {
68
+ const refresh = this.refreshCodexProfile(key, base, environment);
69
+ if (waitForRefresh)
70
+ return refresh;
71
+ }
61
72
  return this.selectCachedProfile(key, cached);
62
73
  }
63
74
  if (cached !== undefined && now < cached.retryAt)
@@ -68,6 +79,7 @@ export class CodexRunner extends AbstractRunner {
68
79
  const existing = this.profileRefreshes.get(key);
69
80
  if (existing !== undefined)
70
81
  return existing;
82
+ const generation = this.profileGeneration;
71
83
  const refresh = this.fetchCodexProfile(base, environment)
72
84
  .then((value) => {
73
85
  const cache = {
@@ -76,15 +88,19 @@ export class CodexRunner extends AbstractRunner {
76
88
  successful: true,
77
89
  retryAt: 0
78
90
  };
79
- this.cacheProfile(key, cache);
80
- if (this.selectedProfileKey === key)
81
- this.latestProfileCache = cache;
91
+ if (generation === this.profileGeneration) {
92
+ this.cacheProfile(key, cache);
93
+ if (this.selectedProfileKey === key)
94
+ this.latestProfileCache = cache;
95
+ }
82
96
  return cache.profile;
83
97
  })
84
98
  .catch((error) => {
85
99
  nodeLog('runner.codex.models.refresh-failed', {
86
100
  error: error instanceof Error ? error.message : String(error)
87
101
  });
102
+ if (generation !== this.profileGeneration)
103
+ return { ...base, models: [] };
88
104
  const cached = this.profileCache.get(key);
89
105
  if (cached !== undefined) {
90
106
  cached.retryAt = Date.now() + CODEX_MODEL_FAILURE_RETRY_MS;
@@ -136,8 +152,12 @@ export class CodexRunner extends AbstractRunner {
136
152
  await client.start();
137
153
  const models = [];
138
154
  let cursor = null;
155
+ let pages = 0;
139
156
  const cursors = new Set();
140
157
  do {
158
+ if (pages >= CODEX_MODEL_MAX_PAGES)
159
+ throw new Error('CODEX_MODELS_PAGE_LIMIT');
160
+ pages += 1;
141
161
  const page = codexModelPage(await client.listModels({ cursor, limit: CODEX_MODEL_PAGE_SIZE, includeHidden: false }));
142
162
  models.push(...page.models);
143
163
  cursor = page.nextCursor;
@@ -454,6 +474,7 @@ export class CodexRunner extends AbstractRunner {
454
474
  this.environmentFingerprint = undefined;
455
475
  }
456
476
  stop() {
477
+ this.profileGeneration += 1;
457
478
  for (const profileClient of this.profileClients)
458
479
  profileClient.stop();
459
480
  this.profileClients.clear();
@@ -67,7 +67,12 @@ export class MarAgentConversationNormalizer {
67
67
  return this.#toolCompleted(event);
68
68
  if (event.type === 'tool.failed')
69
69
  return this.#toolFailed(event);
70
- if (event.type === 'hosted_tool.updated')
70
+ if (event.type === 'hosted_tool.updated') {
71
+ const sourceSummary = event.summary.sources
72
+ ?.slice(0, 20)
73
+ .map((source) => (source.title ? `${source.title} — ${source.url}` : source.url))
74
+ .join('\n');
75
+ const output = sourceSummary || event.summary.title || '';
71
76
  return [
72
77
  {
73
78
  itemId: itemId('hosted', event.itemId),
@@ -84,16 +89,17 @@ export class MarAgentConversationNormalizer {
84
89
  inputSummary: event.summary.query === undefined && event.summary.url === undefined
85
90
  ? null
86
91
  : compactRunnerText(event.summary.query ?? event.summary.url ?? '', 10_000),
87
- outputSummary: event.summary.title === undefined
88
- ? null
89
- : compactRunnerText(event.summary.title, 10_000),
92
+ outputSummary: output ? compactRunnerText(output, 10_000) : null,
90
93
  progressLabel: event.status === 'RUNNING' ? 'Running' : null,
91
94
  errorCode: event.status === 'FAILED' ? 'HOSTED_TOOL_FAILED' : null,
92
- truncated: false
95
+ truncated: event.summary.sourcesTruncated === true ||
96
+ (event.summary.sources?.length ?? 0) > 20 ||
97
+ output.length > 10_000
93
98
  },
94
99
  merge: true
95
100
  }
96
101
  ];
102
+ }
97
103
  if (event.type === 'todo.updated')
98
104
  return [
99
105
  {
@@ -8,6 +8,9 @@ import { homedir } from 'node:os';
8
8
  import { openCodeConversationPage } from './opencode/conversation-parser.js';
9
9
  import { nodeLog } from '../operational.js';
10
10
  const PROFILE_ENTRY_TTL_MS = 30 * 60_000;
11
+ const PROFILE_STALE_TTL_MS = 60 * 60_000;
12
+ const PROFILE_FAILURE_RETRY_MS = 30_000;
13
+ const PROFILE_REFRESH_TIMEOUT_MS = 90_000;
11
14
  export class OpenCodeRunner extends AbstractRunner {
12
15
  client;
13
16
  clientFactory;
@@ -16,7 +19,9 @@ export class OpenCodeRunner extends AbstractRunner {
16
19
  contextLimits = new Map();
17
20
  contextLimitsExpiresAt = 0;
18
21
  profileCache = new Map();
22
+ profileRetryAt = new Map();
19
23
  profileRefreshes = new Map();
24
+ profileGeneration = 0;
20
25
  execution = new Map();
21
26
  managedTurns = new Map();
22
27
  environmentClients = new Map();
@@ -34,17 +39,22 @@ export class OpenCodeRunner extends AbstractRunner {
34
39
  }).find((profile) => profile.runner === this.name);
35
40
  return { ...base, models: [] };
36
41
  }
37
- async refreshProfile(capabilities, _workspace, environment = {}) {
42
+ cachedProfile(capabilities, environment = {}) {
43
+ this.pruneProfileCaches(Date.now());
44
+ const cached = this.profileCache.get(this.environmentFingerprint(environment));
45
+ return cached === undefined
46
+ ? this.profile(capabilities)
47
+ : { ...this.profile(capabilities), models: cached.models };
48
+ }
49
+ async refreshProfile(capabilities, _workspace, environment = {}, _waitForRefresh = false) {
38
50
  if (capabilities.openCode?.available !== true) {
39
51
  return this.profile(capabilities);
40
52
  }
41
53
  const cacheKey = this.environmentFingerprint(environment);
42
54
  const now = Date.now();
43
- for (const [key, entry] of this.profileCache)
44
- if (entry.expiresAt <= now)
45
- this.profileCache.delete(key);
55
+ this.pruneProfileCaches(now);
46
56
  const cached = this.profileCache.get(cacheKey);
47
- if (cached !== undefined) {
57
+ if (cached !== undefined && cached.expiresAt > now) {
48
58
  nodeLog('runner.opencode.profile.cache-hit', {
49
59
  models: cached.models.length,
50
60
  remainingTtlMs: cached.expiresAt - now
@@ -53,34 +63,43 @@ export class OpenCodeRunner extends AbstractRunner {
53
63
  this.contextLimitsExpiresAt = cached.expiresAt;
54
64
  return { ...this.profile(capabilities), models: cached.models };
55
65
  }
66
+ if (now < (this.profileRetryAt.get(cacheKey) ?? 0))
67
+ return cached === undefined
68
+ ? this.profile(capabilities)
69
+ : { ...this.profile(capabilities), models: cached.models };
56
70
  const current = this.profileRefreshes.get(cacheKey);
57
71
  if (current !== undefined) {
58
72
  nodeLog('runner.opencode.profile.single-flight-joined', {});
59
73
  return current;
60
74
  }
61
- const refresh = this.fetchProfile(capabilities, environment, cacheKey).finally(() => {
75
+ const generation = this.profileGeneration;
76
+ const refresh = this.fetchProfile(capabilities, environment, cacheKey, generation, cached).finally(() => {
62
77
  if (this.profileRefreshes.get(cacheKey) === refresh)
63
78
  this.profileRefreshes.delete(cacheKey);
64
79
  });
65
80
  this.profileRefreshes.set(cacheKey, refresh);
66
81
  return refresh;
67
82
  }
68
- async fetchProfile(capabilities, environment, cacheKey) {
69
- const client = this.managedClient(environment);
83
+ async fetchProfile(capabilities, environment, cacheKey, generation, stale) {
84
+ const client = this.managedClient(environment, 'runner-profile');
70
85
  const startedAt = performance.now();
71
86
  try {
72
- const catalog = await client.providers(homedir());
87
+ const catalog = await withProfileTimeout(client.providers(homedir()));
73
88
  const models = openCodeProfileModels(catalog);
74
89
  const contextLimits = openCodeContextLimits(catalog);
75
90
  const expiresAt = Date.now() + PROFILE_ENTRY_TTL_MS;
91
+ if (generation !== this.profileGeneration)
92
+ return this.profile(capabilities);
76
93
  this.contextLimits = contextLimits;
77
94
  this.contextLimitsExpiresAt = expiresAt;
78
95
  this.profileCache.set(cacheKey, {
79
96
  expiresAt,
97
+ discardAt: Date.now() + PROFILE_STALE_TTL_MS,
80
98
  environmentFingerprint: cacheKey,
81
99
  models,
82
100
  contextLimits
83
101
  });
102
+ this.profileRetryAt.delete(cacheKey);
84
103
  nodeLog('runner.opencode.profile.providers.completed', {
85
104
  models: models.length,
86
105
  durationMs: Math.round(performance.now() - startedAt)
@@ -88,6 +107,9 @@ export class OpenCodeRunner extends AbstractRunner {
88
107
  return { ...this.profile(capabilities), models };
89
108
  }
90
109
  catch {
110
+ if (generation !== this.profileGeneration)
111
+ return this.profile(capabilities);
112
+ this.profileRetryAt.set(cacheKey, Date.now() + PROFILE_FAILURE_RETRY_MS);
91
113
  nodeLog('runner.opencode.profile.providers.failed', {
92
114
  durationMs: Math.round(performance.now() - startedAt)
93
115
  });
@@ -95,7 +117,9 @@ export class OpenCodeRunner extends AbstractRunner {
95
117
  finally {
96
118
  this.releaseManagedClient(client);
97
119
  }
98
- return this.profile(capabilities);
120
+ return stale === undefined
121
+ ? this.profile(capabilities)
122
+ : { ...this.profile(capabilities), models: stale.models };
99
123
  }
100
124
  environmentFingerprint(environment) {
101
125
  const entries = Object.entries(environment).sort(([left], [right]) => left.localeCompare(right));
@@ -135,12 +159,23 @@ export class OpenCodeRunner extends AbstractRunner {
135
159
  };
136
160
  }
137
161
  cachedModels(environment) {
162
+ this.pruneProfileCaches(Date.now());
138
163
  const fingerprint = this.environmentFingerprint(environment);
139
- const now = Date.now();
140
164
  return [...this.profileCache.values()]
141
- .filter((entry) => entry.expiresAt > now && entry.environmentFingerprint === fingerprint)
165
+ .filter((entry) => entry.environmentFingerprint === fingerprint)
142
166
  .flatMap((entry) => entry.models);
143
167
  }
168
+ pruneProfileCaches(now) {
169
+ for (const [key, entry] of this.profileCache) {
170
+ if (entry.discardAt > now)
171
+ continue;
172
+ this.profileCache.delete(key);
173
+ this.profileRetryAt.delete(key);
174
+ }
175
+ for (const [key, retryAt] of this.profileRetryAt)
176
+ if (retryAt <= now && !this.profileCache.has(key) && !this.profileRefreshes.has(key))
177
+ this.profileRetryAt.delete(key);
178
+ }
144
179
  mcpConfiguration(raw, secrets) {
145
180
  const servers = super.mcpConfiguration(raw, secrets);
146
181
  return Object.fromEntries(servers.map((server) => [
@@ -300,12 +335,14 @@ export class OpenCodeRunner extends AbstractRunner {
300
335
  return maxTokens === undefined ? undefined : { model: modelId, usedTokens, maxTokens };
301
336
  }
302
337
  async stop() {
338
+ this.profileGeneration += 1;
303
339
  for (const execution of this.execution.values())
304
340
  execution.abort.abort();
305
341
  this.execution.clear();
306
342
  this.managedTurns.clear();
307
343
  this.profileCache.clear();
308
344
  this.profileRefreshes.clear();
345
+ this.profileRetryAt.clear();
309
346
  this.contextLimits = new Map();
310
347
  this.contextLimitsExpiresAt = 0;
311
348
  const stopping = [
@@ -351,6 +388,22 @@ export class OpenCodeRunner extends AbstractRunner {
351
388
  }
352
389
  }
353
390
  }
391
+ async function withProfileTimeout(operation) {
392
+ let timer;
393
+ try {
394
+ return await Promise.race([
395
+ operation,
396
+ new Promise((_resolve, reject) => {
397
+ timer = setTimeout(() => reject(new Error('OPENCODE_PROFILE_REFRESH_TIMEOUT')), PROFILE_REFRESH_TIMEOUT_MS);
398
+ timer.unref();
399
+ })
400
+ ]);
401
+ }
402
+ finally {
403
+ if (timer !== undefined)
404
+ clearTimeout(timer);
405
+ }
406
+ }
354
407
  function object(value) {
355
408
  return typeof value === 'object' && value !== null && !Array.isArray(value)
356
409
  ? value
@@ -29,17 +29,17 @@ export class RunnerRegistry {
29
29
  const runner = this.runners.get(name);
30
30
  return runner?.advertised === true ? runner : undefined;
31
31
  }
32
- profiles(capabilities) {
32
+ profiles(capabilities, environment = {}) {
33
33
  return this.advertised().flatMap((runner) => {
34
- const profile = runner.profile(capabilities);
34
+ const profile = runner.cachedProfile(capabilities, environment);
35
35
  return profile === undefined ? [] : [profile];
36
36
  });
37
37
  }
38
- async refreshProfiles(capabilities, workspace, environment = {}) {
38
+ async refreshProfiles(capabilities, workspace, environment = {}, waitForRefresh = false) {
39
39
  const profiles = await Promise.all(this.advertised().map(async (runner) => {
40
40
  const startedAt = performance.now();
41
41
  try {
42
- return await runner.refreshProfile(capabilities, workspace, environment);
42
+ return await runner.refreshProfile(capabilities, workspace, environment, waitForRefresh);
43
43
  }
44
44
  finally {
45
45
  const durationMs = Math.round(performance.now() - startedAt);
@@ -98,7 +98,11 @@ export class GoalMcpServer {
98
98
  throw new Error('GOAL_STATUS_INVALID');
99
99
  const goal = this.goals.update(capability.sessionId, capability.goalId, capability.revision, status);
100
100
  this.changed(capability.sessionId);
101
- socket.end(`${JSON.stringify({ result: goal })}\n`);
101
+ socket.end(`${JSON.stringify({
102
+ result: goal.status === 'active'
103
+ ? goal
104
+ : { ...goal, elapsedWallMs: this.goals.elapsedWallMs(goal) }
105
+ })}\n`);
102
106
  }
103
107
  catch (error) {
104
108
  socket.end(`${JSON.stringify({ error: error instanceof Error ? error.message : 'GOAL_IPC_FAILED' })}\n`);
@@ -72,10 +72,12 @@ export function publicGoalHistoryText(text) {
72
72
  }
73
73
  export class GoalRuntime {
74
74
  changed;
75
+ now;
75
76
  goals = new Map();
76
77
  executionFailures = new Map();
77
- constructor(changed) {
78
+ constructor(changed, now = Date.now) {
78
79
  this.changed = changed;
80
+ this.now = now;
79
81
  }
80
82
  get(sessionId) {
81
83
  return this.goals.get(sessionId);
@@ -104,6 +106,7 @@ export class GoalRuntime {
104
106
  id: continuing ? current.id : randomUUID(),
105
107
  sessionId,
106
108
  objective: normalized,
109
+ startedAt: continuing ? current.startedAt : this.now(),
107
110
  revision: continuing ? current.revision + 1 : 1,
108
111
  status: 'active',
109
112
  turns: 0,
@@ -117,6 +120,15 @@ export class GoalRuntime {
117
120
  this.changed(goal);
118
121
  return goal;
119
122
  }
123
+ elapsedWallMs(goal) {
124
+ return Math.max(0, this.now() - goal.startedAt);
125
+ }
126
+ exitSummary(sessionId, status) {
127
+ const goal = this.get(sessionId);
128
+ if (goal === undefined || goal.status !== 'active')
129
+ return undefined;
130
+ return { status, elapsedWallMs: this.elapsedWallMs(goal), turns: goal.turns };
131
+ }
120
132
  recordTurn(sessionId) {
121
133
  const current = this.goals.get(sessionId);
122
134
  if (current?.status !== 'active')
@@ -235,7 +247,7 @@ export class GoalRuntime {
235
247
  const heading = kind === 'start' ? 'A Goal has started or changed.' : 'Continue the active Goal.';
236
248
  const open = kind === 'start' ? START_OPEN : CONTINUE_OPEN;
237
249
  const close = kind === 'start' ? START_CLOSE : CONTINUE_CLOSE;
238
- return `${open}\n${heading}\n\nOriginal objective:\n${goal.objective}\n\nContinue executing this Goal across turns:\n- Work on the objective now. If it is incomplete, use tools to finish a coherent unit or obtain decisive evidence; do not stop at an acknowledgement, status, plan, or promise.\n- Preserve the original scope. Before completion, review the full objective and verify it with real evidence, then call mar_update_goal with status=completed.\n- Adapt and retry after individual failures. Request blocked only after the same blocker persists for three turns and the required additional retry.\n- Use mar_get_goal only when you need to reread the authoritative objective or status.\n${close}`;
250
+ return `${open}\n${heading}\n\nOriginal objective:\n${goal.objective}\n\nContinue executing this Goal across turns:\n- Work on the objective now. If it is incomplete, use tools to finish a coherent unit or obtain decisive evidence; do not stop at an acknowledgement, status, plan, or promise.\n- Preserve the original scope. Before completion, review the full objective and verify it with real evidence, then call mar_update_goal with status=completed.\n- Adapt and retry after individual failures. Request blocked only after the same blocker persists for three turns and the required additional retry.\n- Use mar_get_goal only when you need to reread the authoritative objective or status.\n- After mar_update_goal confirms completed or finally blocked, summarize the completed work, verification evidence, remaining issues, and the tool-reported elapsedWallMs converted from milliseconds into a concise readable duration in your final user-facing response. Name whether the Goal completed or is finally blocked. That elapsed time is wall-clock time through the status update, including waiting and retries, not tool execution time or time through this final response. If blocked returns active for the required retry, continue work instead of claiming a final summary.\n${close}`;
239
251
  }
240
252
  requireCurrent(sessionId, goalId, revision) {
241
253
  const current = this.goals.get(sessionId);
@@ -67,6 +67,7 @@ export class RunWorkbenchService {
67
67
  const executionId = this.options.runtime.currentExecutionId(run.id);
68
68
  const turn = this.options.runtime.conversationTurnForRun(run.id);
69
69
  const controlRunId = executionId ?? (turn === undefined || turn.userItemId === null ? run.id : undefined);
70
+ let interruptionResult;
70
71
  try {
71
72
  this.options.pauseQueue(run.sessionId);
72
73
  if (controlRunId === undefined) {
@@ -79,9 +80,9 @@ export class RunWorkbenchService {
79
80
  }
80
81
  }
81
82
  finally {
82
- this.options.runInterrupted(run.sessionId);
83
+ interruptionResult = this.options.runInterrupted(run.sessionId);
83
84
  }
84
- return { run: next };
85
+ return { run: next, ...interruptionResult };
85
86
  }
86
87
  decideApproval(input) {
87
88
  if (typeof input.runId !== 'string' ||
@@ -19,11 +19,11 @@ export class RunnerService {
19
19
  'runner.defaults.save': (data) => this.saveDefaults(data)
20
20
  };
21
21
  }
22
- profiles() {
23
- return this.runners.profiles(this.capabilities());
22
+ profiles(environment = {}) {
23
+ return this.runners.profiles(this.capabilities(), environment);
24
24
  }
25
- refreshProfiles(environment = {}) {
26
- return this.runners.refreshProfiles(this.capabilities(), undefined, environment);
25
+ refreshProfiles(environment = {}, waitForRefresh = false) {
26
+ return this.runners.refreshProfiles(this.capabilities(), undefined, environment, waitForRefresh);
27
27
  }
28
28
  async usageStatuses() {
29
29
  return {
@@ -61,8 +61,18 @@ export class RunnerService {
61
61
  this.database().saveRunnerDefaults(normalized);
62
62
  return normalized;
63
63
  });
64
+ const cacheOnly = input.waitForRefresh === false;
65
+ const waitForRefresh = !cacheOnly;
66
+ const refresh = this.refreshProfiles(environment, waitForRefresh);
67
+ if (cacheOnly) {
68
+ void refresh.catch(() => undefined);
69
+ return {
70
+ profiles: this.profiles(environment),
71
+ defaults
72
+ };
73
+ }
64
74
  return {
65
- profiles: await this.refreshProfiles(environment),
75
+ profiles: await refresh,
66
76
  defaults
67
77
  };
68
78
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.9.117",
3
+ "version": "0.9.119",
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.117",
34
- "@myagentroam/code-navigation": "0.9.117",
35
- "@myagentroam/protocol": "0.9.117"
33
+ "@myagentroam/protocol": "0.9.119",
34
+ "@myagentroam/agent": "0.9.119",
35
+ "@myagentroam/code-navigation": "0.9.119"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/ws": "^8.18.1"