@myagentroam/node 0.9.118 → 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 +14 -5
- package/dist/runner/abstract-runner.js +6 -2
- package/dist/runner/codex-runner.js +27 -6
- package/dist/runner/mar-agent/conversation-normalizer.js +11 -5
- package/dist/runner/opencode-runner.js +65 -12
- package/dist/runner/runner-registry.js +4 -4
- package/dist/service/goal-mcp-server.js +5 -1
- package/dist/service/goal-runtime.js +14 -2
- package/dist/service/run-workbench-service.js +3 -2
- package/dist/service/runner-service.js +15 -5
- package/package.json +4 -4
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) =>
|
|
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 {
|
|
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
|
-
|
|
16
|
-
void
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
80
|
-
|
|
81
|
-
this.
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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({
|
|
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
|
|
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.
|
|
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/
|
|
34
|
-
"@myagentroam/
|
|
35
|
-
"@myagentroam/
|
|
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"
|