@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,271 @@
|
|
|
1
|
+
import { deduplicateDirectSessions } from '../util/runner-native-session-parsers.js';
|
|
2
|
+
export class NativeSessionProjectionService {
|
|
3
|
+
options;
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.options = options;
|
|
6
|
+
}
|
|
7
|
+
async discover(runnerName, workspaceId) {
|
|
8
|
+
const database = this.requireDatabase();
|
|
9
|
+
const workspace = workspaceId === undefined ? undefined : database.getWorkspace(workspaceId);
|
|
10
|
+
if (workspaceId !== undefined && workspace === undefined)
|
|
11
|
+
throw new Error('WORKSPACE_NOT_FOUND');
|
|
12
|
+
const runner = this.options.runners.product(runnerName);
|
|
13
|
+
if (runner === undefined || !runner.available(this.options.capabilities()))
|
|
14
|
+
throw new Error('RUNNER_UNAVAILABLE');
|
|
15
|
+
const sessions = await runner.discoverSessions({
|
|
16
|
+
workspaces: database.listWorkspaces(),
|
|
17
|
+
createProjection: (input) => this.project(input)
|
|
18
|
+
}, workspace);
|
|
19
|
+
const ordered = deduplicateDirectSessions(sessions);
|
|
20
|
+
this.remember(ordered);
|
|
21
|
+
return ordered;
|
|
22
|
+
}
|
|
23
|
+
async discoverCodex(workspaceId) {
|
|
24
|
+
const sessions = await this.discover('codex', workspaceId);
|
|
25
|
+
return { sessions: sessions.map(this.options.present) };
|
|
26
|
+
}
|
|
27
|
+
async list() {
|
|
28
|
+
const discovered = await Promise.all(this.options.runners
|
|
29
|
+
.advertised()
|
|
30
|
+
.filter((runner) => runner.available(this.options.capabilities()))
|
|
31
|
+
.map((runner) => this.discover(runner.name)));
|
|
32
|
+
const sessions = deduplicateDirectSessions(discovered.flat());
|
|
33
|
+
this.remember(sessions);
|
|
34
|
+
return sessions.map(this.options.present);
|
|
35
|
+
}
|
|
36
|
+
async discoverWorkspace(workspaceId) {
|
|
37
|
+
const discovered = await Promise.all(this.options.runners
|
|
38
|
+
.advertised()
|
|
39
|
+
.filter((runner) => runner.available(this.options.capabilities()))
|
|
40
|
+
.map((runner) => this.discover(runner.name, workspaceId)));
|
|
41
|
+
const sessions = deduplicateDirectSessions(discovered.flat());
|
|
42
|
+
this.remember(sessions);
|
|
43
|
+
return sessions;
|
|
44
|
+
}
|
|
45
|
+
async resolve(sessionId) {
|
|
46
|
+
const cached = this.options.state.directNative.get(sessionId);
|
|
47
|
+
if (cached !== undefined)
|
|
48
|
+
return cached;
|
|
49
|
+
const discovered = (await this.list()).find((session) => session.id === sessionId);
|
|
50
|
+
if (discovered !== undefined)
|
|
51
|
+
return discovered;
|
|
52
|
+
const database = this.options.database();
|
|
53
|
+
const record = database
|
|
54
|
+
?.listWorkspaces()
|
|
55
|
+
.flatMap((workspace) => database.listSessionRecords(workspace.id))
|
|
56
|
+
.find((candidate) => candidate.id === sessionId);
|
|
57
|
+
if (record === undefined)
|
|
58
|
+
return undefined;
|
|
59
|
+
const workspace = database?.getWorkspace(record.workspaceId);
|
|
60
|
+
if (workspace === undefined)
|
|
61
|
+
return undefined;
|
|
62
|
+
return {
|
|
63
|
+
id: record.id,
|
|
64
|
+
nodeId: this.options.nodeId(),
|
|
65
|
+
workspaceId: record.workspaceId,
|
|
66
|
+
runner: record.runner,
|
|
67
|
+
externalSessionId: record.id,
|
|
68
|
+
nativeControl: 'EXTERNAL',
|
|
69
|
+
channelToken: null,
|
|
70
|
+
cwd: workspace.path,
|
|
71
|
+
model: record.metadata.model,
|
|
72
|
+
effort: record.metadata.effort,
|
|
73
|
+
access: record.metadata.access,
|
|
74
|
+
customTitle: record.metadata.customTitle,
|
|
75
|
+
runnerTitle: record.metadata.runnerTitle,
|
|
76
|
+
pinnedAt: record.pinOrder === null ? null : 1,
|
|
77
|
+
pinOrder: record.pinOrder,
|
|
78
|
+
externalDiscovered: false,
|
|
79
|
+
titleSource: record.metadata.customTitle === null ? 'RUNNER' : 'CUSTOM',
|
|
80
|
+
lastActivityAt: 0,
|
|
81
|
+
createdAt: 0
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async createMetadataProjection(sessionId) {
|
|
85
|
+
const direct = await this.resolve(sessionId);
|
|
86
|
+
if (direct === undefined || direct.externalSessionId === null)
|
|
87
|
+
return undefined;
|
|
88
|
+
const existing = this.options.runtime.findAgentSessionByExternalSession(direct.runner, direct.externalSessionId);
|
|
89
|
+
if (existing !== undefined && existing.workspaceId === direct.workspaceId)
|
|
90
|
+
return existing;
|
|
91
|
+
return this.options.runtime.createAgentSession({
|
|
92
|
+
workspaceId: direct.workspaceId,
|
|
93
|
+
runner: direct.runner,
|
|
94
|
+
externalSessionId: direct.externalSessionId,
|
|
95
|
+
nativeControl: 'EXTERNAL',
|
|
96
|
+
cwd: direct.cwd,
|
|
97
|
+
...(direct.runnerTitle === null ? {} : { title: direct.runnerTitle })
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
resumedConfiguration(session) {
|
|
101
|
+
const workspace = this.options.database()?.getWorkspace(session.workspaceId);
|
|
102
|
+
const current = {
|
|
103
|
+
runner: session.runner,
|
|
104
|
+
model: session.model,
|
|
105
|
+
effort: session.effort,
|
|
106
|
+
access: session.access
|
|
107
|
+
};
|
|
108
|
+
if (typeof current.model === 'string' &&
|
|
109
|
+
typeof current.effort === 'string' &&
|
|
110
|
+
typeof current.access === 'string' &&
|
|
111
|
+
this.options.runners.supportsConfiguration({
|
|
112
|
+
runner: current.runner,
|
|
113
|
+
model: current.model,
|
|
114
|
+
effort: current.effort,
|
|
115
|
+
access: current.access
|
|
116
|
+
}, workspace))
|
|
117
|
+
return current;
|
|
118
|
+
const saved = this.options
|
|
119
|
+
.database()
|
|
120
|
+
?.getRunnerDefaults()
|
|
121
|
+
.find((value) => value.runner === session.runner &&
|
|
122
|
+
this.options.runners.supportsConfiguration(value, workspace));
|
|
123
|
+
return saved ?? this.options.runners.require(session.runner).defaultConfiguration(workspace);
|
|
124
|
+
}
|
|
125
|
+
ensureResumedConfiguration(session) {
|
|
126
|
+
const configuration = this.resumedConfiguration(session);
|
|
127
|
+
if (session.model === configuration.model &&
|
|
128
|
+
session.effort === configuration.effort &&
|
|
129
|
+
session.access === configuration.access)
|
|
130
|
+
return session;
|
|
131
|
+
return this.options.runtime.updateAgentSessionConfiguration({
|
|
132
|
+
sessionId: session.id,
|
|
133
|
+
model: configuration.model,
|
|
134
|
+
effort: configuration.effort,
|
|
135
|
+
access: configuration.access
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async rename(session, input) {
|
|
139
|
+
if (session.externalSessionId === null)
|
|
140
|
+
return;
|
|
141
|
+
await this.options.runners
|
|
142
|
+
.require(session.runner)
|
|
143
|
+
.renameNative(session, input, this.requireDatabase());
|
|
144
|
+
}
|
|
145
|
+
project(input) {
|
|
146
|
+
const timestamp = input.lastActivityAt ?? 0;
|
|
147
|
+
const database = this.options.database();
|
|
148
|
+
const metadata = database?.getNativeSessionMetadata(input.runner, input.externalSessionId, input.workspace.id);
|
|
149
|
+
const runnerTitle = this.options.runners.require(input.runner).projectedRunnerTitle(metadata, {
|
|
150
|
+
externalSessionId: input.externalSessionId,
|
|
151
|
+
workspaceId: input.workspace.id,
|
|
152
|
+
...(input.title === undefined ? {} : { title: input.title }),
|
|
153
|
+
...(input.titleOrigin === undefined ? {} : { titleOrigin: input.titleOrigin })
|
|
154
|
+
}, database);
|
|
155
|
+
return {
|
|
156
|
+
id: input.externalSessionId,
|
|
157
|
+
nodeId: this.options.nodeId(),
|
|
158
|
+
workspaceId: input.workspace.id,
|
|
159
|
+
runner: input.runner,
|
|
160
|
+
externalSessionId: input.externalSessionId,
|
|
161
|
+
nativeControl: 'EXTERNAL',
|
|
162
|
+
channelToken: null,
|
|
163
|
+
cwd: input.cwd,
|
|
164
|
+
model: metadata?.model ?? null,
|
|
165
|
+
effort: metadata?.effort ?? null,
|
|
166
|
+
access: metadata?.access ?? null,
|
|
167
|
+
customTitle: metadata?.customTitle ?? null,
|
|
168
|
+
runnerTitle,
|
|
169
|
+
pinnedAt: metadata?.pinnedAt ?? null,
|
|
170
|
+
pinOrder: metadata?.pinOrder ?? null,
|
|
171
|
+
externalDiscovered: true,
|
|
172
|
+
titleSource: metadata?.customTitle === null || metadata?.customTitle === undefined
|
|
173
|
+
? runnerTitle === null
|
|
174
|
+
? 'AUTO'
|
|
175
|
+
: 'RUNNER'
|
|
176
|
+
: 'CUSTOM',
|
|
177
|
+
lastActivityAt: timestamp,
|
|
178
|
+
createdAt: timestamp
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
remember(sessions) {
|
|
182
|
+
for (const session of sessions)
|
|
183
|
+
this.options.state.directNative.set(session.id, session);
|
|
184
|
+
}
|
|
185
|
+
projectionForNative(native) {
|
|
186
|
+
if (native.externalSessionId === null)
|
|
187
|
+
return undefined;
|
|
188
|
+
const exact = this.options.runtime.findAgentSessionByExternalSession(native.runner, native.externalSessionId);
|
|
189
|
+
if (exact !== undefined &&
|
|
190
|
+
exact.runner === native.runner &&
|
|
191
|
+
exact.workspaceId === native.workspaceId &&
|
|
192
|
+
exact.externalSessionId === native.externalSessionId)
|
|
193
|
+
return exact;
|
|
194
|
+
return this.managedForNative(native);
|
|
195
|
+
}
|
|
196
|
+
managedForNative(native) {
|
|
197
|
+
if (native.externalSessionId === null)
|
|
198
|
+
return undefined;
|
|
199
|
+
const exact = this.options.runtime.findAgentSessionByExternalSession(native.runner, native.externalSessionId);
|
|
200
|
+
if (this.isManagedProjection(exact, native, false))
|
|
201
|
+
return exact;
|
|
202
|
+
const aliasId = this.options.runners
|
|
203
|
+
.require(native.runner)
|
|
204
|
+
.managedAliasId(native.externalSessionId);
|
|
205
|
+
const alias = aliasId === undefined ? undefined : this.options.runtime.getAgentSession(aliasId);
|
|
206
|
+
return this.isManagedProjection(alias, native, true) ? alias : undefined;
|
|
207
|
+
}
|
|
208
|
+
nativeIds(session) {
|
|
209
|
+
const ids = new Set();
|
|
210
|
+
if (session.externalSessionId !== null)
|
|
211
|
+
ids.add(session.externalSessionId);
|
|
212
|
+
for (const nativeId of this.options.runners
|
|
213
|
+
.require(session.runner)
|
|
214
|
+
.nativeAliasesFor(session.id))
|
|
215
|
+
ids.add(nativeId);
|
|
216
|
+
return ids;
|
|
217
|
+
}
|
|
218
|
+
forget(session, knownNativeIds) {
|
|
219
|
+
if (session.externalSessionId === null)
|
|
220
|
+
return;
|
|
221
|
+
const projectionIds = new Set([session.id]);
|
|
222
|
+
const nativeIds = new Set(knownNativeIds);
|
|
223
|
+
const runner = this.options.runners.require(session.runner);
|
|
224
|
+
for (const nativeId of runner.nativeAliasesFor(session.id)) {
|
|
225
|
+
const managedId = runner.managedAliasId(nativeId);
|
|
226
|
+
if (managedId !== undefined && (nativeIds.has(nativeId) || managedId === session.id)) {
|
|
227
|
+
projectionIds.add(managedId);
|
|
228
|
+
nativeIds.add(nativeId);
|
|
229
|
+
runner.forgetNativeAlias(nativeId);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
for (const [directId, direct] of this.options.state.directNative) {
|
|
233
|
+
if (direct.runner === session.runner &&
|
|
234
|
+
direct.cwd === session.cwd &&
|
|
235
|
+
direct.externalSessionId !== null &&
|
|
236
|
+
nativeIds.has(direct.externalSessionId)) {
|
|
237
|
+
this.options.state.directNative.delete(directId);
|
|
238
|
+
this.options.state.externalActivity.delete(directId);
|
|
239
|
+
this.options.state.resumedByDirectId.delete(directId);
|
|
240
|
+
this.options.state.resumeFailures.delete(directId);
|
|
241
|
+
this.options.state.resumeFailureDetails.delete(directId);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
for (const [directId, resumedId] of this.options.state.resumedByDirectId)
|
|
245
|
+
if (projectionIds.has(resumedId))
|
|
246
|
+
this.options.state.resumedByDirectId.delete(directId);
|
|
247
|
+
for (const projectionId of projectionIds) {
|
|
248
|
+
if (this.options.runtime.getAgentSession(projectionId) !== undefined)
|
|
249
|
+
this.options.runtime.removeAgentSession(projectionId);
|
|
250
|
+
this.options.commands.removeSession(projectionId);
|
|
251
|
+
this.options.state.externalActivity.delete(projectionId);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
isManagedProjection(candidate, native, allowAlias) {
|
|
255
|
+
const exactNativeId = candidate?.externalSessionId === native.externalSessionId;
|
|
256
|
+
const verifiedAlias = allowAlias &&
|
|
257
|
+
this.options.runners.require(native.runner).managedAliasId(native.externalSessionId ?? '') ===
|
|
258
|
+
candidate?.id;
|
|
259
|
+
return (candidate !== undefined &&
|
|
260
|
+
candidate.nativeControl === 'MAR_MANAGED' &&
|
|
261
|
+
candidate.runner === native.runner &&
|
|
262
|
+
candidate.workspaceId === native.workspaceId &&
|
|
263
|
+
(exactNativeId || verifiedAlias));
|
|
264
|
+
}
|
|
265
|
+
requireDatabase() {
|
|
266
|
+
const database = this.options.database();
|
|
267
|
+
if (database === undefined)
|
|
268
|
+
throw new Error('NODE_DATABASE_UNAVAILABLE');
|
|
269
|
+
return database;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { NodeAgentSession } from '../database.js';
|
|
2
|
+
import type { NodeOperationHandler } from '../connector/node-operation-router.js';
|
|
3
|
+
export interface NativeSessionWatchOptions<TPage extends {
|
|
4
|
+
readonly turns: readonly unknown[];
|
|
5
|
+
}> {
|
|
6
|
+
readonly resolve: (sessionId: string) => Promise<NodeAgentSession | undefined>;
|
|
7
|
+
readonly managedActive: (session: NodeAgentSession) => boolean;
|
|
8
|
+
readonly refreshActivity: (session: NodeAgentSession) => Promise<void>;
|
|
9
|
+
readonly present: (session: NodeAgentSession) => unknown;
|
|
10
|
+
readonly readPage: (session: NodeAgentSession, limit: number) => Promise<TPage>;
|
|
11
|
+
readonly emitSession: (session: unknown) => void;
|
|
12
|
+
readonly emitTurn: (turn: unknown) => void;
|
|
13
|
+
}
|
|
14
|
+
export declare class NativeSessionWatchService<TPage extends {
|
|
15
|
+
readonly turns: readonly unknown[];
|
|
16
|
+
}> {
|
|
17
|
+
private readonly options;
|
|
18
|
+
private readonly watches;
|
|
19
|
+
constructor(options: NativeSessionWatchOptions<TPage>);
|
|
20
|
+
operations(): Readonly<Record<string, NodeOperationHandler>>;
|
|
21
|
+
private executeWatch;
|
|
22
|
+
watch(session: NodeAgentSession): {
|
|
23
|
+
readonly expiresAt: number;
|
|
24
|
+
} | undefined;
|
|
25
|
+
renew(session: NodeAgentSession): {
|
|
26
|
+
readonly expiresAt: number;
|
|
27
|
+
} | undefined;
|
|
28
|
+
stop(sessionId: string): void;
|
|
29
|
+
clear(): void;
|
|
30
|
+
has(sessionId: string): boolean;
|
|
31
|
+
entry(sessionId: string): {
|
|
32
|
+
readonly expiresAt: number;
|
|
33
|
+
} | undefined;
|
|
34
|
+
get size(): number;
|
|
35
|
+
refresh(sessionId: string, limit?: number): Promise<TPage | undefined>;
|
|
36
|
+
private performRefresh;
|
|
37
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { isPlainRecord } from '../runner/codex/conversation-parser.js';
|
|
2
|
+
const INTERVAL_MS = 2_000;
|
|
3
|
+
const TTL_MS = 45_000;
|
|
4
|
+
const MAX_FAILURES = 3;
|
|
5
|
+
const INITIAL_TURN_LIMIT = 30;
|
|
6
|
+
export class NativeSessionWatchService {
|
|
7
|
+
options;
|
|
8
|
+
watches = new Map();
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
}
|
|
12
|
+
operations() {
|
|
13
|
+
return {
|
|
14
|
+
'session.watch': (data) => this.executeWatch(isPlainRecord(data) ? data : {})
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
async executeWatch(input) {
|
|
18
|
+
if (typeof input.sessionId !== 'string' || (input.mode !== 'initial' && input.mode !== 'renew'))
|
|
19
|
+
throw new Error('SESSION_INVALID');
|
|
20
|
+
const session = await this.options.resolve(input.sessionId);
|
|
21
|
+
if (session === undefined || session.externalSessionId === null)
|
|
22
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
23
|
+
if (input.mode === 'renew') {
|
|
24
|
+
const watch = this.renew(session);
|
|
25
|
+
if (watch === undefined)
|
|
26
|
+
throw new Error('SESSION_WATCH_NOT_FOUND');
|
|
27
|
+
return { renewed: true };
|
|
28
|
+
}
|
|
29
|
+
const watch = this.watch(session);
|
|
30
|
+
const snapshot = watch === undefined ? undefined : await this.refresh(session.id, INITIAL_TURN_LIMIT);
|
|
31
|
+
if (watch !== undefined && snapshot === undefined)
|
|
32
|
+
throw new Error('NATIVE_TRANSCRIPT_UNAVAILABLE');
|
|
33
|
+
return {
|
|
34
|
+
session: this.options.present(session),
|
|
35
|
+
watch: watch === undefined ? null : { expiresAt: watch.expiresAt },
|
|
36
|
+
...(snapshot === undefined ? {} : { snapshot })
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
watch(session) {
|
|
40
|
+
if (this.options.managedActive(session)) {
|
|
41
|
+
this.stop(session.id);
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const existing = this.watches.get(session.id);
|
|
45
|
+
if (existing !== undefined) {
|
|
46
|
+
existing.expiresAt = Date.now() + TTL_MS;
|
|
47
|
+
return { expiresAt: existing.expiresAt };
|
|
48
|
+
}
|
|
49
|
+
const watch = {
|
|
50
|
+
expiresAt: Date.now() + TTL_MS,
|
|
51
|
+
refresh: undefined,
|
|
52
|
+
sessionSignature: undefined,
|
|
53
|
+
signature: undefined,
|
|
54
|
+
failures: 0,
|
|
55
|
+
timer: undefined
|
|
56
|
+
};
|
|
57
|
+
watch.timer = setInterval(() => void this.refresh(session.id), INTERVAL_MS);
|
|
58
|
+
watch.timer.unref();
|
|
59
|
+
this.watches.set(session.id, watch);
|
|
60
|
+
return { expiresAt: watch.expiresAt };
|
|
61
|
+
}
|
|
62
|
+
renew(session) {
|
|
63
|
+
if (this.options.managedActive(session)) {
|
|
64
|
+
this.stop(session.id);
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const watch = this.watches.get(session.id);
|
|
68
|
+
if (watch === undefined || watch.expiresAt <= Date.now()) {
|
|
69
|
+
this.stop(session.id);
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
watch.expiresAt = Date.now() + TTL_MS;
|
|
73
|
+
return { expiresAt: watch.expiresAt };
|
|
74
|
+
}
|
|
75
|
+
stop(sessionId) {
|
|
76
|
+
const watch = this.watches.get(sessionId);
|
|
77
|
+
if (watch === undefined)
|
|
78
|
+
return;
|
|
79
|
+
clearInterval(watch.timer);
|
|
80
|
+
this.watches.delete(sessionId);
|
|
81
|
+
}
|
|
82
|
+
clear() {
|
|
83
|
+
for (const watch of this.watches.values())
|
|
84
|
+
clearInterval(watch.timer);
|
|
85
|
+
this.watches.clear();
|
|
86
|
+
}
|
|
87
|
+
has(sessionId) {
|
|
88
|
+
return this.watches.has(sessionId);
|
|
89
|
+
}
|
|
90
|
+
entry(sessionId) {
|
|
91
|
+
return this.watches.get(sessionId);
|
|
92
|
+
}
|
|
93
|
+
get size() {
|
|
94
|
+
return this.watches.size;
|
|
95
|
+
}
|
|
96
|
+
async refresh(sessionId, limit = 10) {
|
|
97
|
+
const watch = this.watches.get(sessionId);
|
|
98
|
+
if (watch === undefined)
|
|
99
|
+
return undefined;
|
|
100
|
+
if (watch.refresh !== undefined)
|
|
101
|
+
return watch.refresh;
|
|
102
|
+
if (watch.expiresAt <= Date.now()) {
|
|
103
|
+
this.stop(sessionId);
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
const refresh = this.performRefresh(sessionId, watch, limit);
|
|
107
|
+
watch.refresh = refresh;
|
|
108
|
+
try {
|
|
109
|
+
return await refresh;
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
if (this.watches.get(sessionId) === watch)
|
|
113
|
+
watch.refresh = undefined;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async performRefresh(sessionId, watch, limit) {
|
|
117
|
+
try {
|
|
118
|
+
const session = await this.options.resolve(sessionId);
|
|
119
|
+
if (session === undefined ||
|
|
120
|
+
session.externalSessionId === null ||
|
|
121
|
+
this.options.managedActive(session)) {
|
|
122
|
+
this.stop(sessionId);
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
await this.options.refreshActivity(session);
|
|
126
|
+
const presented = this.options.present(session);
|
|
127
|
+
const sessionSignature = JSON.stringify(presented);
|
|
128
|
+
if (sessionSignature !== watch.sessionSignature) {
|
|
129
|
+
watch.sessionSignature = sessionSignature;
|
|
130
|
+
this.options.emitSession(presented);
|
|
131
|
+
}
|
|
132
|
+
const page = await this.options.readPage(session, limit);
|
|
133
|
+
watch.failures = 0;
|
|
134
|
+
const turns = page.turns.length <= 10 ? page.turns : page.turns.slice(0, 10);
|
|
135
|
+
const signature = JSON.stringify(turns);
|
|
136
|
+
if (signature === watch.signature)
|
|
137
|
+
return page;
|
|
138
|
+
watch.signature = signature;
|
|
139
|
+
for (const turn of turns)
|
|
140
|
+
this.options.emitTurn(turn);
|
|
141
|
+
return page;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
watch.failures += 1;
|
|
145
|
+
if (watch.failures >= MAX_FAILURES)
|
|
146
|
+
this.stop(sessionId);
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { NodeCapabilities } from '@myagentroam/protocol';
|
|
2
|
+
import { type NodeConfig } from '../config.js';
|
|
3
|
+
import type { NodeMetrics } from '../operational.js';
|
|
4
|
+
import type { NodeControlChannel } from './node-control-channel.js';
|
|
5
|
+
import type { NodeTerminalChannel } from './node-terminal-channel.js';
|
|
6
|
+
import type { WorkbenchEventService } from './workbench-event-service.js';
|
|
7
|
+
interface NodeConnectionLifecycleServiceOptions {
|
|
8
|
+
readonly config: () => NodeConfig | undefined;
|
|
9
|
+
readonly setConfig: (config: NodeConfig) => void;
|
|
10
|
+
readonly configPath: string | undefined;
|
|
11
|
+
readonly capabilities: () => NodeCapabilities;
|
|
12
|
+
readonly detectCapabilities: () => NodeCapabilities | Promise<NodeCapabilities>;
|
|
13
|
+
readonly capabilitiesProvided: boolean;
|
|
14
|
+
readonly stopped: () => boolean;
|
|
15
|
+
readonly setCapabilities: (capabilities: NodeCapabilities) => void;
|
|
16
|
+
readonly control: NodeControlChannel;
|
|
17
|
+
readonly terminal: NodeTerminalChannel;
|
|
18
|
+
readonly events: WorkbenchEventService;
|
|
19
|
+
readonly metrics: NodeMetrics;
|
|
20
|
+
readonly heartbeatMs: number;
|
|
21
|
+
readonly refreshMs: number;
|
|
22
|
+
}
|
|
23
|
+
/** Owns registration completion, heartbeat and background capability refresh timers. */
|
|
24
|
+
export declare class NodeConnectionLifecycleService {
|
|
25
|
+
private readonly options;
|
|
26
|
+
private heartbeat;
|
|
27
|
+
private refreshTimer;
|
|
28
|
+
private refreshing;
|
|
29
|
+
constructor(options: NodeConnectionLifecycleServiceOptions);
|
|
30
|
+
start(): void;
|
|
31
|
+
startCapabilityRefresh(): void;
|
|
32
|
+
stop(): void;
|
|
33
|
+
register(nodeId: string, credential: string): Promise<void>;
|
|
34
|
+
connected(): void;
|
|
35
|
+
refreshCapabilities(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { saveNodeConfig } from '../config.js';
|
|
2
|
+
/** Owns registration completion, heartbeat and background capability refresh timers. */
|
|
3
|
+
export class NodeConnectionLifecycleService {
|
|
4
|
+
options;
|
|
5
|
+
heartbeat;
|
|
6
|
+
refreshTimer;
|
|
7
|
+
refreshing = false;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
}
|
|
11
|
+
start() {
|
|
12
|
+
this.startCapabilityRefresh();
|
|
13
|
+
}
|
|
14
|
+
startCapabilityRefresh() {
|
|
15
|
+
if (this.refreshTimer !== undefined)
|
|
16
|
+
clearInterval(this.refreshTimer);
|
|
17
|
+
if (!this.options.capabilitiesProvided)
|
|
18
|
+
void this.refreshCapabilities();
|
|
19
|
+
this.refreshTimer = setInterval(() => void this.refreshCapabilities(), this.options.refreshMs);
|
|
20
|
+
}
|
|
21
|
+
stop() {
|
|
22
|
+
if (this.heartbeat !== undefined)
|
|
23
|
+
clearInterval(this.heartbeat);
|
|
24
|
+
if (this.refreshTimer !== undefined)
|
|
25
|
+
clearInterval(this.refreshTimer);
|
|
26
|
+
this.heartbeat = undefined;
|
|
27
|
+
this.refreshTimer = undefined;
|
|
28
|
+
}
|
|
29
|
+
async register(nodeId, credential) {
|
|
30
|
+
const config = this.options.config();
|
|
31
|
+
if (config === undefined)
|
|
32
|
+
return;
|
|
33
|
+
const registered = {
|
|
34
|
+
serverUrl: config.serverUrl,
|
|
35
|
+
allowedRoots: config.allowedRoots,
|
|
36
|
+
...(config.databasePath === undefined ? {} : { databasePath: config.databasePath }),
|
|
37
|
+
...(config.upgrade === undefined ? {} : { upgrade: config.upgrade }),
|
|
38
|
+
nodeId,
|
|
39
|
+
credential
|
|
40
|
+
};
|
|
41
|
+
this.options.setConfig(registered);
|
|
42
|
+
await saveNodeConfig(registered, this.options.configPath);
|
|
43
|
+
this.connected();
|
|
44
|
+
}
|
|
45
|
+
connected() {
|
|
46
|
+
if (this.heartbeat !== undefined)
|
|
47
|
+
clearInterval(this.heartbeat);
|
|
48
|
+
this.options.control.send('capabilities', this.options.capabilities());
|
|
49
|
+
this.options.terminal.connect();
|
|
50
|
+
this.options.control.send('heartbeat', {});
|
|
51
|
+
this.options.metrics.heartbeatSent();
|
|
52
|
+
this.options.events.replay();
|
|
53
|
+
this.heartbeat = setInterval(() => {
|
|
54
|
+
this.options.metrics.heartbeatSent();
|
|
55
|
+
this.options.control.send('heartbeat', {});
|
|
56
|
+
}, this.options.heartbeatMs);
|
|
57
|
+
}
|
|
58
|
+
async refreshCapabilities() {
|
|
59
|
+
if (this.options.stopped() || this.refreshing)
|
|
60
|
+
return;
|
|
61
|
+
this.refreshing = true;
|
|
62
|
+
try {
|
|
63
|
+
let next;
|
|
64
|
+
try {
|
|
65
|
+
next = await this.options.detectCapabilities();
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
next = {
|
|
69
|
+
...this.options.capabilities(),
|
|
70
|
+
codex: { available: false },
|
|
71
|
+
claudeCode: { available: false },
|
|
72
|
+
openCode: { available: false }
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (this.options.stopped() ||
|
|
76
|
+
JSON.stringify(next) === JSON.stringify(this.options.capabilities()))
|
|
77
|
+
return;
|
|
78
|
+
this.options.setCapabilities(next);
|
|
79
|
+
this.options.control.send('capabilities', next);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
this.refreshing = false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import WebSocket from 'ws';
|
|
2
|
+
import { type NodeCapabilities, type NodeEnvelope } from '@myagentroam/protocol';
|
|
3
|
+
import type { NodeConfig } from '../config.js';
|
|
4
|
+
interface NodeControlChannelOptions {
|
|
5
|
+
readonly config: () => NodeConfig | undefined;
|
|
6
|
+
readonly capabilities: () => NodeCapabilities;
|
|
7
|
+
readonly stopped: () => boolean;
|
|
8
|
+
readonly reconnect: boolean;
|
|
9
|
+
readonly reconnectDelay: (attempt: number) => number;
|
|
10
|
+
readonly onConnecting: (attempt: number) => void;
|
|
11
|
+
readonly onReconnectScheduled: () => void;
|
|
12
|
+
readonly onMessage: (raw: string) => void;
|
|
13
|
+
readonly onClose: (code: number, reason: string) => void;
|
|
14
|
+
}
|
|
15
|
+
export declare class NodeControlChannel {
|
|
16
|
+
private readonly options;
|
|
17
|
+
private socket;
|
|
18
|
+
private readonly outbound;
|
|
19
|
+
private reconnectTimer;
|
|
20
|
+
private attempts;
|
|
21
|
+
constructor(options: NodeControlChannelOptions);
|
|
22
|
+
connect(): void;
|
|
23
|
+
connected(): boolean;
|
|
24
|
+
/** Keeps transport injection at the transport boundary for contract tests. */
|
|
25
|
+
replaceSocketForTesting(socket: WebSocket | undefined): void;
|
|
26
|
+
close(code?: number, reason?: string): void;
|
|
27
|
+
stop(): void;
|
|
28
|
+
send(type: string, payload: unknown, replyTo?: string): void;
|
|
29
|
+
sendEnvelope(envelope: NodeEnvelope): void;
|
|
30
|
+
private scheduleReconnect;
|
|
31
|
+
}
|
|
32
|
+
export {};
|