@myagentroam/node 0.9.8 → 0.9.9
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.d.ts +1 -0
- package/dist/connector.js +6 -2
- package/dist/runtime-state.d.ts +3 -1
- package/dist/runtime-state.js +11 -2
- package/dist/service/node-connection-lifecycle-service.d.ts +1 -0
- package/dist/service/node-connection-lifecycle-service.js +1 -0
- package/dist/service/recent-run-projection-service.d.ts +14 -0
- package/dist/service/recent-run-projection-service.js +78 -0
- package/package.json +2 -2
package/dist/connector.d.ts
CHANGED
|
@@ -68,6 +68,7 @@ export declare class NodeConnector {
|
|
|
68
68
|
private readonly workspaceChangeService;
|
|
69
69
|
private readonly workspaceWorkbenchService;
|
|
70
70
|
private database;
|
|
71
|
+
private readonly recentRunProjectionService;
|
|
71
72
|
/** Runtime/session state is intentionally not persisted to Node SQLite. */
|
|
72
73
|
private readonly runtime;
|
|
73
74
|
private readonly metrics;
|
package/dist/connector.js
CHANGED
|
@@ -30,6 +30,7 @@ import { WorkspaceChangeService } from './service/workspace-change-service.js';
|
|
|
30
30
|
import { WorkspaceSessionService } from './service/workspace-session-service.js';
|
|
31
31
|
import { WorkbenchEventService } from './service/workbench-event-service.js';
|
|
32
32
|
import { RunDomainState } from './service/run-domain-state.js';
|
|
33
|
+
import { RecentRunProjectionService } from './service/recent-run-projection-service.js';
|
|
33
34
|
import { SessionDomainState } from './service/session-domain-state.js';
|
|
34
35
|
import { RunEventService } from './service/run-event-service.js';
|
|
35
36
|
import { RunAttachmentService } from './service/run-attachment-service.js';
|
|
@@ -166,8 +167,9 @@ export class NodeConnector {
|
|
|
166
167
|
workspaceChangeService = new WorkspaceChangeService(this.workspaceState);
|
|
167
168
|
workspaceWorkbenchService;
|
|
168
169
|
database;
|
|
170
|
+
recentRunProjectionService = new RecentRunProjectionService((message) => this.send('recent-runs', message));
|
|
169
171
|
/** Runtime/session state is intentionally not persisted to Node SQLite. */
|
|
170
|
-
runtime = new NodeRuntimeState(() => this.config?.nodeId ?? 'runtime-node', (workspaceId) => this.database?.getWorkspace(workspaceId));
|
|
172
|
+
runtime = new NodeRuntimeState(() => this.config?.nodeId ?? 'runtime-node', (workspaceId) => this.database?.getWorkspace(workspaceId), Date.now, (run, session) => this.recentRunProjectionService.record(run, session));
|
|
171
173
|
metrics = new NodeMetrics();
|
|
172
174
|
runEventService = new RunEventService({
|
|
173
175
|
runtime: this.runtime,
|
|
@@ -684,7 +686,8 @@ export class NodeConnector {
|
|
|
684
686
|
terminal: this.terminalChannel,
|
|
685
687
|
metrics: this.metrics,
|
|
686
688
|
heartbeatMs: HEARTBEAT_MS,
|
|
687
|
-
refreshMs: CAPABILITY_REFRESH_MS
|
|
689
|
+
refreshMs: CAPABILITY_REFRESH_MS,
|
|
690
|
+
onConnected: () => this.recentRunProjectionService.publishSnapshot()
|
|
688
691
|
});
|
|
689
692
|
const requireDatabase = () => {
|
|
690
693
|
if (this.database === undefined)
|
|
@@ -763,6 +766,7 @@ export class NodeConnector {
|
|
|
763
766
|
}
|
|
764
767
|
stop() {
|
|
765
768
|
this.stopped = true;
|
|
769
|
+
this.recentRunProjectionService.dispose();
|
|
766
770
|
this.upgradeCoordinator.stop();
|
|
767
771
|
this.connectionLifecycle.stop();
|
|
768
772
|
for (const approval of this.claudeClient.execution.approvals.values()) {
|
package/dist/runtime-state.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export declare class NodeRuntimeState {
|
|
|
10
10
|
private readonly nodeId;
|
|
11
11
|
private readonly getWorkspace;
|
|
12
12
|
private readonly now;
|
|
13
|
+
private readonly onRunChanged?;
|
|
14
|
+
private static readonly MAX_RUN_EVENTS_PER_RUN;
|
|
13
15
|
private static readonly MAX_COMPLETED_TURNS_PER_SESSION;
|
|
14
16
|
private readonly sessions;
|
|
15
17
|
private readonly runs;
|
|
@@ -26,7 +28,7 @@ export declare class NodeRuntimeState {
|
|
|
26
28
|
/** Bounded tombstones reject stale internal start envelopes after terminal cleanup. */
|
|
27
29
|
private readonly terminalRunIds;
|
|
28
30
|
private nextEventId;
|
|
29
|
-
constructor(nodeId: () => string, getWorkspace: (id: string) => NodeWorkspace | undefined, now?: () => number);
|
|
31
|
+
constructor(nodeId: () => string, getWorkspace: (id: string) => NodeWorkspace | undefined, now?: () => number, onRunChanged?: ((run: NodeAgentRun, session: NodeAgentSession) => void) | undefined);
|
|
30
32
|
activeWorkspaceLeaseCount(): number;
|
|
31
33
|
activeSessionRun(sessionId: string): NodeAgentRun | undefined;
|
|
32
34
|
hasActiveSessionLease(sessionId: string): boolean;
|
package/dist/runtime-state.js
CHANGED
|
@@ -9,6 +9,8 @@ export class NodeRuntimeState {
|
|
|
9
9
|
nodeId;
|
|
10
10
|
getWorkspace;
|
|
11
11
|
now;
|
|
12
|
+
onRunChanged;
|
|
13
|
+
static MAX_RUN_EVENTS_PER_RUN = 100;
|
|
12
14
|
static MAX_COMPLETED_TURNS_PER_SESSION = 10;
|
|
13
15
|
sessions = new Map();
|
|
14
16
|
runs = new Map();
|
|
@@ -25,10 +27,11 @@ export class NodeRuntimeState {
|
|
|
25
27
|
/** Bounded tombstones reject stale internal start envelopes after terminal cleanup. */
|
|
26
28
|
terminalRunIds = new Set();
|
|
27
29
|
nextEventId = 1;
|
|
28
|
-
constructor(nodeId, getWorkspace, now = Date.now) {
|
|
30
|
+
constructor(nodeId, getWorkspace, now = Date.now, onRunChanged) {
|
|
29
31
|
this.nodeId = nodeId;
|
|
30
32
|
this.getWorkspace = getWorkspace;
|
|
31
33
|
this.now = now;
|
|
34
|
+
this.onRunChanged = onRunChanged;
|
|
32
35
|
}
|
|
33
36
|
activeWorkspaceLeaseCount() {
|
|
34
37
|
return this.leases.size;
|
|
@@ -118,6 +121,7 @@ export class NodeRuntimeState {
|
|
|
118
121
|
};
|
|
119
122
|
this.runs.set(run.id, run);
|
|
120
123
|
this.leases.set(run.sessionId, run.id);
|
|
124
|
+
this.onRunChanged?.(run, session);
|
|
121
125
|
return run;
|
|
122
126
|
}
|
|
123
127
|
getAgentSession(id) {
|
|
@@ -322,6 +326,7 @@ export class NodeRuntimeState {
|
|
|
322
326
|
this.bumpQueueVersion(session.id);
|
|
323
327
|
this.turns.set(session.id, [...(this.turns.get(session.id) ?? []), fixedTurn]);
|
|
324
328
|
this.replaceSession(session.id, { lastActivityAt: now });
|
|
329
|
+
this.onRunChanged?.(run, this.requireSession(session.id));
|
|
325
330
|
return { run, created: true };
|
|
326
331
|
}
|
|
327
332
|
sessionQueue(sessionId) {
|
|
@@ -434,6 +439,7 @@ export class NodeRuntimeState {
|
|
|
434
439
|
throw new Error('RUN_STATE_INVALID');
|
|
435
440
|
const value = { ...current, status: next, version: current.version + 1, updatedAt: this.now() };
|
|
436
441
|
this.runs.set(id, value);
|
|
442
|
+
this.onRunChanged?.(value, this.requireSession(value.sessionId));
|
|
437
443
|
if (terminal(next) && this.leases.get(value.sessionId) === value.id)
|
|
438
444
|
this.leases.delete(value.sessionId);
|
|
439
445
|
this.updateTurnForRun(id, (turn) => ({
|
|
@@ -464,7 +470,7 @@ export class NodeRuntimeState {
|
|
|
464
470
|
...(input.status === undefined ? {} : { status: input.status }),
|
|
465
471
|
createdAt: this.now()
|
|
466
472
|
};
|
|
467
|
-
this.events.set(input.runId, [...prior, value]);
|
|
473
|
+
this.events.set(input.runId, [...prior, value].slice(-NodeRuntimeState.MAX_RUN_EVENTS_PER_RUN));
|
|
468
474
|
if (input.status !== undefined)
|
|
469
475
|
this.transitionRun(input.runId, input.status);
|
|
470
476
|
this.projectEvent(value);
|
|
@@ -714,6 +720,9 @@ export class NodeRuntimeState {
|
|
|
714
720
|
const value = this.requireSession(id);
|
|
715
721
|
const next = { ...value, ...patch };
|
|
716
722
|
this.sessions.set(id, next);
|
|
723
|
+
if (value.customTitle !== next.customTitle || value.runnerTitle !== next.runnerTitle)
|
|
724
|
+
for (const run of this.listRunsForSession(id))
|
|
725
|
+
this.onRunChanged?.(run, next);
|
|
717
726
|
return next;
|
|
718
727
|
}
|
|
719
728
|
bumpQueueVersion(sessionId) {
|
|
@@ -17,6 +17,7 @@ interface NodeConnectionLifecycleServiceOptions {
|
|
|
17
17
|
readonly metrics: NodeMetrics;
|
|
18
18
|
readonly heartbeatMs: number;
|
|
19
19
|
readonly refreshMs: number;
|
|
20
|
+
readonly onConnected?: () => void;
|
|
20
21
|
}
|
|
21
22
|
/** Owns registration completion, heartbeat and background capability refresh timers. */
|
|
22
23
|
export declare class NodeConnectionLifecycleService {
|
|
@@ -47,6 +47,7 @@ export class NodeConnectionLifecycleService {
|
|
|
47
47
|
this.options.control.send('capabilities', this.options.capabilities());
|
|
48
48
|
this.options.terminal.connect();
|
|
49
49
|
this.options.control.send('heartbeat', {});
|
|
50
|
+
this.options.onConnected?.();
|
|
50
51
|
this.options.metrics.heartbeatSent();
|
|
51
52
|
this.heartbeat = setInterval(() => {
|
|
52
53
|
this.options.metrics.heartbeatSent();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { NodeAgentRun, NodeAgentSession } from '../database.js';
|
|
2
|
+
import type { RecentRunProjection, RecentRunProjectionMessage } from '@myagentroam/protocol';
|
|
3
|
+
export declare class RecentRunProjectionService {
|
|
4
|
+
private readonly emit;
|
|
5
|
+
private readonly now;
|
|
6
|
+
private readonly runs;
|
|
7
|
+
private readonly cleanupTimer;
|
|
8
|
+
constructor(emit: (message: RecentRunProjectionMessage) => void, now?: () => number);
|
|
9
|
+
record(run: NodeAgentRun, session: NodeAgentSession): void;
|
|
10
|
+
snapshot(): readonly RecentRunProjection[];
|
|
11
|
+
publishSnapshot(): void;
|
|
12
|
+
dispose(): void;
|
|
13
|
+
private cleanup;
|
|
14
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const TERMINAL_TTL_MS = 12 * 60 * 60_000;
|
|
2
|
+
const CLEANUP_INTERVAL_MS = 5 * 60_000;
|
|
3
|
+
const TERMINAL_LIMIT = 100;
|
|
4
|
+
export class RecentRunProjectionService {
|
|
5
|
+
emit;
|
|
6
|
+
now;
|
|
7
|
+
runs = new Map();
|
|
8
|
+
cleanupTimer;
|
|
9
|
+
constructor(emit, now = Date.now) {
|
|
10
|
+
this.emit = emit;
|
|
11
|
+
this.now = now;
|
|
12
|
+
this.cleanupTimer = setInterval(() => this.cleanup(), CLEANUP_INTERVAL_MS);
|
|
13
|
+
this.cleanupTimer.unref();
|
|
14
|
+
}
|
|
15
|
+
record(run, session) {
|
|
16
|
+
if (!Number.isSafeInteger(run.initiatedByUserId) || run.initiatedByUserId === undefined)
|
|
17
|
+
return;
|
|
18
|
+
const existing = this.runs.get(run.id);
|
|
19
|
+
if (existing !== undefined && existing.version > run.version)
|
|
20
|
+
return;
|
|
21
|
+
const startedAt = existing?.startedAt ?? (run.status === 'QUEUED' ? null : run.updatedAt);
|
|
22
|
+
const completedAt = terminal(run.status) ? (existing?.completedAt ?? run.updatedAt) : null;
|
|
23
|
+
const projection = {
|
|
24
|
+
runId: run.id,
|
|
25
|
+
nodeId: run.nodeId,
|
|
26
|
+
workspaceId: run.workspaceId,
|
|
27
|
+
sessionId: run.sessionId,
|
|
28
|
+
sessionTitle: session.customTitle ?? session.runnerTitle ?? session.id,
|
|
29
|
+
runner: run.runner,
|
|
30
|
+
status: run.status,
|
|
31
|
+
createdAt: run.createdAt,
|
|
32
|
+
startedAt,
|
|
33
|
+
completedAt,
|
|
34
|
+
sortAt: startedAt ?? run.createdAt,
|
|
35
|
+
updatedAt: run.updatedAt,
|
|
36
|
+
version: run.version,
|
|
37
|
+
userId: run.initiatedByUserId
|
|
38
|
+
};
|
|
39
|
+
this.runs.set(run.id, projection);
|
|
40
|
+
this.cleanup();
|
|
41
|
+
this.emit({ kind: 'UPDATE', run: projection });
|
|
42
|
+
}
|
|
43
|
+
snapshot() {
|
|
44
|
+
this.cleanup();
|
|
45
|
+
return [...this.runs.values()].sort(compareRecentRuns);
|
|
46
|
+
}
|
|
47
|
+
publishSnapshot() {
|
|
48
|
+
this.emit({ kind: 'SNAPSHOT', runs: [...this.snapshot()] });
|
|
49
|
+
}
|
|
50
|
+
dispose() {
|
|
51
|
+
clearInterval(this.cleanupTimer);
|
|
52
|
+
}
|
|
53
|
+
cleanup() {
|
|
54
|
+
const cutoff = this.now() - TERMINAL_TTL_MS;
|
|
55
|
+
for (const [runId, run] of this.runs)
|
|
56
|
+
if (run.completedAt !== null && run.completedAt <= cutoff)
|
|
57
|
+
this.runs.delete(runId);
|
|
58
|
+
const byUser = new Map();
|
|
59
|
+
for (const run of this.runs.values()) {
|
|
60
|
+
if (run.completedAt === null)
|
|
61
|
+
continue;
|
|
62
|
+
const values = byUser.get(run.userId) ?? [];
|
|
63
|
+
values.push(run);
|
|
64
|
+
byUser.set(run.userId, values);
|
|
65
|
+
}
|
|
66
|
+
for (const values of byUser.values())
|
|
67
|
+
for (const run of values.sort(compareRecentRuns).slice(TERMINAL_LIMIT))
|
|
68
|
+
this.runs.delete(run.runId);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function compareRecentRuns(left, right) {
|
|
72
|
+
return (right.sortAt - left.sortAt ||
|
|
73
|
+
right.createdAt - left.createdAt ||
|
|
74
|
+
right.runId.localeCompare(left.runId));
|
|
75
|
+
}
|
|
76
|
+
function terminal(status) {
|
|
77
|
+
return ['SUCCEEDED', 'FAILED', 'CANCELLED', 'INTERRUPTED'].includes(status);
|
|
78
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.9",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"node-pty": "1.1.0",
|
|
25
25
|
"ws": "^8.21.3",
|
|
26
26
|
"zod": "4.4.3",
|
|
27
|
-
"@myagentroam/protocol": "^0.9.
|
|
27
|
+
"@myagentroam/protocol": "^0.9.9"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/ws": "^8.18.1"
|