@myagentroam/node 0.1.3 → 0.1.6
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/README.md +9 -1
- package/dist/capabilities.d.ts +2 -0
- package/dist/capabilities.js +15 -4
- package/dist/claude-agent-sdk.d.ts +4 -16
- package/dist/claude-agent-sdk.js +18 -79
- package/dist/claude-channel.js +0 -1
- package/dist/codex-app-server.d.ts +5 -0
- package/dist/codex-app-server.js +26 -5
- package/dist/config.d.ts +3 -0
- package/dist/config.js +18 -2
- package/dist/connector/node-connector-options.d.ts +26 -0
- package/dist/connector/node-connector-options.js +1 -0
- package/dist/connector/node-operation-router.d.ts +9 -0
- package/dist/connector/node-operation-router.js +22 -0
- package/dist/connector.d.ts +62 -356
- package/dist/connector.js +714 -6176
- package/dist/control-outbound-scheduler.d.ts +26 -0
- package/dist/control-outbound-scheduler.js +121 -0
- package/dist/database.d.ts +4 -0
- package/dist/database.js +15 -10
- package/dist/event-buffer.js +0 -1
- package/dist/fake-runner.js +0 -1
- package/dist/health.js +0 -1
- package/dist/main.js +6 -1
- package/dist/migrations/v001.js +0 -1
- package/dist/migrations/v002.js +0 -1
- package/dist/migrations/v003.d.ts +4 -0
- package/dist/migrations/v003.js +44 -0
- package/dist/native-jsonl-reader.d.ts +14 -0
- package/dist/native-jsonl-reader.js +97 -0
- package/dist/native-session-history.d.ts +41 -4
- package/dist/native-session-history.js +340 -159
- package/dist/opencode-runtime.d.ts +33 -0
- package/dist/opencode-runtime.js +80 -0
- package/dist/opencode-server.d.ts +47 -0
- package/dist/opencode-server.js +250 -0
- package/dist/operational.js +10 -3
- package/dist/process-tree.js +0 -1
- package/dist/runner/abstract-runner.d.ts +153 -0
- package/dist/runner/abstract-runner.js +154 -0
- package/dist/runner/claude/managed-run-controller.d.ts +52 -0
- package/dist/runner/claude/managed-run-controller.js +326 -0
- package/dist/runner/claude-code-runner.d.ts +70 -0
- package/dist/runner/claude-code-runner.js +286 -0
- package/dist/runner/codex/conversation-parser.d.ts +59 -0
- package/dist/runner/codex/conversation-parser.js +816 -0
- package/dist/runner/codex/managed-run-controller.d.ts +80 -0
- package/dist/runner/codex/managed-run-controller.js +388 -0
- package/dist/runner/codex-runner.d.ts +85 -0
- package/dist/runner/codex-runner.js +365 -0
- package/dist/runner/fake-test-runner.d.ts +26 -0
- package/dist/runner/fake-test-runner.js +47 -0
- package/dist/runner/opencode/conversation-parser.d.ts +27 -0
- package/dist/runner/opencode/conversation-parser.js +217 -0
- package/dist/runner/opencode/managed-run-controller.d.ts +76 -0
- package/dist/runner/opencode/managed-run-controller.js +451 -0
- package/dist/runner/opencode-runner.d.ts +60 -0
- package/dist/runner/opencode-runner.js +330 -0
- package/dist/runner/runner-registry.d.ts +19 -0
- package/dist/runner/runner-registry.js +75 -0
- package/dist/runner-command-engine.js +0 -1
- package/dist/runner-profiles.d.ts +4 -0
- package/dist/runner-profiles.js +76 -3
- package/dist/runner-usage.d.ts +2 -2
- package/dist/runner-usage.js +3 -6
- package/dist/runtime-state.js +26 -14
- package/dist/service/attachment-domain-state.d.ts +5 -0
- package/dist/service/attachment-domain-state.js +5 -0
- package/dist/service/conversation-history-service.d.ts +34 -0
- package/dist/service/conversation-history-service.js +158 -0
- package/dist/service/external-session-resume-service.d.ts +32 -0
- package/dist/service/external-session-resume-service.js +98 -0
- package/dist/service/fake-managed-run-service.d.ts +29 -0
- package/dist/service/fake-managed-run-service.js +62 -0
- package/dist/service/managed-runner-session-service.d.ts +12 -0
- package/dist/service/managed-runner-session-service.js +35 -0
- package/dist/service/native-session-projection-service.d.ts +51 -0
- package/dist/service/native-session-projection-service.js +271 -0
- package/dist/service/native-session-watch-service.d.ts +37 -0
- package/dist/service/native-session-watch-service.js +150 -0
- package/dist/service/node-connection-lifecycle-service.d.ts +37 -0
- package/dist/service/node-connection-lifecycle-service.js +85 -0
- package/dist/service/node-control-channel.d.ts +32 -0
- package/dist/service/node-control-channel.js +85 -0
- package/dist/service/node-control-message-service.d.ts +28 -0
- package/dist/service/node-control-message-service.js +104 -0
- package/dist/service/node-request-service.d.ts +14 -0
- package/dist/service/node-request-service.js +50 -0
- package/dist/service/node-run-event-bridge.d.ts +40 -0
- package/dist/service/node-run-event-bridge.js +83 -0
- package/dist/service/node-terminal-channel.d.ts +29 -0
- package/dist/service/node-terminal-channel.js +182 -0
- package/dist/service/node-upgrade-coordinator.d.ts +29 -0
- package/dist/service/node-upgrade-coordinator.js +137 -0
- package/dist/service/node-workspace-coordinator.d.ts +32 -0
- package/dist/service/node-workspace-coordinator.js +80 -0
- package/dist/service/run-attachment-service.d.ts +37 -0
- package/dist/service/run-attachment-service.js +160 -0
- package/dist/service/run-domain-state.d.ts +4 -0
- package/dist/service/run-domain-state.js +4 -0
- package/dist/service/run-event-service.d.ts +31 -0
- package/dist/service/run-event-service.js +102 -0
- package/dist/service/run-workbench-service.d.ts +27 -0
- package/dist/service/run-workbench-service.js +113 -0
- package/dist/service/runner-channel-message-service.d.ts +13 -0
- package/dist/service/runner-channel-message-service.js +20 -0
- package/dist/service/runner-interaction-service.d.ts +40 -0
- package/dist/service/runner-interaction-service.js +246 -0
- package/dist/service/runner-service.d.ts +44 -0
- package/dist/service/runner-service.js +68 -0
- package/dist/service/session-catalog-service.d.ts +45 -0
- package/dist/service/session-catalog-service.js +224 -0
- package/dist/service/session-command-service.d.ts +23 -0
- package/dist/service/session-command-service.js +69 -0
- package/dist/service/session-context-service.d.ts +34 -0
- package/dist/service/session-context-service.js +87 -0
- package/dist/service/session-domain-state.d.ts +8 -0
- package/dist/service/session-domain-state.js +8 -0
- package/dist/service/session-identity-service.d.ts +24 -0
- package/dist/service/session-identity-service.js +86 -0
- package/dist/service/session-lifecycle-service.d.ts +31 -0
- package/dist/service/session-lifecycle-service.js +242 -0
- package/dist/service/session-message-service.d.ts +33 -0
- package/dist/service/session-message-service.js +157 -0
- package/dist/service/session-presentation-service.d.ts +33 -0
- package/dist/service/session-presentation-service.js +36 -0
- package/dist/service/session-query-service.d.ts +31 -0
- package/dist/service/session-query-service.js +77 -0
- package/dist/service/terminal-relay-state.d.ts +6 -0
- package/dist/service/terminal-relay-state.js +3 -0
- package/dist/service/terminal-service.d.ts +12 -0
- package/dist/service/terminal-service.js +52 -0
- package/dist/service/workbench-event-service.d.ts +21 -0
- package/dist/service/workbench-event-service.js +53 -0
- package/dist/service/workbench-manifest-service.d.ts +89 -0
- package/dist/service/workbench-manifest-service.js +70 -0
- package/dist/service/workspace-change-service.d.ts +9 -0
- package/dist/service/workspace-change-service.js +86 -0
- package/dist/service/workspace-content-search-service.d.ts +14 -0
- package/dist/service/workspace-content-search-service.js +477 -0
- package/dist/service/workspace-domain-state.d.ts +75 -0
- package/dist/service/workspace-domain-state.js +40 -0
- package/dist/service/workspace-file-service.d.ts +17 -0
- package/dist/service/workspace-file-service.js +109 -0
- package/dist/service/workspace-queue-workbench-service.d.ts +55 -0
- package/dist/service/workspace-queue-workbench-service.js +226 -0
- package/dist/service/workspace-service.d.ts +15 -0
- package/dist/service/workspace-service.js +67 -0
- package/dist/service/workspace-session-service.d.ts +23 -0
- package/dist/service/workspace-session-service.js +139 -0
- package/dist/service/workspace-upload-service.d.ts +37 -0
- package/dist/service/workspace-upload-service.js +240 -0
- package/dist/service/workspace-watch-service.d.ts +18 -0
- package/dist/service/workspace-watch-service.js +120 -0
- package/dist/service/workspace-workbench-service.d.ts +47 -0
- package/dist/service/workspace-workbench-service.js +223 -0
- package/dist/service.js +2 -3
- package/dist/storage.js +0 -1
- package/dist/supervisor.d.ts +1 -0
- package/dist/supervisor.js +154 -0
- package/dist/terminal.d.ts +1 -0
- package/dist/terminal.js +1 -2
- package/dist/util/node-operation-parsers.d.ts +92 -0
- package/dist/util/node-operation-parsers.js +382 -0
- package/dist/util/runner-native-session-parsers.d.ts +72 -0
- package/dist/util/runner-native-session-parsers.js +381 -0
- package/dist/util/secret-environment.d.ts +1 -0
- package/dist/util/secret-environment.js +25 -0
- package/dist/workspace.d.ts +62 -2
- package/dist/workspace.js +539 -57
- package/package.json +3 -2
- package/dist/capabilities.js.map +0 -1
- package/dist/claude-agent-sdk.js.map +0 -1
- package/dist/claude-channel.js.map +0 -1
- package/dist/codex-app-server.js.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/connector.js.map +0 -1
- package/dist/database.js.map +0 -1
- package/dist/event-buffer.js.map +0 -1
- package/dist/fake-runner.js.map +0 -1
- package/dist/health.js.map +0 -1
- package/dist/main.js.map +0 -1
- package/dist/migrations/v001.js.map +0 -1
- package/dist/migrations/v002.js.map +0 -1
- package/dist/native-session-history.js.map +0 -1
- package/dist/operational.js.map +0 -1
- package/dist/process-tree.js.map +0 -1
- package/dist/runner-command-engine.js.map +0 -1
- package/dist/runner-profiles.js.map +0 -1
- package/dist/runner-usage.js.map +0 -1
- package/dist/runtime-state.js.map +0 -1
- package/dist/service.js.map +0 -1
- package/dist/storage.js.map +0 -1
- package/dist/terminal.js.map +0 -1
- package/dist/workspace.js.map +0 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { removeNativeSession } from '../native-session-history.js';
|
|
2
|
+
import { isTerminalRunStatus, sessionPage } from '../util/node-operation-parsers.js';
|
|
3
|
+
export class SessionCatalogService {
|
|
4
|
+
options;
|
|
5
|
+
rewindQueue = Promise.resolve();
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
}
|
|
9
|
+
operations() {
|
|
10
|
+
return {
|
|
11
|
+
'session.list': (data) => this.list(record(data)),
|
|
12
|
+
'session.resume': (data) => this.resume(record(data)),
|
|
13
|
+
'session.remove': (data) => this.remove(record(data)),
|
|
14
|
+
'session.rewind': (data) => this.enqueueRewind(record(data))
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
async list(input) {
|
|
18
|
+
const workspaceId = typeof input.workspaceId === 'string' ? input.workspaceId : undefined;
|
|
19
|
+
const nativeSessions = workspaceId === undefined
|
|
20
|
+
? this.options.cachedNative()
|
|
21
|
+
: await this.options.cachedWorkspace(workspaceId);
|
|
22
|
+
const native = nativeSessions.map((session) => this.options.present({ ...session, externalDiscovered: true }));
|
|
23
|
+
const projected = this.options.runtime
|
|
24
|
+
.listAgentSessions()
|
|
25
|
+
.filter((session) => session.nativeControl === 'MAR_MANAGED' ||
|
|
26
|
+
(session.nativeControl === 'EXTERNAL' && session.externalSessionId !== null))
|
|
27
|
+
.map((session) => {
|
|
28
|
+
const source = native.find((candidate) => this.options.projectionForNative(candidate)?.id === session.id);
|
|
29
|
+
return this.options.present(source === undefined
|
|
30
|
+
? session
|
|
31
|
+
: {
|
|
32
|
+
...session,
|
|
33
|
+
runnerTitle: source.runnerTitle,
|
|
34
|
+
pinnedAt: source.pinnedAt,
|
|
35
|
+
pinOrder: source.pinOrder ?? null,
|
|
36
|
+
externalDiscovered: true,
|
|
37
|
+
titleSource: session.customTitle === null ? source.titleSource : session.titleSource,
|
|
38
|
+
lastActivityAt: source.lastActivityAt
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
const merged = new Map([
|
|
42
|
+
...projected,
|
|
43
|
+
...native.filter((candidate) => candidate.externalSessionId === null ||
|
|
44
|
+
this.options.projectionForNative(candidate) === undefined)
|
|
45
|
+
].map((session) => [session.id, session]));
|
|
46
|
+
if (workspaceId !== undefined)
|
|
47
|
+
this.mergePersisted(merged, workspaceId);
|
|
48
|
+
const sessions = [...merged.values()];
|
|
49
|
+
return sessionPage(workspaceId === undefined
|
|
50
|
+
? sessions
|
|
51
|
+
: sessions.filter((session) => session.workspaceId === workspaceId), input);
|
|
52
|
+
}
|
|
53
|
+
mergePersisted(merged, workspaceId) {
|
|
54
|
+
const database = this.options.database();
|
|
55
|
+
const workspace = database.getWorkspace(workspaceId);
|
|
56
|
+
if (workspace === undefined)
|
|
57
|
+
throw new Error('WORKSPACE_NOT_FOUND');
|
|
58
|
+
for (const persisted of database.listSessionRecords(workspaceId)) {
|
|
59
|
+
if (merged.has(persisted.id))
|
|
60
|
+
continue;
|
|
61
|
+
merged.set(persisted.id, this.options.present({
|
|
62
|
+
id: persisted.id,
|
|
63
|
+
nodeId: this.options.nodeId(),
|
|
64
|
+
workspaceId,
|
|
65
|
+
runner: persisted.runner,
|
|
66
|
+
externalSessionId: persisted.id,
|
|
67
|
+
nativeControl: 'EXTERNAL',
|
|
68
|
+
channelToken: null,
|
|
69
|
+
cwd: workspace.path,
|
|
70
|
+
model: persisted.metadata.model,
|
|
71
|
+
effort: persisted.metadata.effort,
|
|
72
|
+
access: persisted.metadata.access,
|
|
73
|
+
customTitle: persisted.metadata.customTitle,
|
|
74
|
+
runnerTitle: persisted.metadata.runnerTitle,
|
|
75
|
+
pinnedAt: persisted.pinOrder === null ? null : 1,
|
|
76
|
+
pinOrder: persisted.pinOrder,
|
|
77
|
+
externalDiscovered: false,
|
|
78
|
+
titleSource: persisted.metadata.customTitle === null ? 'RUNNER' : 'CUSTOM',
|
|
79
|
+
lastActivityAt: 0,
|
|
80
|
+
createdAt: 0
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async resume(input) {
|
|
85
|
+
if (typeof input.sessionId !== 'string')
|
|
86
|
+
throw new Error('SESSION_INVALID');
|
|
87
|
+
const value = this.options.runtime.getAgentSession(input.sessionId) ??
|
|
88
|
+
(await this.options.projection.resolve(input.sessionId));
|
|
89
|
+
if (value === undefined)
|
|
90
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
91
|
+
if (value.nativeControl !== 'EXTERNAL')
|
|
92
|
+
return {
|
|
93
|
+
session: this.options.present(value),
|
|
94
|
+
delivery: this.options.resume.delivery(value)
|
|
95
|
+
};
|
|
96
|
+
const resumed = await this.options.resume.resume(value, true);
|
|
97
|
+
const failure = resumed === undefined ? this.options.resume.failure(value.id) : undefined;
|
|
98
|
+
return {
|
|
99
|
+
session: this.options.present(resumed ?? value),
|
|
100
|
+
delivery: resumed === undefined ? 'REJECTED' : 'RESUMABLE',
|
|
101
|
+
...(resumed === undefined
|
|
102
|
+
? {
|
|
103
|
+
reasonCode: failure?.code ?? 'SESSION_RESUME_REJECTED',
|
|
104
|
+
...(failure?.detail === undefined ? {} : { reasonDetail: failure.detail })
|
|
105
|
+
}
|
|
106
|
+
: {})
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
enqueueRewind(input) {
|
|
110
|
+
const result = this.rewindQueue.then(() => this.rewind(input));
|
|
111
|
+
this.rewindQueue = result.then(() => undefined, () => undefined);
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
async rewind(input) {
|
|
115
|
+
if (!validRewindId(input.sessionId, 128) ||
|
|
116
|
+
!validRewindId(input.turnId, 128) ||
|
|
117
|
+
(input.position !== 'BEFORE' && input.position !== 'AFTER') ||
|
|
118
|
+
(input.boundary !== null && !validRewindId(input.boundary, 256)))
|
|
119
|
+
throw new Error('SESSION_REWIND_INVALID');
|
|
120
|
+
const source = this.options.runtime.getAgentSession(input.sessionId) ??
|
|
121
|
+
(await this.options.projection.resolve(input.sessionId));
|
|
122
|
+
if (source === undefined)
|
|
123
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
124
|
+
if (source.externalSessionId === null)
|
|
125
|
+
throw new Error('SESSION_REWIND_UNAVAILABLE');
|
|
126
|
+
const presented = this.options.present(source);
|
|
127
|
+
if (!presented.runtime?.capabilities?.includes('session.rewind'))
|
|
128
|
+
throw new Error('SESSION_REWIND_UNAVAILABLE');
|
|
129
|
+
const turn = await this.findTurn(source, input.turnId);
|
|
130
|
+
const expected = input.position === 'BEFORE' ? turn?.rewind?.before : turn?.rewind?.after;
|
|
131
|
+
if (turn === undefined || expected !== input.boundary)
|
|
132
|
+
throw new Error('SESSION_REWIND_TARGET_INVALID');
|
|
133
|
+
const title = rewindTitle(source.customTitle ?? source.runnerTitle ?? '未命名会话', this.options.runtime
|
|
134
|
+
.listAgentSessions()
|
|
135
|
+
.flatMap((candidate) => [candidate.customTitle ?? candidate.runnerTitle].filter(isString)));
|
|
136
|
+
const runner = this.options.runners.require(source.runner);
|
|
137
|
+
const nativeId = await runner.forkNative(source, input.boundary, title);
|
|
138
|
+
const session = this.options.runtime.createAgentSession({
|
|
139
|
+
workspaceId: source.workspaceId,
|
|
140
|
+
runner: source.runner,
|
|
141
|
+
cwd: source.cwd,
|
|
142
|
+
nativeControl: 'MAR_MANAGED',
|
|
143
|
+
...(nativeId === null ? {} : { externalSessionId: nativeId }),
|
|
144
|
+
...(source.model === null ? {} : { model: source.model }),
|
|
145
|
+
...(source.effort === null ? {} : { effort: source.effort }),
|
|
146
|
+
...(source.access === null ? {} : { access: source.access }),
|
|
147
|
+
title
|
|
148
|
+
});
|
|
149
|
+
if (nativeId !== null)
|
|
150
|
+
runner.rememberManagedNative(session, nativeId);
|
|
151
|
+
this.options.emitSession(session);
|
|
152
|
+
return { session: this.options.present(session) };
|
|
153
|
+
}
|
|
154
|
+
async findTurn(session, turnId) {
|
|
155
|
+
let cursor;
|
|
156
|
+
for (let pageIndex = 0; pageIndex < 100; pageIndex += 1) {
|
|
157
|
+
const page = await this.options.history(session, {
|
|
158
|
+
...(cursor === undefined ? {} : { cursor }),
|
|
159
|
+
limit: 100
|
|
160
|
+
});
|
|
161
|
+
const turn = page.turns.find((candidate) => candidate.id === turnId);
|
|
162
|
+
if (turn !== undefined || page.nextCursor === null)
|
|
163
|
+
return turn;
|
|
164
|
+
cursor = page.nextCursor;
|
|
165
|
+
}
|
|
166
|
+
throw new Error('SESSION_REWIND_TARGET_INVALID');
|
|
167
|
+
}
|
|
168
|
+
async remove(input) {
|
|
169
|
+
if (typeof input.sessionId !== 'string')
|
|
170
|
+
throw new Error('SESSION_INVALID');
|
|
171
|
+
const session = this.options.runtime.getAgentSession(input.sessionId) ??
|
|
172
|
+
(await this.options.projection.resolve(input.sessionId));
|
|
173
|
+
if (session === undefined)
|
|
174
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
175
|
+
if (session.externalSessionId === null)
|
|
176
|
+
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
177
|
+
if (this.options.runtime
|
|
178
|
+
.listRunsForSession(session.id)
|
|
179
|
+
.some((run) => !isTerminalRunStatus(run.status)))
|
|
180
|
+
throw new Error('SESSION_ACTIVE');
|
|
181
|
+
if (session.nativeControl === 'EXTERNAL') {
|
|
182
|
+
await this.options.refreshActivity(session);
|
|
183
|
+
if (this.options.externalActivity(session.id) === 'EXTERNAL_ACTIVE')
|
|
184
|
+
throw new Error('SESSION_ACTIVE');
|
|
185
|
+
}
|
|
186
|
+
const nativeIds = this.options.nativeIds(session);
|
|
187
|
+
const runner = this.options.runners.require(session.runner);
|
|
188
|
+
for (const externalSessionId of nativeIds) {
|
|
189
|
+
try {
|
|
190
|
+
if (!(await runner.removeNative(session, externalSessionId)))
|
|
191
|
+
await removeNativeSession(session.runner, session.cwd, externalSessionId);
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
if (!(error instanceof Error) || error.message !== 'NATIVE_TRANSCRIPT_UNAVAILABLE')
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
this.options.database().deleteSessionRecord(session.id);
|
|
199
|
+
this.options.forget(session, nativeIds);
|
|
200
|
+
return { deleted: true };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
export function rewindTitle(sourceTitle, existingTitles = []) {
|
|
204
|
+
const base = sourceTitle.replace(/ \(Copy \d+\)$/u, '');
|
|
205
|
+
const existing = new Set(existingTitles);
|
|
206
|
+
for (let sequence = 1; sequence < Number.MAX_SAFE_INTEGER; sequence += 1) {
|
|
207
|
+
const suffix = ` (Copy ${sequence})`;
|
|
208
|
+
const candidate = `${base.slice(0, 160 - suffix.length)}${suffix}`;
|
|
209
|
+
if (!existing.has(candidate))
|
|
210
|
+
return candidate;
|
|
211
|
+
}
|
|
212
|
+
throw new Error('SESSION_REWIND_TITLE_UNAVAILABLE');
|
|
213
|
+
}
|
|
214
|
+
function validRewindId(value, maximum) {
|
|
215
|
+
return typeof value === 'string' && value.length > 0 && value.length <= maximum;
|
|
216
|
+
}
|
|
217
|
+
function isString(value) {
|
|
218
|
+
return value !== null;
|
|
219
|
+
}
|
|
220
|
+
function record(value) {
|
|
221
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
222
|
+
return {};
|
|
223
|
+
return value;
|
|
224
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { RunnerProfile } from '@myagentroam/protocol';
|
|
2
|
+
import type { NodeOperationHandler } from '../connector/node-operation-router.js';
|
|
3
|
+
import type { NodeAgentSession } from '../database.js';
|
|
4
|
+
import type { NodeRuntimeState } from '../runtime-state.js';
|
|
5
|
+
import type { RunnerRegistry } from '../runner/runner-registry.js';
|
|
6
|
+
import type { ExternalSessionResumeService } from './external-session-resume-service.js';
|
|
7
|
+
import type { NativeSessionProjectionService } from './native-session-projection-service.js';
|
|
8
|
+
interface SessionCommandServiceOptions {
|
|
9
|
+
readonly runtime: NodeRuntimeState;
|
|
10
|
+
readonly runners: RunnerRegistry;
|
|
11
|
+
readonly projection: NativeSessionProjectionService;
|
|
12
|
+
readonly resume: ExternalSessionResumeService;
|
|
13
|
+
readonly profiles: () => readonly RunnerProfile[];
|
|
14
|
+
readonly present: (session: NodeAgentSession) => unknown;
|
|
15
|
+
}
|
|
16
|
+
export declare class SessionCommandService {
|
|
17
|
+
private readonly options;
|
|
18
|
+
constructor(options: SessionCommandServiceOptions);
|
|
19
|
+
operations(): Readonly<Record<string, NodeOperationHandler>>;
|
|
20
|
+
private runnerCommand;
|
|
21
|
+
private sessionCommand;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { parseCommandInvocation } from '../runner-command-engine.js';
|
|
2
|
+
import { isPlainRecord } from '../util/node-operation-parsers.js';
|
|
3
|
+
import { parseSecretEnvironment } from '../util/secret-environment.js';
|
|
4
|
+
export class SessionCommandService {
|
|
5
|
+
options;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
}
|
|
9
|
+
operations() {
|
|
10
|
+
return {
|
|
11
|
+
'runner.command.execute': (data) => this.runnerCommand(record(data)),
|
|
12
|
+
'session.command.execute': (data) => this.sessionCommand(record(data))
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
runnerCommand(input) {
|
|
16
|
+
const runner = this.options.runners.product(input.runner);
|
|
17
|
+
if (runner === undefined ||
|
|
18
|
+
typeof input.commandId !== 'string' ||
|
|
19
|
+
typeof input.input !== 'string')
|
|
20
|
+
throw new Error('RUNNER_COMMAND_INVALID');
|
|
21
|
+
try {
|
|
22
|
+
return runner.executeGlobalCommand(input.commandId, input.input);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error instanceof Error && error.message === 'RUNNER_COMMAND_INVALID')
|
|
26
|
+
throw error;
|
|
27
|
+
throw new Error('RUNNER_COMMAND_INVALID');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async sessionCommand(input) {
|
|
31
|
+
if (typeof input.sessionId !== 'string' ||
|
|
32
|
+
typeof input.commandId !== 'string' ||
|
|
33
|
+
input.commandId.length === 0 ||
|
|
34
|
+
typeof input.input !== 'string' ||
|
|
35
|
+
input.input.length < 2 ||
|
|
36
|
+
input.input.length > 20_000)
|
|
37
|
+
throw new Error('RUNNER_COMMAND_INVALID');
|
|
38
|
+
const resolved = this.options.runtime.getAgentSession(input.sessionId) ??
|
|
39
|
+
(await this.options.projection.resolve(input.sessionId));
|
|
40
|
+
if (resolved === undefined)
|
|
41
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
42
|
+
const session = resolved.nativeControl === 'EXTERNAL' ? await this.options.resume.resume(resolved) : resolved;
|
|
43
|
+
if (session === undefined)
|
|
44
|
+
throw new Error('RUNNER_COMMAND_UNAVAILABLE');
|
|
45
|
+
const command = this.options
|
|
46
|
+
.profiles()
|
|
47
|
+
.find((profile) => profile.runner === session.runner)
|
|
48
|
+
?.commands.find((candidate) => isPlainRecord(candidate) && candidate.id === input.commandId);
|
|
49
|
+
if (command?.available !== true)
|
|
50
|
+
throw new Error('RUNNER_COMMAND_UNAVAILABLE');
|
|
51
|
+
const invocation = parseCommandInvocation({ descriptor: command }, input.input);
|
|
52
|
+
const secretEnvironment = parseSecretEnvironment(input.secretEnvironment);
|
|
53
|
+
const runner = this.options.runners.product(session.runner);
|
|
54
|
+
if (runner === undefined)
|
|
55
|
+
throw new Error('RUNNER_COMMAND_UNAVAILABLE');
|
|
56
|
+
const result = await runner.executeCommand(session, command, invocation.input, {
|
|
57
|
+
secretEnvironment
|
|
58
|
+
});
|
|
59
|
+
return {
|
|
60
|
+
...(isPlainRecord(result) ? result : { result }),
|
|
61
|
+
session: this.options.present(session)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function record(value) {
|
|
66
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
67
|
+
return {};
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { NodeAgentSession } from '../database.js';
|
|
2
|
+
import type { RunnerRegistry } from '../runner/runner-registry.js';
|
|
3
|
+
export interface SessionContextUsage {
|
|
4
|
+
readonly model: string | null;
|
|
5
|
+
readonly usedTokens: number;
|
|
6
|
+
readonly maxTokens: number;
|
|
7
|
+
readonly percentage: number;
|
|
8
|
+
}
|
|
9
|
+
export interface SessionContextReadResult {
|
|
10
|
+
readonly context: SessionContextUsage | null;
|
|
11
|
+
readonly pending: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface SessionContextServiceOptions {
|
|
14
|
+
readonly runners: RunnerRegistry;
|
|
15
|
+
readonly session: (sessionId: string) => NodeAgentSession | undefined;
|
|
16
|
+
readonly stopped: () => boolean;
|
|
17
|
+
readonly publish: (input: {
|
|
18
|
+
sessionId: string;
|
|
19
|
+
workspaceId: string;
|
|
20
|
+
context: SessionContextUsage;
|
|
21
|
+
pending: false;
|
|
22
|
+
}) => void;
|
|
23
|
+
}
|
|
24
|
+
export declare class SessionContextService {
|
|
25
|
+
private readonly options;
|
|
26
|
+
private readonly snapshots;
|
|
27
|
+
private readonly timers;
|
|
28
|
+
private readonly versions;
|
|
29
|
+
constructor(options: SessionContextServiceOptions);
|
|
30
|
+
read(session: NodeAgentSession): Promise<SessionContextReadResult>;
|
|
31
|
+
schedule(sessionId: string): void;
|
|
32
|
+
dispose(): void;
|
|
33
|
+
private current;
|
|
34
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const REFRESH_DELAYS_MS = [100, 400, 1_000];
|
|
2
|
+
export class SessionContextService {
|
|
3
|
+
options;
|
|
4
|
+
snapshots = new Map();
|
|
5
|
+
timers = new Map();
|
|
6
|
+
versions = new Map();
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
async read(session) {
|
|
11
|
+
if (session.externalSessionId === null)
|
|
12
|
+
return { context: null, pending: false };
|
|
13
|
+
const usage = await this.options.runners.product(session.runner)?.readContextUsage(session);
|
|
14
|
+
if (usage === undefined)
|
|
15
|
+
return { context: null, pending: false };
|
|
16
|
+
const context = {
|
|
17
|
+
model: usage.model ?? session.model,
|
|
18
|
+
usedTokens: usage.usedTokens,
|
|
19
|
+
maxTokens: usage.maxTokens,
|
|
20
|
+
percentage: Math.round((usage.usedTokens / usage.maxTokens) * 1000) / 10
|
|
21
|
+
};
|
|
22
|
+
this.snapshots.set(session.id, context);
|
|
23
|
+
return { context, pending: false };
|
|
24
|
+
}
|
|
25
|
+
schedule(sessionId) {
|
|
26
|
+
const pending = this.timers.get(sessionId);
|
|
27
|
+
if (pending !== undefined)
|
|
28
|
+
clearTimeout(pending);
|
|
29
|
+
this.timers.delete(sessionId);
|
|
30
|
+
const version = (this.versions.get(sessionId) ?? 0) + 1;
|
|
31
|
+
this.versions.set(sessionId, version);
|
|
32
|
+
let published = this.snapshots.get(sessionId);
|
|
33
|
+
const attempt = (index) => {
|
|
34
|
+
const timer = setTimeout(() => {
|
|
35
|
+
if (this.timers.get(sessionId) === timer)
|
|
36
|
+
this.timers.delete(sessionId);
|
|
37
|
+
if (!this.current(sessionId, version))
|
|
38
|
+
return;
|
|
39
|
+
const session = this.options.session(sessionId);
|
|
40
|
+
if (session === undefined || session.externalSessionId === null)
|
|
41
|
+
return;
|
|
42
|
+
void this.read(session)
|
|
43
|
+
.then(({ context }) => {
|
|
44
|
+
if (!this.current(sessionId, version))
|
|
45
|
+
return;
|
|
46
|
+
if (context !== null && (published === undefined || !sameUsage(published, context))) {
|
|
47
|
+
published = context;
|
|
48
|
+
this.options.publish({
|
|
49
|
+
sessionId: session.id,
|
|
50
|
+
workspaceId: session.workspaceId,
|
|
51
|
+
context,
|
|
52
|
+
pending: false
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
.catch(() => undefined)
|
|
57
|
+
.finally(() => {
|
|
58
|
+
if (!this.current(sessionId, version))
|
|
59
|
+
return;
|
|
60
|
+
if (index + 1 < REFRESH_DELAYS_MS.length)
|
|
61
|
+
attempt(index + 1);
|
|
62
|
+
else
|
|
63
|
+
this.versions.delete(sessionId);
|
|
64
|
+
});
|
|
65
|
+
}, REFRESH_DELAYS_MS[index]);
|
|
66
|
+
this.timers.set(sessionId, timer);
|
|
67
|
+
timer.unref();
|
|
68
|
+
};
|
|
69
|
+
attempt(0);
|
|
70
|
+
}
|
|
71
|
+
dispose() {
|
|
72
|
+
for (const timer of this.timers.values())
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
this.timers.clear();
|
|
75
|
+
this.versions.clear();
|
|
76
|
+
this.snapshots.clear();
|
|
77
|
+
}
|
|
78
|
+
current(sessionId, version) {
|
|
79
|
+
return !this.options.stopped() && this.versions.get(sessionId) === version;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function sameUsage(left, right) {
|
|
83
|
+
return (left.model === right.model &&
|
|
84
|
+
left.usedTokens === right.usedTokens &&
|
|
85
|
+
left.maxTokens === right.maxTokens &&
|
|
86
|
+
left.percentage === right.percentage);
|
|
87
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare class SessionDomainState<TSession, TActivity> {
|
|
2
|
+
readonly directNative: Map<string, TSession>;
|
|
3
|
+
readonly externalActivity: Map<string, TActivity>;
|
|
4
|
+
readonly resumedByDirectId: Map<string, string>;
|
|
5
|
+
readonly resumeFailures: Map<string, string>;
|
|
6
|
+
readonly messageResumeFailures: Map<string, string>;
|
|
7
|
+
readonly resumeFailureDetails: Map<string, string>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { NodeAgentSession } from '../database.js';
|
|
2
|
+
import type { NodeRuntimeState } from '../runtime-state.js';
|
|
3
|
+
export interface SessionIdentityServiceOptions {
|
|
4
|
+
readonly runtime: NodeRuntimeState;
|
|
5
|
+
readonly persist: (session: NodeAgentSession, nativeSessionId: string) => void;
|
|
6
|
+
readonly migrateCommands: (previousId: string, nextId: string) => void;
|
|
7
|
+
readonly migrateQueue: (previousId: string, nextId: string) => void;
|
|
8
|
+
readonly migrateRunnerState: (previousId: string, nextId: string) => void;
|
|
9
|
+
readonly emitSession: (session: NodeAgentSession) => void;
|
|
10
|
+
readonly emitRun: (run: unknown) => void;
|
|
11
|
+
readonly emitQueue: (workspaceId: string, sessionId: string) => void;
|
|
12
|
+
readonly emitTurn: (turn: unknown) => void;
|
|
13
|
+
}
|
|
14
|
+
export declare class SessionIdentityService {
|
|
15
|
+
private readonly options;
|
|
16
|
+
readonly bootstrapIds: Set<string>;
|
|
17
|
+
private readonly waiters;
|
|
18
|
+
constructor(options: SessionIdentityServiceOptions);
|
|
19
|
+
markBootstrap(sessionId: string): void;
|
|
20
|
+
promote(sessionId: string, nativeSessionId: string): void;
|
|
21
|
+
wait(bootstrapSessionId: string): Promise<string>;
|
|
22
|
+
reject(sessionId: string, code: string): void;
|
|
23
|
+
dispose(): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export class SessionIdentityService {
|
|
2
|
+
options;
|
|
3
|
+
bootstrapIds = new Set();
|
|
4
|
+
waiters = new Map();
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.options = options;
|
|
7
|
+
}
|
|
8
|
+
markBootstrap(sessionId) {
|
|
9
|
+
this.bootstrapIds.add(sessionId);
|
|
10
|
+
}
|
|
11
|
+
promote(sessionId, nativeSessionId) {
|
|
12
|
+
const current = this.options.runtime.getAgentSession(sessionId);
|
|
13
|
+
this.options.runtime.promoteSessionIdentity(sessionId, nativeSessionId);
|
|
14
|
+
if (current !== undefined)
|
|
15
|
+
this.options.persist(current, nativeSessionId);
|
|
16
|
+
this.options.migrateCommands(sessionId, nativeSessionId);
|
|
17
|
+
this.options.migrateQueue(sessionId, nativeSessionId);
|
|
18
|
+
this.options.migrateRunnerState(sessionId, nativeSessionId);
|
|
19
|
+
this.bootstrapIds.delete(sessionId);
|
|
20
|
+
const waiter = this.waiters.get(sessionId);
|
|
21
|
+
if (waiter !== undefined) {
|
|
22
|
+
clearTimeout(waiter.timer);
|
|
23
|
+
this.waiters.delete(sessionId);
|
|
24
|
+
waiter.resolve(nativeSessionId);
|
|
25
|
+
}
|
|
26
|
+
const session = this.options.runtime.getAgentSession(nativeSessionId);
|
|
27
|
+
if (session === undefined)
|
|
28
|
+
return;
|
|
29
|
+
this.options.emitSession(session);
|
|
30
|
+
for (const run of this.options.runtime.listRunsForSession(nativeSessionId))
|
|
31
|
+
this.options.emitRun(run);
|
|
32
|
+
this.options.emitQueue(session.workspaceId, nativeSessionId);
|
|
33
|
+
for (const turn of this.options.runtime.listConversationTurns({ sessionId: nativeSessionId })
|
|
34
|
+
.turns)
|
|
35
|
+
if (turn.status !== 'QUEUED')
|
|
36
|
+
this.options.emitTurn(turn);
|
|
37
|
+
}
|
|
38
|
+
wait(bootstrapSessionId) {
|
|
39
|
+
const existing = this.waiters.get(bootstrapSessionId);
|
|
40
|
+
if (existing !== undefined)
|
|
41
|
+
return existing.promise;
|
|
42
|
+
let resolveIdentity;
|
|
43
|
+
let rejectIdentity;
|
|
44
|
+
const promise = new Promise((resolve, reject) => {
|
|
45
|
+
resolveIdentity = resolve;
|
|
46
|
+
rejectIdentity = reject;
|
|
47
|
+
});
|
|
48
|
+
const timer = setTimeout(() => {
|
|
49
|
+
this.waiters.delete(bootstrapSessionId);
|
|
50
|
+
rejectIdentity(new Error('SESSION_NATIVE_ID_TIMEOUT'));
|
|
51
|
+
}, 30_000);
|
|
52
|
+
timer.unref();
|
|
53
|
+
this.waiters.set(bootstrapSessionId, {
|
|
54
|
+
promise,
|
|
55
|
+
resolve: resolveIdentity,
|
|
56
|
+
reject: rejectIdentity,
|
|
57
|
+
timer
|
|
58
|
+
});
|
|
59
|
+
return promise;
|
|
60
|
+
}
|
|
61
|
+
reject(sessionId, code) {
|
|
62
|
+
const waiter = this.waiters.get(sessionId);
|
|
63
|
+
if (waiter === undefined)
|
|
64
|
+
return;
|
|
65
|
+
clearTimeout(waiter.timer);
|
|
66
|
+
this.waiters.delete(sessionId);
|
|
67
|
+
waiter.reject(new Error(code));
|
|
68
|
+
queueMicrotask(() => {
|
|
69
|
+
try {
|
|
70
|
+
this.options.runtime.removeAgentSession(sessionId);
|
|
71
|
+
this.bootstrapIds.delete(sessionId);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Runner cleanup may still own the bootstrap Session.
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
dispose() {
|
|
79
|
+
for (const waiter of this.waiters.values()) {
|
|
80
|
+
clearTimeout(waiter.timer);
|
|
81
|
+
waiter.reject(new Error('NODE_STOPPED'));
|
|
82
|
+
}
|
|
83
|
+
this.waiters.clear();
|
|
84
|
+
this.bootstrapIds.clear();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { NodeOperationHandler } from '../connector/node-operation-router.js';
|
|
2
|
+
import type { NodeAgentSession, NodeDatabase } from '../database.js';
|
|
3
|
+
import type { NodeRuntimeState } from '../runtime-state.js';
|
|
4
|
+
import type { RunnerRegistry } from '../runner/runner-registry.js';
|
|
5
|
+
import type { NativeSessionProjectionService } from './native-session-projection-service.js';
|
|
6
|
+
import type { SessionIdentityService } from './session-identity-service.js';
|
|
7
|
+
interface SessionLifecycleServiceOptions {
|
|
8
|
+
readonly database: () => NodeDatabase;
|
|
9
|
+
readonly runtime: NodeRuntimeState;
|
|
10
|
+
readonly runners: RunnerRegistry;
|
|
11
|
+
readonly projection: NativeSessionProjectionService;
|
|
12
|
+
readonly identity: SessionIdentityService;
|
|
13
|
+
readonly present: (session: NodeAgentSession) => unknown;
|
|
14
|
+
readonly emitSession: (session: NodeAgentSession) => void;
|
|
15
|
+
readonly execute: (operation: string, data: Record<string, unknown>) => Promise<unknown>;
|
|
16
|
+
readonly interceptedSessionId: (clientMessageId: string) => string | undefined;
|
|
17
|
+
}
|
|
18
|
+
export declare class SessionLifecycleService {
|
|
19
|
+
private readonly options;
|
|
20
|
+
private readonly starts;
|
|
21
|
+
constructor(options: SessionLifecycleServiceOptions);
|
|
22
|
+
operations(): Readonly<Record<string, NodeOperationHandler>>;
|
|
23
|
+
clear(): void;
|
|
24
|
+
private updateMetadata;
|
|
25
|
+
private movePin;
|
|
26
|
+
private create;
|
|
27
|
+
private updateConfiguration;
|
|
28
|
+
private start;
|
|
29
|
+
private startFresh;
|
|
30
|
+
}
|
|
31
|
+
export {};
|