@myagentroam/node 0.9.157 → 0.9.159
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/opencode-server.js +12 -1
- package/dist/runner/mar-agent/managed-run-controller.js +18 -3
- package/dist/runner/mar-agent-runner.js +25 -5
- package/dist/runner/opencode/conversation-parser.js +26 -0
- package/dist/runner/opencode-runner.js +80 -3
- package/dist/runtime-state.js +6 -4
- package/package.json +3 -3
package/dist/opencode-server.js
CHANGED
|
@@ -93,6 +93,10 @@ export class OpenCodeServerClient {
|
|
|
93
93
|
const client = await this.requireClient();
|
|
94
94
|
return unwrap(await client.session.list({ directory, limit }));
|
|
95
95
|
}
|
|
96
|
+
async session(directory, sessionID) {
|
|
97
|
+
const client = await this.requireClient();
|
|
98
|
+
return unwrap(await client.session.get({ directory, sessionID }));
|
|
99
|
+
}
|
|
96
100
|
async createSession(directory, title) {
|
|
97
101
|
const client = await this.requireClient();
|
|
98
102
|
return unwrap(await client.session.create({ directory, ...(title === undefined ? {} : { title }) }));
|
|
@@ -102,8 +106,15 @@ export class OpenCodeServerClient {
|
|
|
102
106
|
return unwrap(await client.session.fork({ directory, sessionID, messageID }));
|
|
103
107
|
}
|
|
104
108
|
async messages(directory, sessionID, limit = 100) {
|
|
109
|
+
return (await this.messagePage(directory, sessionID, { limit })).messages;
|
|
110
|
+
}
|
|
111
|
+
async messagePage(directory, sessionID, input) {
|
|
105
112
|
const client = await this.requireClient();
|
|
106
|
-
|
|
113
|
+
const result = await client.session.messages({ directory, sessionID, ...input });
|
|
114
|
+
return {
|
|
115
|
+
messages: unwrap(result),
|
|
116
|
+
nextCursor: result.response.headers.get('x-next-cursor') || null
|
|
117
|
+
};
|
|
107
118
|
}
|
|
108
119
|
async updateSession(directory, sessionID, title) {
|
|
109
120
|
const client = await this.requireClient();
|
|
@@ -249,7 +249,6 @@ export class MarAgentManagedRunController {
|
|
|
249
249
|
if (controller.signal.aborted)
|
|
250
250
|
return;
|
|
251
251
|
controller.abort();
|
|
252
|
-
titleController.abort();
|
|
253
252
|
cancelExecution?.();
|
|
254
253
|
},
|
|
255
254
|
cancelTitle: () => titleController.abort()
|
|
@@ -434,7 +433,16 @@ export class MarAgentManagedRunController {
|
|
|
434
433
|
currentModelId: input.model.id,
|
|
435
434
|
prompt: input.input,
|
|
436
435
|
signal: titleController.signal
|
|
437
|
-
}).catch(() =>
|
|
436
|
+
}).catch((error) => {
|
|
437
|
+
if (!titleController.signal.aborted)
|
|
438
|
+
nodeLog('runner.mar-agent.title.failed', {
|
|
439
|
+
sessionId: session.id,
|
|
440
|
+
runId: input.runId,
|
|
441
|
+
stage: 'generate',
|
|
442
|
+
code: runnerErrorProjection(error, 'MAR_AGENT_TITLE_GENERATION_FAILED').code
|
|
443
|
+
});
|
|
444
|
+
return undefined;
|
|
445
|
+
});
|
|
438
446
|
runtime.automaticTitleTask = { cancel: () => titleController.abort(), result: titleTask };
|
|
439
447
|
}
|
|
440
448
|
this.project(input.runId, event);
|
|
@@ -448,7 +456,14 @@ export class MarAgentManagedRunController {
|
|
|
448
456
|
expectedTitle: metadata.title,
|
|
449
457
|
executionId: handle.executionId
|
|
450
458
|
})
|
|
451
|
-
.catch(() =>
|
|
459
|
+
.catch((error) => {
|
|
460
|
+
nodeLog('runner.mar-agent.title.failed', {
|
|
461
|
+
sessionId: session.id,
|
|
462
|
+
runId: input.runId,
|
|
463
|
+
stage: 'persist',
|
|
464
|
+
code: runnerErrorProjection(error, 'MAR_AGENT_TITLE_PERSIST_FAILED').code
|
|
465
|
+
});
|
|
466
|
+
});
|
|
452
467
|
await this.host.syncTitle(session.id, input.cwd);
|
|
453
468
|
const status = result.status === 'completed'
|
|
454
469
|
? 'SUCCEEDED'
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { marAgentNodeModelSummarySchema, resolveMarAgentDefaultReasoningEffort } from '@myagentroam/protocol';
|
|
2
|
+
import { nodeLog } from '../operational.js';
|
|
3
|
+
import { runnerErrorProjection } from '../util/runner-error.js';
|
|
2
4
|
import { MarAgentError, openMarAgentSessionCatalog } from '@myagentroam/agent';
|
|
3
5
|
import { homedir } from 'node:os';
|
|
4
6
|
import { resolve } from 'node:path';
|
|
@@ -231,6 +233,7 @@ export class MarAgentRunner extends AbstractRunner {
|
|
|
231
233
|
const executionConfiguration = session.modelId === undefined
|
|
232
234
|
? await activeCatalog.executionConfiguration(session.id)
|
|
233
235
|
: session;
|
|
236
|
+
const title = await marAgentSessionTitle(session, activeCatalog);
|
|
234
237
|
return [
|
|
235
238
|
{
|
|
236
239
|
...context.createProjection({
|
|
@@ -238,8 +241,7 @@ export class MarAgentRunner extends AbstractRunner {
|
|
|
238
241
|
runner: this.name,
|
|
239
242
|
externalSessionId: session.id,
|
|
240
243
|
cwd: session.workspace,
|
|
241
|
-
title
|
|
242
|
-
titleOrigin: 'NATIVE',
|
|
244
|
+
...title,
|
|
243
245
|
lastActivityAt: Date.parse(session.lastActivityAt)
|
|
244
246
|
}),
|
|
245
247
|
...(executionConfiguration?.modelId === undefined
|
|
@@ -336,9 +338,9 @@ export class MarAgentRunner extends AbstractRunner {
|
|
|
336
338
|
let cursor;
|
|
337
339
|
do {
|
|
338
340
|
const page = await catalog.list({ limit: 200, ...(cursor ? { cursor } : {}) });
|
|
339
|
-
const
|
|
340
|
-
if (
|
|
341
|
-
return title;
|
|
341
|
+
const summary = page.sessions.find((candidate) => candidate.id === nativeSessionId);
|
|
342
|
+
if (summary !== undefined)
|
|
343
|
+
return (await marAgentSessionTitle(summary, catalog)).title;
|
|
342
344
|
cursor = page.nextCursor;
|
|
343
345
|
} while (cursor !== undefined);
|
|
344
346
|
return undefined;
|
|
@@ -456,6 +458,24 @@ export class MarAgentRunner extends AbstractRunner {
|
|
|
456
458
|
}
|
|
457
459
|
}
|
|
458
460
|
}
|
|
461
|
+
async function marAgentSessionTitle(session, catalog) {
|
|
462
|
+
const nativeTitle = session.title.trim();
|
|
463
|
+
if (nativeTitle.length === 0 || /^(?:new|untitled) session$/iu.test(nativeTitle)) {
|
|
464
|
+
try {
|
|
465
|
+
const preview = session.firstUserMessagePreview ?? (await catalog.firstUserMessagePreview(session.id));
|
|
466
|
+
if (preview)
|
|
467
|
+
return { title: preview, titleOrigin: 'PREVIEW' };
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
nodeLog('runner.mar-agent.title.failed', {
|
|
471
|
+
sessionId: session.id,
|
|
472
|
+
stage: 'preview',
|
|
473
|
+
code: runnerErrorProjection(error, 'MAR_AGENT_TITLE_PREVIEW_FAILED').code
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return { title: session.title, titleOrigin: 'NATIVE' };
|
|
478
|
+
}
|
|
459
479
|
function firstSequenceForTurn(entries, turnId) {
|
|
460
480
|
return entries.find((entry) => entry.kind === 'user' ? entry.turnId === turnId : entry.event.turnId === turnId)?.sequence;
|
|
461
481
|
}
|
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
import { boundedConversationItemId, compactRunnerText } from '../codex/conversation-parser.js';
|
|
2
2
|
import { stripGoalStartPrompt } from '../../service/goal-runtime.js';
|
|
3
3
|
import { normalizeOpenCodeTool } from './tool-normalizer.js';
|
|
4
|
+
export function openCodeUserMessagePreview(messages) {
|
|
5
|
+
for (const message of messages) {
|
|
6
|
+
const entry = record(message);
|
|
7
|
+
const info = record(entry?.info);
|
|
8
|
+
if (info?.role !== 'user' || typeof info.id !== 'string')
|
|
9
|
+
continue;
|
|
10
|
+
const parts = array(entry?.parts);
|
|
11
|
+
if (parts.some((part) => record(part)?.type === 'compaction'))
|
|
12
|
+
continue;
|
|
13
|
+
const text = parts
|
|
14
|
+
.flatMap((part) => {
|
|
15
|
+
const value = record(part);
|
|
16
|
+
return value?.type === 'text' &&
|
|
17
|
+
value.synthetic !== true &&
|
|
18
|
+
value.ignored !== true &&
|
|
19
|
+
typeof value.text === 'string'
|
|
20
|
+
? [value.text]
|
|
21
|
+
: [];
|
|
22
|
+
})
|
|
23
|
+
.join('\n');
|
|
24
|
+
const preview = stripGoalStartPrompt(text).trim().replace(/\s+/gu, ' ').slice(0, 160);
|
|
25
|
+
if (preview)
|
|
26
|
+
return preview;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
4
30
|
export function openCodeConversationPage(session, messages, input, managedTurn) {
|
|
5
31
|
const records = messages.flatMap((value) => {
|
|
6
32
|
const entry = record(value);
|
|
@@ -5,11 +5,14 @@ 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 { homedir } from 'node:os';
|
|
8
|
-
import { openCodeConversationPage } from './opencode/conversation-parser.js';
|
|
8
|
+
import { openCodeConversationPage, openCodeUserMessagePreview } from './opencode/conversation-parser.js';
|
|
9
9
|
import { nodeLog } from '../operational.js';
|
|
10
|
+
import { runnerErrorProjection } from '../util/runner-error.js';
|
|
10
11
|
const PROFILE_ENTRY_TTL_MS = 30 * 60_000;
|
|
11
12
|
const PROFILE_FAILURE_RETRY_MS = 30_000;
|
|
12
13
|
const PROFILE_REFRESH_TIMEOUT_MS = 90_000;
|
|
14
|
+
const TITLE_PREVIEW_TTL_MS = 60 * 60_000;
|
|
15
|
+
const MAX_TITLE_PREVIEWS = 256;
|
|
13
16
|
export class OpenCodeRunner extends AbstractRunner {
|
|
14
17
|
client;
|
|
15
18
|
clientFactory;
|
|
@@ -24,6 +27,7 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
24
27
|
profileGeneration = 0;
|
|
25
28
|
execution = new Map();
|
|
26
29
|
managedTurns = new Map();
|
|
30
|
+
titlePreviews = new Map();
|
|
27
31
|
environmentClients = new Map();
|
|
28
32
|
stoppingClients = new Set();
|
|
29
33
|
constructor(client = new OpenCodeServerClient(), clientFactory = () => new OpenCodeServerClient()) {
|
|
@@ -198,19 +202,91 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
198
202
|
session.parentId !== undefined ||
|
|
199
203
|
!isWithinWorkspace(session.directory, target.path))
|
|
200
204
|
continue;
|
|
205
|
+
const title = await this.sessionTitle(session);
|
|
201
206
|
result.push(context.createProjection({
|
|
202
207
|
workspace: target,
|
|
203
208
|
runner: this.name,
|
|
204
209
|
externalSessionId: session.id,
|
|
205
210
|
cwd: session.directory,
|
|
206
|
-
title:
|
|
207
|
-
titleOrigin:
|
|
211
|
+
title: title.title,
|
|
212
|
+
titleOrigin: title.titleOrigin,
|
|
208
213
|
lastActivityAt: session.updatedAt
|
|
209
214
|
}));
|
|
210
215
|
}
|
|
211
216
|
}
|
|
212
217
|
return result;
|
|
213
218
|
}
|
|
219
|
+
async readManagedRunnerTitle(session) {
|
|
220
|
+
if (session.externalSessionId === null)
|
|
221
|
+
return undefined;
|
|
222
|
+
const native = openCodeSession(await this.client.session(session.cwd, session.externalSessionId));
|
|
223
|
+
if (native === undefined ||
|
|
224
|
+
native.id !== session.externalSessionId ||
|
|
225
|
+
!isWithinWorkspace(native.directory, session.cwd))
|
|
226
|
+
return undefined;
|
|
227
|
+
return (await this.sessionTitle(native)).title;
|
|
228
|
+
}
|
|
229
|
+
async sessionTitle(session) {
|
|
230
|
+
const nativeTitle = session.title.trim();
|
|
231
|
+
if (nativeTitle.length === 0 ||
|
|
232
|
+
/^(?:new|untitled) session(?:\s*[-–—:]\s*\d{4}-\d{2}-\d{2}.*)?$/iu.test(nativeTitle)) {
|
|
233
|
+
try {
|
|
234
|
+
const preview = await this.firstUserMessagePreview(session);
|
|
235
|
+
if (preview)
|
|
236
|
+
return { title: preview, titleOrigin: 'PREVIEW' };
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
nodeLog('runner.opencode.title.failed', {
|
|
240
|
+
sessionId: session.id,
|
|
241
|
+
stage: 'preview',
|
|
242
|
+
code: runnerErrorProjection(error, 'OPENCODE_TITLE_PREVIEW_FAILED').code
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return { title: session.title, titleOrigin: 'OFFICIAL' };
|
|
247
|
+
}
|
|
248
|
+
async firstUserMessagePreview(session) {
|
|
249
|
+
const key = `${session.directory}\0${session.id}`;
|
|
250
|
+
const cached = this.titlePreviews.get(key);
|
|
251
|
+
if (cached !== undefined &&
|
|
252
|
+
cached.updatedAt === session.updatedAt &&
|
|
253
|
+
cached.expiresAt > Date.now())
|
|
254
|
+
return cached.result;
|
|
255
|
+
this.titlePreviews.delete(key);
|
|
256
|
+
if (this.titlePreviews.size >= MAX_TITLE_PREVIEWS)
|
|
257
|
+
this.titlePreviews.delete(this.titlePreviews.keys().next().value);
|
|
258
|
+
const entry = {
|
|
259
|
+
updatedAt: session.updatedAt,
|
|
260
|
+
expiresAt: Date.now() + TITLE_PREVIEW_TTL_MS,
|
|
261
|
+
result: (async () => {
|
|
262
|
+
let before;
|
|
263
|
+
let preview;
|
|
264
|
+
const cursors = new Set();
|
|
265
|
+
for (;;) {
|
|
266
|
+
const page = await this.client.messagePage(session.directory, session.id, {
|
|
267
|
+
limit: 100,
|
|
268
|
+
...(before === undefined ? {} : { before })
|
|
269
|
+
});
|
|
270
|
+
preview = openCodeUserMessagePreview(page.messages) ?? preview;
|
|
271
|
+
if (page.nextCursor === null)
|
|
272
|
+
return preview;
|
|
273
|
+
if (cursors.has(page.nextCursor))
|
|
274
|
+
throw new Error('OPENCODE_MESSAGE_CURSOR_INVALID');
|
|
275
|
+
cursors.add(page.nextCursor);
|
|
276
|
+
before = page.nextCursor;
|
|
277
|
+
}
|
|
278
|
+
})()
|
|
279
|
+
};
|
|
280
|
+
this.titlePreviews.set(key, entry);
|
|
281
|
+
try {
|
|
282
|
+
return await entry.result;
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
if (this.titlePreviews.get(key) === entry)
|
|
286
|
+
this.titlePreviews.delete(key);
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
214
290
|
async resumeExternal(external, context) {
|
|
215
291
|
if (external.runner !== this.name || external.externalSessionId === null || !context.available)
|
|
216
292
|
return { failureCode: 'RUNNER_UNAVAILABLE' };
|
|
@@ -328,6 +404,7 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
328
404
|
execution.abort.abort();
|
|
329
405
|
this.execution.clear();
|
|
330
406
|
this.managedTurns.clear();
|
|
407
|
+
this.titlePreviews.clear();
|
|
331
408
|
this.profileCache = undefined;
|
|
332
409
|
this.profileRefresh = undefined;
|
|
333
410
|
if (this.profileRefreshTimer !== undefined)
|
package/dist/runtime-state.js
CHANGED
|
@@ -268,14 +268,16 @@ export class NodeRuntimeState {
|
|
|
268
268
|
});
|
|
269
269
|
}
|
|
270
270
|
/**
|
|
271
|
-
* Give a
|
|
272
|
-
* Runner has
|
|
273
|
-
*
|
|
271
|
+
* Give a new or restored managed Session a readable local title while the
|
|
272
|
+
* Runner still has a placeholder. Never replace a user rename or a
|
|
273
|
+
* non-placeholder Runner title.
|
|
274
274
|
*/
|
|
275
275
|
setTemporaryTitleIfMissing(id, title) {
|
|
276
276
|
const current = this.requireSession(id);
|
|
277
277
|
const value = title.trim().replace(/\s+/g, ' ').slice(0, 160);
|
|
278
|
-
if (value.length === 0 ||
|
|
278
|
+
if (value.length === 0 ||
|
|
279
|
+
current.customTitle !== null ||
|
|
280
|
+
(current.runnerTitle !== null && !isDefaultRunnerTitle(current.runnerTitle)))
|
|
279
281
|
return current;
|
|
280
282
|
return this.replaceSession(id, { runnerTitle: value, titleSource: 'AUTO' });
|
|
281
283
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.159",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"license": "Apache-2.0 AND GPL-2.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"playwright": "1.62.1",
|
|
37
37
|
"ws": "^8.21.3",
|
|
38
38
|
"zod": "4.4.3",
|
|
39
|
-
"@myagentroam/agent": "0.9.
|
|
39
|
+
"@myagentroam/agent": "0.9.159",
|
|
40
40
|
"@myagentroam/code-navigation": "0.9.129",
|
|
41
41
|
"@myagentroam/marctl": "0.9.120",
|
|
42
|
-
"@myagentroam/protocol": "0.9.
|
|
42
|
+
"@myagentroam/protocol": "0.9.159"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/ws": "^8.18.1"
|