@myagentroam/node 0.11.18 → 0.11.19
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/capabilities.js +3 -0
- package/dist/connector.js +35 -2
- package/dist/database.js +8 -1
- package/dist/runtime-state.js +3 -0
- package/dist/service/node-control-message-service.js +4 -0
- package/dist/service/notification-mcp-bridge.js +2 -1
- package/dist/service/prompt-completion-service.js +5 -1
- package/dist/service/recent-run-projection-service.js +7 -0
- package/dist/service/run-event-service.js +2 -0
- package/dist/service/scheduled-session-coordinator.js +2 -0
- package/dist/service/session-catalog-service.js +22 -10
- package/dist/service/session-lifecycle-service.js +28 -0
- package/dist/service/session-presentation-service.js +5 -1
- package/dist/service/workbench-manifest-service.js +1 -0
- package/dist/service/workspace-watch-service.js +4 -1
- package/dist/service/workspace-watch-snapshot-service.js +7 -5
- package/dist/terminal.js +1 -1
- package/package.json +3 -3
package/dist/capabilities.js
CHANGED
|
@@ -60,6 +60,7 @@ export function unavailableCapabilities() {
|
|
|
60
60
|
promptCompletion: { apiVersion: 1, contextVersion: 3, automaticTriggers: true },
|
|
61
61
|
chatgptLogin: { apiVersion: 1, loopback: true },
|
|
62
62
|
workspaceDirectTransfer: workspaceDirectTransferCapability(),
|
|
63
|
+
sessionGroups: { apiVersion: 1 },
|
|
63
64
|
terminalDirect: terminalDirectCapability()
|
|
64
65
|
};
|
|
65
66
|
}
|
|
@@ -96,6 +97,7 @@ export function detectCapabilities(commands = {}) {
|
|
|
96
97
|
promptCompletion: { apiVersion: 1, contextVersion: 3, automaticTriggers: true },
|
|
97
98
|
chatgptLogin: { apiVersion: 1, loopback: true },
|
|
98
99
|
workspaceDirectTransfer: workspaceDirectTransferCapability(),
|
|
100
|
+
sessionGroups: { apiVersion: 1 },
|
|
99
101
|
terminalDirect: terminalDirectCapability()
|
|
100
102
|
};
|
|
101
103
|
}
|
|
@@ -136,6 +138,7 @@ export async function detectCapabilitiesAsync(commands = {}) {
|
|
|
136
138
|
promptCompletion: { apiVersion: 1, contextVersion: 3, automaticTriggers: true },
|
|
137
139
|
chatgptLogin: { apiVersion: 1, loopback: true },
|
|
138
140
|
workspaceDirectTransfer: workspaceDirectTransferCapability(),
|
|
141
|
+
sessionGroups: { apiVersion: 1 },
|
|
139
142
|
terminalDirect: terminalDirectCapability()
|
|
140
143
|
};
|
|
141
144
|
}
|
package/dist/connector.js
CHANGED
|
@@ -46,6 +46,7 @@ import { WorkspaceSessionService } from './service/workspace-session-service.js'
|
|
|
46
46
|
import { WorkbenchEventService } from './service/workbench-event-service.js';
|
|
47
47
|
import { RunDomainState } from './service/run-domain-state.js';
|
|
48
48
|
import { RecentRunProjectionService } from './service/recent-run-projection-service.js';
|
|
49
|
+
import { sessionGroupChangedSchema } from '@myagentroam/protocol';
|
|
49
50
|
import { SessionDomainState } from './service/session-domain-state.js';
|
|
50
51
|
import { RunEventService } from './service/run-event-service.js';
|
|
51
52
|
import { RunAttachmentService } from './service/run-attachment-service.js';
|
|
@@ -79,7 +80,7 @@ import { MailNotificationControl } from './service/mail-notification-control.js'
|
|
|
79
80
|
import { NotificationMcpBridge, resolveNotificationCapability } from './service/notification-mcp-bridge.js';
|
|
80
81
|
import { SessionCatalogService } from './service/session-catalog-service.js';
|
|
81
82
|
import { SessionCommandService } from './service/session-command-service.js';
|
|
82
|
-
import { SessionPresentationService } from './service/session-presentation-service.js';
|
|
83
|
+
import { SessionPresentationService, sessionGroup } from './service/session-presentation-service.js';
|
|
83
84
|
import { isPlainRecord, isRunnerImageAttachment, parseRunnerImageAttachments } from './util/node-operation-parsers.js';
|
|
84
85
|
import { WebToolService } from './service/web-tool-service.js';
|
|
85
86
|
export { nodeConnectEndpoint, validatedCapabilities } from './util/node-operation-parsers.js';
|
|
@@ -172,6 +173,7 @@ export class NodeConnector {
|
|
|
172
173
|
restartAuthorization;
|
|
173
174
|
restartHandoff;
|
|
174
175
|
inFlightOperations = new Set();
|
|
176
|
+
pendingSessionGroups = new Map();
|
|
175
177
|
mailNotificationControl = new MailNotificationControl((envelope) => this.sendEnvelope(envelope));
|
|
176
178
|
scheduledSessions;
|
|
177
179
|
nodeRequests;
|
|
@@ -907,6 +909,17 @@ export class NodeConnector {
|
|
|
907
909
|
emitTurn: (turn) => this.emitConversationTurn(turn)
|
|
908
910
|
});
|
|
909
911
|
this.sessionLifecycleService = new SessionLifecycleService({
|
|
912
|
+
parentCleared: (session) => {
|
|
913
|
+
this.recentRunProjectionService.clearSessionParent(session.workspaceId, session.id);
|
|
914
|
+
const change = {
|
|
915
|
+
nodeId: session.nodeId,
|
|
916
|
+
workspaceId: session.workspaceId,
|
|
917
|
+
sessionId: session.id,
|
|
918
|
+
sessionGroup: 'MAIN'
|
|
919
|
+
};
|
|
920
|
+
this.pendingSessionGroups.set(session.id, change);
|
|
921
|
+
this.send('session.group.changed', change);
|
|
922
|
+
},
|
|
910
923
|
database: () => {
|
|
911
924
|
if (this.database === undefined)
|
|
912
925
|
throw new Error('NODE_DATABASE_UNAVAILABLE');
|
|
@@ -1176,8 +1189,19 @@ export class NodeConnector {
|
|
|
1176
1189
|
await this.connectionLifecycle.register(nodeId, credential);
|
|
1177
1190
|
this.mailNotificationControl.setFeatures(payload);
|
|
1178
1191
|
this.usageOutbox?.connected(payload);
|
|
1192
|
+
for (const change of this.pendingSessionGroups.values())
|
|
1193
|
+
this.send('session.group.changed', change);
|
|
1179
1194
|
},
|
|
1180
1195
|
usageAck: (payload) => this.usageOutbox?.handle(createEnvelope('usage.ack', payload)),
|
|
1196
|
+
sessionGroupAck: (payload) => {
|
|
1197
|
+
const parsed = sessionGroupChangedSchema.safeParse(payload);
|
|
1198
|
+
if (!parsed.success)
|
|
1199
|
+
return;
|
|
1200
|
+
const pending = this.pendingSessionGroups.get(parsed.data.sessionId);
|
|
1201
|
+
if (pending?.nodeId === parsed.data.nodeId &&
|
|
1202
|
+
pending.workspaceId === parsed.data.workspaceId)
|
|
1203
|
+
this.pendingSessionGroups.delete(parsed.data.sessionId);
|
|
1204
|
+
},
|
|
1181
1205
|
bindUsage: (payload) => {
|
|
1182
1206
|
if (!isPlainRecord(payload) ||
|
|
1183
1207
|
typeof payload.runId !== 'string' ||
|
|
@@ -1189,6 +1213,8 @@ export class NodeConnector {
|
|
|
1189
1213
|
},
|
|
1190
1214
|
connected: (payload) => {
|
|
1191
1215
|
this.usageOutbox?.connected(payload);
|
|
1216
|
+
for (const change of this.pendingSessionGroups.values())
|
|
1217
|
+
this.send('session.group.changed', change);
|
|
1192
1218
|
this.connectionLifecycle.connected();
|
|
1193
1219
|
this.mailNotificationControl.setFeatures(payload);
|
|
1194
1220
|
},
|
|
@@ -1326,6 +1352,9 @@ export class NodeConnector {
|
|
|
1326
1352
|
const session = this.runtime.getAgentSession(sessionId);
|
|
1327
1353
|
return session?.customTitle ?? session?.runnerTitle ?? sessionId;
|
|
1328
1354
|
},
|
|
1355
|
+
sessionGroup: (task) => sessionGroup(this.runtime.getAgentSession(task.sessionId) ??
|
|
1356
|
+
this.database?.getNativeSessionMetadata(task.runner, task.sessionId, task.workspaceId) ??
|
|
1357
|
+
{}),
|
|
1329
1358
|
emitNotification: (payload) => this.emitWorkbenchEvent('conversation', payload)
|
|
1330
1359
|
});
|
|
1331
1360
|
this.runnerService = new RunnerService(requireDatabase, () => this.capabilities, this.runners, this.runnerUsageReader);
|
|
@@ -1357,7 +1386,11 @@ export class NodeConnector {
|
|
|
1357
1386
|
files: this.workspaceFileService,
|
|
1358
1387
|
uploads: this.workspaceUploadService,
|
|
1359
1388
|
changes: this.workspaceChangeService,
|
|
1360
|
-
listSessions: (workspaceId) => this.executeNodeOperation('session.list', {
|
|
1389
|
+
listSessions: (workspaceId, sessionGroup) => this.executeNodeOperation('session.list', {
|
|
1390
|
+
workspaceId,
|
|
1391
|
+
limit: 30,
|
|
1392
|
+
...(sessionGroup === undefined ? {} : { sessionGroup })
|
|
1393
|
+
}),
|
|
1361
1394
|
invalidate: this.invalidateWorkspaceFiles,
|
|
1362
1395
|
nodeId: () => this.config?.nodeId ?? 'runtime-node',
|
|
1363
1396
|
nodeGeneration: () => this.nodeGeneration,
|
package/dist/database.js
CHANGED
|
@@ -281,6 +281,13 @@ export class NodeDatabase {
|
|
|
281
281
|
pinOrder: nullableNumber(row, 'pin_order')
|
|
282
282
|
};
|
|
283
283
|
}
|
|
284
|
+
clearSessionParent(runner, externalSessionId, workspaceId) {
|
|
285
|
+
const result = this.raw
|
|
286
|
+
.prepare("UPDATE agent_sessions SET metadata = json_set(metadata, '$.parentSessionId', NULL) WHERE id = ? AND runner = ? AND workspace_id = ?")
|
|
287
|
+
.run(externalSessionId, runner, workspaceId);
|
|
288
|
+
if (result.changes !== 1)
|
|
289
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
290
|
+
}
|
|
284
291
|
saveNativeSessionMetadata(input) {
|
|
285
292
|
const current = this.getNativeSessionMetadata(input.runner, input.externalSessionId, input.workspaceId);
|
|
286
293
|
const customTitle = input.customTitle === undefined
|
|
@@ -308,7 +315,7 @@ export class NodeDatabase {
|
|
|
308
315
|
const model = input.model === undefined ? (current?.model ?? null) : input.model;
|
|
309
316
|
const effort = input.effort === undefined ? (current?.effort ?? null) : input.effort;
|
|
310
317
|
const access = input.access === undefined ? (current?.access ?? null) : input.access;
|
|
311
|
-
const parentSessionId = current
|
|
318
|
+
const parentSessionId = current === undefined ? (input.parentSessionId ?? null) : (current.parentSessionId ?? null);
|
|
312
319
|
const metadata = JSON.stringify({
|
|
313
320
|
version: 1,
|
|
314
321
|
parentSessionId,
|
package/dist/runtime-state.js
CHANGED
|
@@ -343,6 +343,9 @@ export class NodeRuntimeState {
|
|
|
343
343
|
titleSource: title === null ? 'AUTO' : 'CUSTOM'
|
|
344
344
|
});
|
|
345
345
|
}
|
|
346
|
+
clearSessionParent(sessionId) {
|
|
347
|
+
return this.replaceSession(this.requireSession(sessionId).id, { parentSessionId: null });
|
|
348
|
+
}
|
|
346
349
|
/**
|
|
347
350
|
* Give a new or restored managed Session a readable local title while the
|
|
348
351
|
* Runner still has a placeholder. Never replace a user rename or a
|
|
@@ -51,6 +51,10 @@ export class NodeControlMessageService {
|
|
|
51
51
|
}
|
|
52
52
|
if (envelope.type === 'ack')
|
|
53
53
|
return;
|
|
54
|
+
if (envelope.type === 'session.group.ack') {
|
|
55
|
+
this.options.sessionGroupAck?.(envelope.payload);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
54
58
|
if (envelope.type === 'run.approval.decision') {
|
|
55
59
|
this.decideApproval(envelope);
|
|
56
60
|
return;
|
|
@@ -7,6 +7,7 @@ import { tmpdir } from 'node:os';
|
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { notificationInputSchema, notificationChannelsRequestSchema, mailNotificationRequestSchema } from '@myagentroam/protocol';
|
|
10
|
+
import { sessionGroup } from './session-presentation-service.js';
|
|
10
11
|
export class NotificationMcpBridge {
|
|
11
12
|
resolve;
|
|
12
13
|
send;
|
|
@@ -177,7 +178,7 @@ export function resolveNotificationCapability(runtime, capability) {
|
|
|
177
178
|
run.initiatedByUserId !== capability.userId ||
|
|
178
179
|
(run.source === 'SCHEDULED' ? 'SCHEDULED' : 'MANUAL') !== capability.source)
|
|
179
180
|
return undefined;
|
|
180
|
-
return { ...capability, sessionId: session.id };
|
|
181
|
+
return { ...capability, sessionId: session.id, sessionGroup: sessionGroup(session) };
|
|
181
182
|
}
|
|
182
183
|
function notificationMcpStdioArgs() {
|
|
183
184
|
const javascript = fileURLToPath(new URL('./notification-mcp-stdio.js', import.meta.url));
|
|
@@ -38,7 +38,8 @@ export class PromptCompletionService {
|
|
|
38
38
|
finally {
|
|
39
39
|
const context = JSON.stringify(analysis);
|
|
40
40
|
try {
|
|
41
|
-
|
|
41
|
+
// Keep persistence on the journal's serial queue, outside the response path.
|
|
42
|
+
const recording = this.options.record?.({
|
|
42
43
|
schemaVersion: 1,
|
|
43
44
|
startedAt,
|
|
44
45
|
finishedAt: this.now(),
|
|
@@ -63,6 +64,9 @@ export class PromptCompletionService {
|
|
|
63
64
|
status: failed ? 'failed' : result?.status,
|
|
64
65
|
reason: failed ? failureCode : result?.status === 'skipped' ? result.reason : undefined
|
|
65
66
|
});
|
|
67
|
+
void recording?.catch(() => {
|
|
68
|
+
console.warn('PROMPT_COMPLETION_JOURNAL_WRITE_FAILED');
|
|
69
|
+
});
|
|
66
70
|
}
|
|
67
71
|
catch {
|
|
68
72
|
console.warn('PROMPT_COMPLETION_JOURNAL_WRITE_FAILED');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sessionGroup } from './session-presentation-service.js';
|
|
1
2
|
import { composerVisibleText } from '@myagentroam/protocol';
|
|
2
3
|
const TERMINAL_TTL_MS = 12 * 60 * 60_000;
|
|
3
4
|
const CLEANUP_INTERVAL_MS = 5 * 60_000;
|
|
@@ -36,6 +37,7 @@ export class RecentRunProjectionService {
|
|
|
36
37
|
workspaceId: run.workspaceId,
|
|
37
38
|
sessionId: run.sessionId,
|
|
38
39
|
sessionTitle: session.customTitle ?? session.runnerTitle ?? session.id,
|
|
40
|
+
sessionGroup: sessionGroup(session),
|
|
39
41
|
...(latestUserMessage === undefined ? {} : { userMessage: latestUserMessage }),
|
|
40
42
|
runner: run.runner,
|
|
41
43
|
status: run.status,
|
|
@@ -55,6 +57,11 @@ export class RecentRunProjectionService {
|
|
|
55
57
|
this.cleanup();
|
|
56
58
|
return [...this.runsBySession.values()].sort(compareRecentRuns);
|
|
57
59
|
}
|
|
60
|
+
clearSessionParent(workspaceId, sessionId) {
|
|
61
|
+
for (const [key, run] of this.runsBySession)
|
|
62
|
+
if (run.workspaceId === workspaceId && run.sessionId === sessionId)
|
|
63
|
+
this.runsBySession.set(key, { ...run, sessionGroup: 'MAIN' });
|
|
64
|
+
}
|
|
58
65
|
publishSnapshot() {
|
|
59
66
|
this.emit({ kind: 'SNAPSHOT', runs: [...this.snapshot()] });
|
|
60
67
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sessionGroup } from './session-presentation-service.js';
|
|
1
2
|
import { permissionStopSchema, runPerformanceSchema } from '@myagentroam/protocol';
|
|
2
3
|
import { nodeLog } from '../operational.js';
|
|
3
4
|
import { isPlainRecord, isTerminalRunStatus } from '../util/node-operation-parsers.js';
|
|
@@ -252,6 +253,7 @@ export class RunEventService {
|
|
|
252
253
|
workspaceId: run.workspaceId,
|
|
253
254
|
sessionId: run.sessionId,
|
|
254
255
|
sessionTitle: session.customTitle ?? session.runnerTitle ?? session.id,
|
|
256
|
+
sessionGroup: sessionGroup(session),
|
|
255
257
|
runId
|
|
256
258
|
};
|
|
257
259
|
if (!Number.isSafeInteger(base.userId))
|
|
@@ -41,6 +41,7 @@ export class ScheduledSessionCoordinator {
|
|
|
41
41
|
eventType: 'notification',
|
|
42
42
|
payload: {
|
|
43
43
|
targetType: 'WORKSPACE_SESSION',
|
|
44
|
+
sessionGroup: this.options.sessionGroup?.(task) ?? 'MAIN',
|
|
44
45
|
userId: currentTask.ownerUserId,
|
|
45
46
|
workspaceId: task.workspaceId,
|
|
46
47
|
sessionId: task.sessionId,
|
|
@@ -68,6 +69,7 @@ export class ScheduledSessionCoordinator {
|
|
|
68
69
|
eventType: 'notification',
|
|
69
70
|
payload: {
|
|
70
71
|
targetType: 'WORKSPACE_SESSION',
|
|
72
|
+
sessionGroup: this.options.sessionGroup?.(task) ?? 'MAIN',
|
|
71
73
|
userId: task.ownerUserId,
|
|
72
74
|
workspaceId: task.workspaceId,
|
|
73
75
|
sessionId,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sessionGroup } from './session-presentation-service.js';
|
|
1
2
|
import { removeNativeSession } from '../native-session-history.js';
|
|
2
3
|
import { nodeLog } from '../operational.js';
|
|
3
4
|
import { isTerminalRunStatus, sessionPage } from '../util/node-operation-parsers.js';
|
|
@@ -20,15 +21,33 @@ export class SessionCatalogService {
|
|
|
20
21
|
};
|
|
21
22
|
}
|
|
22
23
|
async list(input) {
|
|
24
|
+
if (input.sessionGroup !== undefined &&
|
|
25
|
+
input.sessionGroup !== 'MAIN' &&
|
|
26
|
+
input.sessionGroup !== 'CHILD')
|
|
27
|
+
throw new Error('SESSION_INVALID');
|
|
23
28
|
const sessions = await this.collect(input);
|
|
24
|
-
const
|
|
29
|
+
const runningCounts = { MAIN: 0, CHILD: 0 };
|
|
30
|
+
for (const session of sessions)
|
|
31
|
+
if ('activity' in session && session.activity === 'MANAGED_ACTIVE')
|
|
32
|
+
runningCounts[sessionGroup(session)] += 1;
|
|
33
|
+
const query = typeof input.query === 'string'
|
|
34
|
+
? input.query.trim().normalize('NFKC').toLocaleLowerCase()
|
|
35
|
+
: '';
|
|
36
|
+
const page = sessionPage(sessions.filter((session) => (input.parentSessionId === undefined ||
|
|
37
|
+
session.parentSessionId === input.parentSessionId) &&
|
|
38
|
+
(input.sessionGroup === undefined || sessionGroup(session) === input.sessionGroup) &&
|
|
39
|
+
(query.length === 0 ||
|
|
40
|
+
(session.customTitle ?? session.runnerTitle ?? '')
|
|
41
|
+
.normalize('NFKC')
|
|
42
|
+
.toLocaleLowerCase()
|
|
43
|
+
.includes(query))), input);
|
|
25
44
|
return input.view === 'marctl'
|
|
26
45
|
? {
|
|
27
46
|
view: 'marctl',
|
|
28
47
|
sessions: page.sessions.map((session) => marctlSessionSummary(this.options.runtime, session)),
|
|
29
48
|
nextCursor: page.nextCursor
|
|
30
49
|
}
|
|
31
|
-
: page;
|
|
50
|
+
: { ...page, runningCounts };
|
|
32
51
|
}
|
|
33
52
|
async graph(input) {
|
|
34
53
|
if (typeof input.workspaceId !== 'string')
|
|
@@ -125,14 +144,7 @@ export class SessionCatalogService {
|
|
|
125
144
|
].map((session) => [session.id, session]));
|
|
126
145
|
if (workspaceId !== undefined)
|
|
127
146
|
this.mergePersisted(merged, workspaceId, !registeredOnly && query === undefined && this.options.discoveryPending(workspaceId));
|
|
128
|
-
const sessions = [...merged.values()]
|
|
129
|
-
if (query === undefined)
|
|
130
|
-
return true;
|
|
131
|
-
const title = (session.customTitle ?? session.runnerTitle ?? '')
|
|
132
|
-
.normalize('NFKC')
|
|
133
|
-
.toLocaleLowerCase();
|
|
134
|
-
return title.includes(query);
|
|
135
|
-
});
|
|
147
|
+
const sessions = [...merged.values()];
|
|
136
148
|
return workspaceId === undefined
|
|
137
149
|
? sessions
|
|
138
150
|
: sessions.filter((session) => session.workspaceId === workspaceId);
|
|
@@ -24,6 +24,13 @@ export class SessionLifecycleService {
|
|
|
24
24
|
this.starts.clear();
|
|
25
25
|
}
|
|
26
26
|
async updateMetadata(input) {
|
|
27
|
+
if (input.parentSessionId !== undefined ||
|
|
28
|
+
(input.clearParent !== undefined &&
|
|
29
|
+
(input.clearParent !== true ||
|
|
30
|
+
input.customTitle !== undefined ||
|
|
31
|
+
input.pinned !== undefined ||
|
|
32
|
+
input.sessionPrompt !== undefined)))
|
|
33
|
+
throw new Error('SESSION_INVALID');
|
|
27
34
|
const sessionPrompt = input.sessionPrompt === undefined ? undefined : normalizeSessionPrompt(input.sessionPrompt);
|
|
28
35
|
if (typeof input.sessionId !== 'string' ||
|
|
29
36
|
(input.customTitle !== undefined &&
|
|
@@ -39,6 +46,27 @@ export class SessionLifecycleService {
|
|
|
39
46
|
(await this.options.projection.createMetadataProjection(input.sessionId));
|
|
40
47
|
if (current === undefined)
|
|
41
48
|
throw new Error('SESSION_NOT_FOUND');
|
|
49
|
+
if (input.clearParent === true) {
|
|
50
|
+
if (current.externalSessionId === null)
|
|
51
|
+
throw new Error('SESSION_ACTIVATION_REQUIRED');
|
|
52
|
+
const metadata = this.options
|
|
53
|
+
.database()
|
|
54
|
+
.getNativeSessionMetadata(current.runner, current.externalSessionId, current.workspaceId);
|
|
55
|
+
const parentSessionId = metadata === undefined ? current.parentSessionId : metadata.parentSessionId;
|
|
56
|
+
if (parentSessionId == null) {
|
|
57
|
+
const next = current.parentSessionId == null
|
|
58
|
+
? current
|
|
59
|
+
: this.options.runtime.clearSessionParent(current.id);
|
|
60
|
+
return { session: this.options.present(next) };
|
|
61
|
+
}
|
|
62
|
+
this.options
|
|
63
|
+
.database()
|
|
64
|
+
.clearSessionParent(current.runner, current.externalSessionId, current.workspaceId);
|
|
65
|
+
const next = this.options.runtime.clearSessionParent(current.id);
|
|
66
|
+
this.options.parentCleared?.(next);
|
|
67
|
+
this.options.emitSession(next);
|
|
68
|
+
return { session: this.options.present(next) };
|
|
69
|
+
}
|
|
42
70
|
if (sessionPrompt !== undefined && current.externalSessionId === null)
|
|
43
71
|
throw new Error('SESSION_ACTIVATION_REQUIRED');
|
|
44
72
|
if (input.pinned === false) {
|
|
@@ -12,7 +12,7 @@ export class SessionPresentationService {
|
|
|
12
12
|
session = {
|
|
13
13
|
...session,
|
|
14
14
|
sessionPrompt: metadata.sessionPrompt ?? null,
|
|
15
|
-
parentSessionId: metadata.parentSessionId ??
|
|
15
|
+
parentSessionId: metadata.parentSessionId ?? null,
|
|
16
16
|
pinnedAt: metadata.pinnedAt,
|
|
17
17
|
pinOrder: metadata.pinOrder
|
|
18
18
|
};
|
|
@@ -26,6 +26,7 @@ export class SessionPresentationService {
|
|
|
26
26
|
return {
|
|
27
27
|
...session,
|
|
28
28
|
parentSessionId: session.parentSessionId ?? null,
|
|
29
|
+
sessionGroup: sessionGroup(session),
|
|
29
30
|
...presentation,
|
|
30
31
|
runtime: {
|
|
31
32
|
...presentation.runtime,
|
|
@@ -60,6 +61,9 @@ export class SessionPresentationService {
|
|
|
60
61
|
});
|
|
61
62
|
}
|
|
62
63
|
}
|
|
64
|
+
export function sessionGroup(session) {
|
|
65
|
+
return session.parentSessionId ? 'CHILD' : 'MAIN';
|
|
66
|
+
}
|
|
63
67
|
export function latestSessionActivityAt(current, discovered) {
|
|
64
68
|
const known = [current, discovered].filter((value) => Number.isFinite(value) && value > 0);
|
|
65
69
|
return known.length === 0 ? 0 : Math.max(...known);
|
|
@@ -17,6 +17,9 @@ export class WorkspaceWatchService {
|
|
|
17
17
|
if (!plain(input.topics))
|
|
18
18
|
throw new Error('WORKSPACE_WATCH_INVALID');
|
|
19
19
|
const sessions = input.topics.sessions === true;
|
|
20
|
+
const sessionGroup = input.topics.sessionGroup;
|
|
21
|
+
if (sessionGroup !== undefined && sessionGroup !== 'MAIN' && sessionGroup !== 'CHILD')
|
|
22
|
+
throw new Error('WORKSPACE_WATCH_INVALID');
|
|
20
23
|
const changes = input.topics.changes === true;
|
|
21
24
|
const rawFiles = input.topics.files;
|
|
22
25
|
let files;
|
|
@@ -38,7 +41,7 @@ export class WorkspaceWatchService {
|
|
|
38
41
|
}
|
|
39
42
|
if (!sessions && !changes && files === undefined)
|
|
40
43
|
throw new Error('WORKSPACE_WATCH_INVALID');
|
|
41
|
-
return { sessions, changes, files };
|
|
44
|
+
return { sessions, changes, files, ...(sessionGroup === undefined ? {} : { sessionGroup }) };
|
|
42
45
|
}
|
|
43
46
|
key(input) {
|
|
44
47
|
return `${this.sessionHash(input)}:${this.connectionId(input)}`;
|
|
@@ -123,7 +123,7 @@ export class WorkspaceWatchSnapshotService {
|
|
|
123
123
|
const subscribers = this.subscribers(workspaceId, topic);
|
|
124
124
|
if (subscribers.length === 0)
|
|
125
125
|
return;
|
|
126
|
-
if (topic
|
|
126
|
+
if (topic === 'changes') {
|
|
127
127
|
const first = subscribers[0];
|
|
128
128
|
if (first === undefined)
|
|
129
129
|
return;
|
|
@@ -154,7 +154,9 @@ export class WorkspaceWatchSnapshotService {
|
|
|
154
154
|
}
|
|
155
155
|
const views = new Map();
|
|
156
156
|
for (const watch of subscribers) {
|
|
157
|
-
const key =
|
|
157
|
+
const key = topic === 'files'
|
|
158
|
+
? JSON.stringify(watch.topics.files)
|
|
159
|
+
: (watch.topics.sessionGroup ?? 'ALL');
|
|
158
160
|
views.set(key, [...(views.get(key) ?? []), watch]);
|
|
159
161
|
}
|
|
160
162
|
await Promise.all([...views.values()].map(async (viewSubscribers) => {
|
|
@@ -180,12 +182,12 @@ export class WorkspaceWatchSnapshotService {
|
|
|
180
182
|
read(workspace, topic, watch) {
|
|
181
183
|
const key = topic === 'files'
|
|
182
184
|
? `${workspace.id}:files:${JSON.stringify(watch.topics.files)}`
|
|
183
|
-
: `${workspace.id}:${topic}:${this.readRevision(workspace.id, topic)}`;
|
|
185
|
+
: `${workspace.id}:${topic}:${watch.topics.sessionGroup ?? 'ALL'}:${this.readRevision(workspace.id, topic)}`;
|
|
184
186
|
const active = this.reads.get(key);
|
|
185
187
|
if (active !== undefined)
|
|
186
188
|
return active;
|
|
187
189
|
const read = (topic === 'sessions'
|
|
188
|
-
? this.options.listSessions(workspace.id)
|
|
190
|
+
? this.options.listSessions(workspace.id, watch.topics.sessionGroup)
|
|
189
191
|
: topic === 'changes'
|
|
190
192
|
? workspace.kind === 'GIT_WORKSPACE'
|
|
191
193
|
? this.options.changes.current(workspace)
|
|
@@ -214,7 +216,7 @@ export class WorkspaceWatchSnapshotService {
|
|
|
214
216
|
viewKey(workspaceId, topic, watch) {
|
|
215
217
|
return topic === 'files'
|
|
216
218
|
? `${workspaceId}:files:${JSON.stringify(watch.topics.files)}`
|
|
217
|
-
: `${workspaceId}:${topic}`;
|
|
219
|
+
: `${workspaceId}:${topic}:${watch.topics.sessionGroup ?? 'ALL'}`;
|
|
218
220
|
}
|
|
219
221
|
readActive(workspaceId, topic) {
|
|
220
222
|
const prefix = `${workspaceId}:${topic}`;
|
package/dist/terminal.js
CHANGED
|
@@ -631,7 +631,7 @@ function terminalDimension(value, fallback) {
|
|
|
631
631
|
function terminalSequence(value) {
|
|
632
632
|
if (value === undefined)
|
|
633
633
|
return 0;
|
|
634
|
-
if (typeof value !== 'number' || !Number.
|
|
634
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0)
|
|
635
635
|
throw new Error('TERMINAL_REPLAY_INVALID');
|
|
636
636
|
return value;
|
|
637
637
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.19",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"license": "Apache-2.0 AND GPL-2.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"playwright": "1.62.1",
|
|
38
38
|
"ws": "^8.21.3",
|
|
39
39
|
"zod": "4.4.3",
|
|
40
|
-
"@myagentroam/agent": "0.11.
|
|
40
|
+
"@myagentroam/agent": "0.11.19",
|
|
41
41
|
"@myagentroam/marctl": "0.11.0",
|
|
42
42
|
"@myagentroam/code-navigation": "0.11.0",
|
|
43
|
-
"@myagentroam/protocol": "0.11.
|
|
43
|
+
"@myagentroam/protocol": "0.11.19"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/ws": "^8.18.1"
|