@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.
Files changed (195) hide show
  1. package/README.md +9 -1
  2. package/dist/capabilities.d.ts +2 -0
  3. package/dist/capabilities.js +15 -4
  4. package/dist/claude-agent-sdk.d.ts +4 -16
  5. package/dist/claude-agent-sdk.js +18 -79
  6. package/dist/claude-channel.js +0 -1
  7. package/dist/codex-app-server.d.ts +5 -0
  8. package/dist/codex-app-server.js +26 -5
  9. package/dist/config.d.ts +3 -0
  10. package/dist/config.js +18 -2
  11. package/dist/connector/node-connector-options.d.ts +26 -0
  12. package/dist/connector/node-connector-options.js +1 -0
  13. package/dist/connector/node-operation-router.d.ts +9 -0
  14. package/dist/connector/node-operation-router.js +22 -0
  15. package/dist/connector.d.ts +62 -356
  16. package/dist/connector.js +714 -6176
  17. package/dist/control-outbound-scheduler.d.ts +26 -0
  18. package/dist/control-outbound-scheduler.js +121 -0
  19. package/dist/database.d.ts +4 -0
  20. package/dist/database.js +15 -10
  21. package/dist/event-buffer.js +0 -1
  22. package/dist/fake-runner.js +0 -1
  23. package/dist/health.js +0 -1
  24. package/dist/main.js +6 -1
  25. package/dist/migrations/v001.js +0 -1
  26. package/dist/migrations/v002.js +0 -1
  27. package/dist/migrations/v003.d.ts +4 -0
  28. package/dist/migrations/v003.js +44 -0
  29. package/dist/native-jsonl-reader.d.ts +14 -0
  30. package/dist/native-jsonl-reader.js +97 -0
  31. package/dist/native-session-history.d.ts +41 -4
  32. package/dist/native-session-history.js +340 -159
  33. package/dist/opencode-runtime.d.ts +33 -0
  34. package/dist/opencode-runtime.js +80 -0
  35. package/dist/opencode-server.d.ts +47 -0
  36. package/dist/opencode-server.js +250 -0
  37. package/dist/operational.js +10 -3
  38. package/dist/process-tree.js +0 -1
  39. package/dist/runner/abstract-runner.d.ts +153 -0
  40. package/dist/runner/abstract-runner.js +154 -0
  41. package/dist/runner/claude/managed-run-controller.d.ts +52 -0
  42. package/dist/runner/claude/managed-run-controller.js +326 -0
  43. package/dist/runner/claude-code-runner.d.ts +70 -0
  44. package/dist/runner/claude-code-runner.js +286 -0
  45. package/dist/runner/codex/conversation-parser.d.ts +59 -0
  46. package/dist/runner/codex/conversation-parser.js +816 -0
  47. package/dist/runner/codex/managed-run-controller.d.ts +80 -0
  48. package/dist/runner/codex/managed-run-controller.js +388 -0
  49. package/dist/runner/codex-runner.d.ts +85 -0
  50. package/dist/runner/codex-runner.js +365 -0
  51. package/dist/runner/fake-test-runner.d.ts +26 -0
  52. package/dist/runner/fake-test-runner.js +47 -0
  53. package/dist/runner/opencode/conversation-parser.d.ts +27 -0
  54. package/dist/runner/opencode/conversation-parser.js +217 -0
  55. package/dist/runner/opencode/managed-run-controller.d.ts +76 -0
  56. package/dist/runner/opencode/managed-run-controller.js +451 -0
  57. package/dist/runner/opencode-runner.d.ts +60 -0
  58. package/dist/runner/opencode-runner.js +330 -0
  59. package/dist/runner/runner-registry.d.ts +19 -0
  60. package/dist/runner/runner-registry.js +75 -0
  61. package/dist/runner-command-engine.js +0 -1
  62. package/dist/runner-profiles.d.ts +4 -0
  63. package/dist/runner-profiles.js +76 -3
  64. package/dist/runner-usage.d.ts +2 -2
  65. package/dist/runner-usage.js +3 -6
  66. package/dist/runtime-state.js +26 -14
  67. package/dist/service/attachment-domain-state.d.ts +5 -0
  68. package/dist/service/attachment-domain-state.js +5 -0
  69. package/dist/service/conversation-history-service.d.ts +34 -0
  70. package/dist/service/conversation-history-service.js +158 -0
  71. package/dist/service/external-session-resume-service.d.ts +32 -0
  72. package/dist/service/external-session-resume-service.js +98 -0
  73. package/dist/service/fake-managed-run-service.d.ts +29 -0
  74. package/dist/service/fake-managed-run-service.js +62 -0
  75. package/dist/service/managed-runner-session-service.d.ts +12 -0
  76. package/dist/service/managed-runner-session-service.js +35 -0
  77. package/dist/service/native-session-projection-service.d.ts +51 -0
  78. package/dist/service/native-session-projection-service.js +271 -0
  79. package/dist/service/native-session-watch-service.d.ts +37 -0
  80. package/dist/service/native-session-watch-service.js +150 -0
  81. package/dist/service/node-connection-lifecycle-service.d.ts +37 -0
  82. package/dist/service/node-connection-lifecycle-service.js +85 -0
  83. package/dist/service/node-control-channel.d.ts +32 -0
  84. package/dist/service/node-control-channel.js +85 -0
  85. package/dist/service/node-control-message-service.d.ts +28 -0
  86. package/dist/service/node-control-message-service.js +104 -0
  87. package/dist/service/node-request-service.d.ts +14 -0
  88. package/dist/service/node-request-service.js +50 -0
  89. package/dist/service/node-run-event-bridge.d.ts +40 -0
  90. package/dist/service/node-run-event-bridge.js +83 -0
  91. package/dist/service/node-terminal-channel.d.ts +29 -0
  92. package/dist/service/node-terminal-channel.js +182 -0
  93. package/dist/service/node-upgrade-coordinator.d.ts +29 -0
  94. package/dist/service/node-upgrade-coordinator.js +137 -0
  95. package/dist/service/node-workspace-coordinator.d.ts +32 -0
  96. package/dist/service/node-workspace-coordinator.js +80 -0
  97. package/dist/service/run-attachment-service.d.ts +37 -0
  98. package/dist/service/run-attachment-service.js +160 -0
  99. package/dist/service/run-domain-state.d.ts +4 -0
  100. package/dist/service/run-domain-state.js +4 -0
  101. package/dist/service/run-event-service.d.ts +31 -0
  102. package/dist/service/run-event-service.js +102 -0
  103. package/dist/service/run-workbench-service.d.ts +27 -0
  104. package/dist/service/run-workbench-service.js +113 -0
  105. package/dist/service/runner-channel-message-service.d.ts +13 -0
  106. package/dist/service/runner-channel-message-service.js +20 -0
  107. package/dist/service/runner-interaction-service.d.ts +40 -0
  108. package/dist/service/runner-interaction-service.js +246 -0
  109. package/dist/service/runner-service.d.ts +44 -0
  110. package/dist/service/runner-service.js +68 -0
  111. package/dist/service/session-catalog-service.d.ts +45 -0
  112. package/dist/service/session-catalog-service.js +224 -0
  113. package/dist/service/session-command-service.d.ts +23 -0
  114. package/dist/service/session-command-service.js +69 -0
  115. package/dist/service/session-context-service.d.ts +34 -0
  116. package/dist/service/session-context-service.js +87 -0
  117. package/dist/service/session-domain-state.d.ts +8 -0
  118. package/dist/service/session-domain-state.js +8 -0
  119. package/dist/service/session-identity-service.d.ts +24 -0
  120. package/dist/service/session-identity-service.js +86 -0
  121. package/dist/service/session-lifecycle-service.d.ts +31 -0
  122. package/dist/service/session-lifecycle-service.js +242 -0
  123. package/dist/service/session-message-service.d.ts +33 -0
  124. package/dist/service/session-message-service.js +157 -0
  125. package/dist/service/session-presentation-service.d.ts +33 -0
  126. package/dist/service/session-presentation-service.js +36 -0
  127. package/dist/service/session-query-service.d.ts +31 -0
  128. package/dist/service/session-query-service.js +77 -0
  129. package/dist/service/terminal-relay-state.d.ts +6 -0
  130. package/dist/service/terminal-relay-state.js +3 -0
  131. package/dist/service/terminal-service.d.ts +12 -0
  132. package/dist/service/terminal-service.js +52 -0
  133. package/dist/service/workbench-event-service.d.ts +21 -0
  134. package/dist/service/workbench-event-service.js +53 -0
  135. package/dist/service/workbench-manifest-service.d.ts +89 -0
  136. package/dist/service/workbench-manifest-service.js +70 -0
  137. package/dist/service/workspace-change-service.d.ts +9 -0
  138. package/dist/service/workspace-change-service.js +86 -0
  139. package/dist/service/workspace-content-search-service.d.ts +14 -0
  140. package/dist/service/workspace-content-search-service.js +477 -0
  141. package/dist/service/workspace-domain-state.d.ts +75 -0
  142. package/dist/service/workspace-domain-state.js +40 -0
  143. package/dist/service/workspace-file-service.d.ts +17 -0
  144. package/dist/service/workspace-file-service.js +109 -0
  145. package/dist/service/workspace-queue-workbench-service.d.ts +55 -0
  146. package/dist/service/workspace-queue-workbench-service.js +226 -0
  147. package/dist/service/workspace-service.d.ts +15 -0
  148. package/dist/service/workspace-service.js +67 -0
  149. package/dist/service/workspace-session-service.d.ts +23 -0
  150. package/dist/service/workspace-session-service.js +139 -0
  151. package/dist/service/workspace-upload-service.d.ts +37 -0
  152. package/dist/service/workspace-upload-service.js +240 -0
  153. package/dist/service/workspace-watch-service.d.ts +18 -0
  154. package/dist/service/workspace-watch-service.js +120 -0
  155. package/dist/service/workspace-workbench-service.d.ts +47 -0
  156. package/dist/service/workspace-workbench-service.js +223 -0
  157. package/dist/service.js +2 -3
  158. package/dist/storage.js +0 -1
  159. package/dist/supervisor.d.ts +1 -0
  160. package/dist/supervisor.js +154 -0
  161. package/dist/terminal.d.ts +1 -0
  162. package/dist/terminal.js +1 -2
  163. package/dist/util/node-operation-parsers.d.ts +92 -0
  164. package/dist/util/node-operation-parsers.js +382 -0
  165. package/dist/util/runner-native-session-parsers.d.ts +72 -0
  166. package/dist/util/runner-native-session-parsers.js +381 -0
  167. package/dist/util/secret-environment.d.ts +1 -0
  168. package/dist/util/secret-environment.js +25 -0
  169. package/dist/workspace.d.ts +62 -2
  170. package/dist/workspace.js +539 -57
  171. package/package.json +3 -2
  172. package/dist/capabilities.js.map +0 -1
  173. package/dist/claude-agent-sdk.js.map +0 -1
  174. package/dist/claude-channel.js.map +0 -1
  175. package/dist/codex-app-server.js.map +0 -1
  176. package/dist/config.js.map +0 -1
  177. package/dist/connector.js.map +0 -1
  178. package/dist/database.js.map +0 -1
  179. package/dist/event-buffer.js.map +0 -1
  180. package/dist/fake-runner.js.map +0 -1
  181. package/dist/health.js.map +0 -1
  182. package/dist/main.js.map +0 -1
  183. package/dist/migrations/v001.js.map +0 -1
  184. package/dist/migrations/v002.js.map +0 -1
  185. package/dist/native-session-history.js.map +0 -1
  186. package/dist/operational.js.map +0 -1
  187. package/dist/process-tree.js.map +0 -1
  188. package/dist/runner-command-engine.js.map +0 -1
  189. package/dist/runner-profiles.js.map +0 -1
  190. package/dist/runner-usage.js.map +0 -1
  191. package/dist/runtime-state.js.map +0 -1
  192. package/dist/service.js.map +0 -1
  193. package/dist/storage.js.map +0 -1
  194. package/dist/terminal.js.map +0 -1
  195. package/dist/workspace.js.map +0 -1
@@ -0,0 +1,330 @@
1
+ import { OpenCodeServerClient } from '../opencode-server.js';
2
+ import { openCodeContextLimits, openCodeProfileModels } from '../opencode-runtime.js';
3
+ import { declaredRunnerProfiles } from '../runner-profiles.js';
4
+ import { AbstractRunner } from './abstract-runner.js';
5
+ import { isWithinWorkspace } from './codex/conversation-parser.js';
6
+ import { createHash } from 'node:crypto';
7
+ import { openCodeConversationPage } from './opencode/conversation-parser.js';
8
+ export class OpenCodeRunner extends AbstractRunner {
9
+ client;
10
+ clientFactory;
11
+ name = 'opencode';
12
+ discoveryScope = 'WORKSPACE_REQUIRED';
13
+ modelsByWorkspace = new Map();
14
+ contextLimitsByWorkspace = new Map();
15
+ execution = new Map();
16
+ managedTurns = new Map();
17
+ environmentClients = new Map();
18
+ stoppingClients = new Set();
19
+ constructor(client = new OpenCodeServerClient(), clientFactory = () => new OpenCodeServerClient()) {
20
+ super();
21
+ this.client = client;
22
+ this.clientFactory = clientFactory;
23
+ }
24
+ profile(capabilities) {
25
+ const base = declaredRunnerProfiles({
26
+ codexAvailable: capabilities.codex.available,
27
+ claudeCodeAvailable: capabilities.claudeCode.available,
28
+ openCodeAvailable: capabilities.openCode?.available === true
29
+ }).find((profile) => profile.runner === this.name);
30
+ return { ...base, models: [] };
31
+ }
32
+ async refreshProfile(capabilities, workspace, environment = {}) {
33
+ if (capabilities.openCode?.available !== true || workspace === undefined) {
34
+ return this.profile(capabilities);
35
+ }
36
+ const client = this.managedClient(environment);
37
+ try {
38
+ const catalog = await client.providers(workspace.path);
39
+ const models = openCodeProfileModels(catalog);
40
+ this.modelsByWorkspace.set(workspace.path, models);
41
+ this.contextLimitsByWorkspace.set(workspace.path, openCodeContextLimits(catalog));
42
+ return { ...this.profile(capabilities), models };
43
+ }
44
+ catch {
45
+ this.modelsByWorkspace.delete(workspace.path);
46
+ this.contextLimitsByWorkspace.delete(workspace.path);
47
+ }
48
+ finally {
49
+ this.releaseManagedClient(client);
50
+ }
51
+ return this.profile(capabilities);
52
+ }
53
+ available(capabilities) {
54
+ return capabilities.openCode?.available === true;
55
+ }
56
+ profileForValidation() {
57
+ return this.profile({
58
+ platform: 'linux',
59
+ architecture: 'validation',
60
+ nodeAppVersion: 'validation',
61
+ nodeVersion: 'validation',
62
+ codex: { available: false },
63
+ claudeCode: { available: false },
64
+ openCode: { available: true }
65
+ });
66
+ }
67
+ supportsConfiguration(configuration, workspace) {
68
+ if (configuration.runner !== this.name)
69
+ return false;
70
+ const models = workspace === undefined
71
+ ? [...this.modelsByWorkspace.values()].flat()
72
+ : (this.modelsByWorkspace.get(workspace.path) ?? []);
73
+ const model = models.find((candidate) => candidate.id === configuration.model);
74
+ return (model !== undefined &&
75
+ (model.supportedEfforts.length === 0 ||
76
+ model.supportedEfforts.includes(configuration.effort)) &&
77
+ this.profileForValidation().accessOptions.some((candidate) => candidate.id === configuration.access));
78
+ }
79
+ defaultConfiguration(workspace) {
80
+ const model = workspace === undefined
81
+ ? this.modelsByWorkspace.values().next().value?.[0]
82
+ : this.modelsByWorkspace.get(workspace.path)?.[0];
83
+ return {
84
+ runner: this.name,
85
+ model: model?.id ?? '',
86
+ effort: model?.supportedEfforts[0] ?? '',
87
+ access: 'default'
88
+ };
89
+ }
90
+ async discoverSessions(context, workspace) {
91
+ const result = [];
92
+ for (const target of workspace === undefined ? context.workspaces : [workspace]) {
93
+ let sessions;
94
+ try {
95
+ sessions = await this.client.sessions(target.path);
96
+ }
97
+ catch {
98
+ continue;
99
+ }
100
+ for (const value of sessions) {
101
+ const session = openCodeSession(value);
102
+ if (session === undefined || !isWithinWorkspace(session.directory, target.path))
103
+ continue;
104
+ result.push(context.createProjection({
105
+ workspace: target,
106
+ runner: this.name,
107
+ externalSessionId: session.id,
108
+ cwd: session.directory,
109
+ title: session.title,
110
+ titleOrigin: 'OFFICIAL',
111
+ lastActivityAt: session.updatedAt
112
+ }));
113
+ }
114
+ }
115
+ return result;
116
+ }
117
+ async resumeExternal(external, context) {
118
+ if (external.runner !== this.name || external.externalSessionId === null || !context.available)
119
+ return { failureCode: 'RUNNER_UNAVAILABLE' };
120
+ const activity = await this.readExternalActivity(external);
121
+ if (activity !== 'IDLE')
122
+ return {
123
+ activity: activity ?? 'UNAVAILABLE',
124
+ failureCode: activity === 'EXTERNAL_ACTIVE' ? 'EXTERNAL_SESSION_ACTIVE' : 'RUNNER_RESUME_FAILED'
125
+ };
126
+ const projection = context.projectionFor(external);
127
+ const session = projection === undefined ? context.create(external) : context.promote(projection.id);
128
+ return session === undefined
129
+ ? { activity: 'UNAVAILABLE', failureCode: 'RUNNER_RESUME_FAILED' }
130
+ : { activity: 'IDLE', session: context.ensureConfiguration(session) };
131
+ }
132
+ async readExternalActivity(session) {
133
+ if (session.externalSessionId === null)
134
+ return 'UNAVAILABLE';
135
+ try {
136
+ const status = await this.client.sessionStatus(session.cwd);
137
+ const value = status[session.externalSessionId];
138
+ return value?.type === 'busy' || value?.type === 'retry'
139
+ ? 'EXTERNAL_ACTIVE'
140
+ : value?.type === 'idle' || value === undefined
141
+ ? 'IDLE'
142
+ : 'UNAVAILABLE';
143
+ }
144
+ catch {
145
+ return 'UNAVAILABLE';
146
+ }
147
+ }
148
+ async readOfficialConversation(session, input, managedTurn) {
149
+ if (session.externalSessionId === null)
150
+ return undefined;
151
+ try {
152
+ return openCodeConversationPage(session, await this.client.messages(session.cwd, session.externalSessionId, 500), input, managedTurn);
153
+ }
154
+ catch {
155
+ return undefined;
156
+ }
157
+ }
158
+ managedRunForNativeTurn(nativeTurnId) {
159
+ return this.managedTurns.get(nativeTurnId);
160
+ }
161
+ presentSession(session, capabilities, context) {
162
+ const available = capabilities.openCode?.available === true;
163
+ const managed = session.nativeControl === 'MAR_MANAGED';
164
+ const activity = context.managedActive
165
+ ? 'MANAGED_ACTIVE'
166
+ : (context.externalActivity ?? (available ? 'IDLE' : 'UNAVAILABLE'));
167
+ if (!available)
168
+ return {
169
+ activity: 'UNAVAILABLE',
170
+ control: { level: 'UNAVAILABLE', capabilities: [] },
171
+ runtime: { level: 'UNAVAILABLE', capabilities: [], reasonCode: 'RUNNER_UNAVAILABLE' },
172
+ commandStates: context.commandStates
173
+ };
174
+ const runtimeCapabilities = [
175
+ 'history.read',
176
+ 'events.live',
177
+ 'turn.start',
178
+ 'turn.queue',
179
+ 'turn.interrupt',
180
+ 'run.cancel',
181
+ 'approval.respond',
182
+ 'user-input.respond',
183
+ 'session.rename',
184
+ 'session.remove',
185
+ 'session.pin',
186
+ ...(session.externalSessionId === null ? [] : ['session.rewind'])
187
+ ];
188
+ return {
189
+ activity,
190
+ control: {
191
+ level: managed ? 'MANAGED' : activity === 'IDLE' ? 'RESUMABLE' : 'OBSERVE_ONLY',
192
+ capabilities: managed || activity === 'IDLE'
193
+ ? ['history.read', 'events.live', 'turn.start', 'turn.interrupt']
194
+ : ['history.read', 'events.live']
195
+ },
196
+ runtime: {
197
+ level: managed ? 'MANAGED' : activity === 'IDLE' ? 'RESUMABLE' : 'OBSERVE_ONLY',
198
+ capabilities: runtimeCapabilities,
199
+ reasonCode: activity === 'EXTERNAL_ACTIVE' ? 'EXTERNAL_SESSION_OBSERVE_ONLY' : null
200
+ },
201
+ commandStates: context.commandStates
202
+ };
203
+ }
204
+ async renameNative(session, input, database) {
205
+ if (session.externalSessionId !== null && input.title !== undefined)
206
+ await this.client.updateSession(session.cwd, session.externalSessionId, input.title ?? '');
207
+ await super.renameNative(session, input, database);
208
+ }
209
+ async removeNative(session, nativeSessionId) {
210
+ await this.client.deleteSession(session.cwd, nativeSessionId);
211
+ return true;
212
+ }
213
+ async forkNative(session, boundary, title) {
214
+ if (session.externalSessionId === null)
215
+ throw new Error('SESSION_REWIND_UNAVAILABLE');
216
+ const result = boundary === null
217
+ ? await this.client.createSession(session.cwd, title)
218
+ : await this.client.forkSession(session.cwd, session.externalSessionId, boundary);
219
+ const value = object(result);
220
+ if (typeof value?.id !== 'string')
221
+ throw new Error('SESSION_REWIND_FAILED');
222
+ if (boundary !== null)
223
+ await this.client.updateSession(session.cwd, value.id, title);
224
+ return value.id;
225
+ }
226
+ async executeCompact(session, environment) {
227
+ if (session.externalSessionId === null || session.model === null)
228
+ throw new Error('RUNNER_COMMAND_UNAVAILABLE');
229
+ const client = this.managedClient(environment);
230
+ try {
231
+ return await client.summarize(session.cwd, session.externalSessionId, session.model);
232
+ }
233
+ finally {
234
+ this.releaseManagedClient(client);
235
+ }
236
+ }
237
+ async readContextUsage(session) {
238
+ if (session.externalSessionId === null)
239
+ return undefined;
240
+ const messages = await this.client.messages(session.cwd, session.externalSessionId, 100);
241
+ const assistants = messages.flatMap((value) => {
242
+ const entry = object(value);
243
+ const info = object(entry?.info);
244
+ return info?.role === 'assistant' ? [info] : [];
245
+ });
246
+ const latest = assistants.at(-1);
247
+ const provider = typeof latest?.providerID === 'string' ? latest.providerID : undefined;
248
+ const model = typeof latest?.modelID === 'string' ? latest.modelID : undefined;
249
+ const tokens = object(latest?.tokens);
250
+ const cache = object(tokens?.cache);
251
+ const usedTokens = positiveNumber(tokens?.input) + positiveNumber(cache?.read);
252
+ const modelId = provider !== undefined && model !== undefined ? `${provider}/${model}` : session.model;
253
+ const limits = [...this.contextLimitsByWorkspace].find(([workspacePath]) => isWithinWorkspace(session.cwd, workspacePath))?.[1];
254
+ const maxTokens = modelId === null ? undefined : limits?.get(modelId);
255
+ return maxTokens === undefined || usedTokens <= 0
256
+ ? undefined
257
+ : { model: modelId, usedTokens, maxTokens };
258
+ }
259
+ async stop() {
260
+ for (const execution of this.execution.values())
261
+ execution.abort.abort();
262
+ this.execution.clear();
263
+ this.managedTurns.clear();
264
+ const stopping = [
265
+ this.client.stop(),
266
+ ...[...this.environmentClients.values()].map(({ client }) => client.stop()),
267
+ ...this.stoppingClients
268
+ ];
269
+ this.environmentClients.clear();
270
+ await Promise.allSettled(stopping);
271
+ this.stoppingClients.clear();
272
+ }
273
+ managedClient(environment) {
274
+ const entries = Object.entries(environment).sort(([left], [right]) => left.localeCompare(right));
275
+ if (entries.length === 0)
276
+ return this.client;
277
+ const fingerprint = createHash('sha256').update(JSON.stringify(entries)).digest('hex');
278
+ const existing = this.environmentClients.get(fingerprint);
279
+ if (existing !== undefined) {
280
+ existing.leases += 1;
281
+ return existing.client;
282
+ }
283
+ const client = this.clientFactory();
284
+ client.configureEnvironment(environment);
285
+ this.environmentClients.set(fingerprint, { client, leases: 1 });
286
+ return client;
287
+ }
288
+ releaseManagedClient(client) {
289
+ if (client === this.client)
290
+ return;
291
+ for (const [fingerprint, entry] of this.environmentClients) {
292
+ if (entry.client !== client)
293
+ continue;
294
+ entry.leases -= 1;
295
+ if (entry.leases <= 0) {
296
+ const stopping = entry.client.stop();
297
+ this.stoppingClients.add(stopping);
298
+ void stopping.finally(() => this.stoppingClients.delete(stopping));
299
+ this.environmentClients.delete(fingerprint);
300
+ }
301
+ return;
302
+ }
303
+ }
304
+ }
305
+ function object(value) {
306
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
307
+ ? value
308
+ : undefined;
309
+ }
310
+ function positiveNumber(value) {
311
+ return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : 0;
312
+ }
313
+ function openCodeSession(value) {
314
+ if (typeof value !== 'object' || value === null)
315
+ return undefined;
316
+ const record = value;
317
+ const time = typeof record.time === 'object' && record.time !== null
318
+ ? record.time
319
+ : undefined;
320
+ if (typeof record.id !== 'string' ||
321
+ typeof record.directory !== 'string' ||
322
+ typeof record.title !== 'string')
323
+ return undefined;
324
+ return {
325
+ id: record.id,
326
+ directory: record.directory,
327
+ title: record.title,
328
+ updatedAt: typeof time?.updated === 'number' ? time.updated : 0
329
+ };
330
+ }
@@ -0,0 +1,19 @@
1
+ import { AbstractRunner, type RegisteredRunnerName, type RunnerApprovalDecision, type RunnerCancellationIntent } from './abstract-runner.js';
2
+ import type { NodeCapabilities, RunnerDefaultConfiguration, NodeEnvelope, RunnerName, RunnerProfile } from '@myagentroam/protocol';
3
+ export declare class RunnerRegistry {
4
+ private readonly runners;
5
+ constructor(runners?: readonly AbstractRunner[]);
6
+ register<T extends AbstractRunner>(runner: T): T;
7
+ optional<T extends AbstractRunner>(name: RegisteredRunnerName): T | undefined;
8
+ require<T extends AbstractRunner>(name: RegisteredRunnerName): T;
9
+ advertised(): readonly AbstractRunner[];
10
+ product(name: unknown): AbstractRunner<RunnerName> | undefined;
11
+ profiles(capabilities: NodeCapabilities): readonly RunnerProfile[];
12
+ refreshProfiles(capabilities: NodeCapabilities, workspace?: import('../database.js').NodeWorkspace, environment?: Readonly<Record<string, string>>): Promise<readonly RunnerProfile[]>;
13
+ supportsConfiguration(configuration: RunnerDefaultConfiguration, workspace?: import('../database.js').NodeWorkspace): boolean;
14
+ startManagedRun(envelope: NodeEnvelope): boolean;
15
+ cancelManagedRun(runId: string, intent: RunnerCancellationIntent): boolean;
16
+ decideApproval(input: RunnerApprovalDecision): boolean;
17
+ respondUserInput(runId: string, requestId: string, answers: Record<string, unknown>): boolean;
18
+ stop(): Promise<void>;
19
+ }
@@ -0,0 +1,75 @@
1
+ export class RunnerRegistry {
2
+ runners = new Map();
3
+ constructor(runners = []) {
4
+ for (const runner of runners)
5
+ this.register(runner);
6
+ }
7
+ register(runner) {
8
+ if (this.runners.has(runner.name))
9
+ throw new Error(`RUNNER_ALREADY_REGISTERED:${runner.name}`);
10
+ this.runners.set(runner.name, runner);
11
+ return runner;
12
+ }
13
+ optional(name) {
14
+ return this.runners.get(name);
15
+ }
16
+ require(name) {
17
+ const runner = this.optional(name);
18
+ if (runner === undefined)
19
+ throw new Error(`RUNNER_NOT_REGISTERED:${name}`);
20
+ return runner;
21
+ }
22
+ advertised() {
23
+ return [...this.runners.values()].filter((runner) => runner.advertised);
24
+ }
25
+ product(name) {
26
+ if (typeof name !== 'string')
27
+ return undefined;
28
+ const runner = this.runners.get(name);
29
+ return runner?.advertised === true ? runner : undefined;
30
+ }
31
+ profiles(capabilities) {
32
+ return this.advertised().flatMap((runner) => {
33
+ const profile = runner.profile(capabilities);
34
+ return profile === undefined ? [] : [profile];
35
+ });
36
+ }
37
+ async refreshProfiles(capabilities, workspace, environment = {}) {
38
+ const profiles = await Promise.all(this.advertised().map((runner) => runner.refreshProfile(capabilities, workspace, environment)));
39
+ return profiles.flatMap((profile) => (profile === undefined ? [] : [profile]));
40
+ }
41
+ supportsConfiguration(configuration, workspace) {
42
+ return (this.product(configuration.runner)?.supportsConfiguration(configuration, workspace) === true);
43
+ }
44
+ startManagedRun(envelope) {
45
+ const runnerName = envelope.payload.runner;
46
+ const values = [...this.runners.values()];
47
+ const runner = values.find((candidate) => candidate.interceptsRuns && candidate.acceptsRun(runnerName)) ??
48
+ values.find((candidate) => candidate.acceptsRun(runnerName));
49
+ if (runner === undefined)
50
+ return false;
51
+ runner.startManagedRun(envelope);
52
+ return true;
53
+ }
54
+ cancelManagedRun(runId, intent) {
55
+ for (const runner of this.runners.values())
56
+ if (runner.cancelManagedRun(runId, intent))
57
+ return true;
58
+ return false;
59
+ }
60
+ decideApproval(input) {
61
+ let handled = false;
62
+ for (const runner of this.runners.values())
63
+ handled = runner.decideApproval(input) || handled;
64
+ return handled;
65
+ }
66
+ respondUserInput(runId, requestId, answers) {
67
+ for (const runner of this.runners.values())
68
+ if (runner.respondUserInput(runId, requestId, answers))
69
+ return true;
70
+ return false;
71
+ }
72
+ async stop() {
73
+ await Promise.allSettled([...this.runners.values()].map((runner) => runner.stop()));
74
+ }
75
+ }
@@ -68,4 +68,3 @@ export class CommandStateStore {
68
68
  return this.states.get(sessionId)?.get(commandId);
69
69
  }
70
70
  }
71
- //# sourceMappingURL=runner-command-engine.js.map
@@ -2,10 +2,14 @@ import type { RunnerName, RunnerProfile } from '@myagentroam/protocol';
2
2
  export declare function declaredRunnerProfiles(input: {
3
3
  readonly codexAvailable: boolean;
4
4
  readonly claudeCodeAvailable: boolean;
5
+ readonly environment?: NodeJS.ProcessEnv;
6
+ readonly openCodeAvailable?: boolean;
5
7
  }): readonly RunnerProfile[];
6
8
  export declare function supportsRunnerConfiguration(input: {
7
9
  readonly runner: RunnerName;
8
10
  readonly model: string;
9
11
  readonly effort: string;
10
12
  readonly access: string;
13
+ readonly environment?: NodeJS.ProcessEnv;
11
14
  }): boolean;
15
+ export declare function claudeDefaultModel(environment?: NodeJS.ProcessEnv): string;
@@ -1,6 +1,11 @@
1
1
  // Codex reasoning efforts follow the official Codex model surface. Keep this
2
2
  // shared so an alias such as GPT-5.6 Luna cannot accidentally lose `high`.
3
3
  const CODEX_REASONING_EFFORTS = ['low', 'medium', 'high', 'xhigh'];
4
+ const CLAUDE_MODEL_ENVIRONMENT = {
5
+ opus: 'ANTHROPIC_DEFAULT_OPUS_MODEL',
6
+ sonnet: 'ANTHROPIC_DEFAULT_SONNET_MODEL',
7
+ haiku: 'ANTHROPIC_DEFAULT_HAIKU_MODEL'
8
+ };
4
9
  /**
5
10
  * The Runner APIs used by MAR do not expose a portable model-list endpoint.
6
11
  * Keep the supported, product-visible choices in one Node-owned definition so
@@ -123,21 +128,90 @@ const definitions = {
123
128
  reasonCode: null
124
129
  }
125
130
  ]
131
+ },
132
+ opencode: {
133
+ runner: 'opencode',
134
+ models: [],
135
+ accessOptions: [
136
+ {
137
+ id: 'default',
138
+ label: 'Default',
139
+ description: '按 OpenCode 和当前 Workspace 配置确认权限。'
140
+ },
141
+ {
142
+ id: 'auto',
143
+ label: 'Auto approve',
144
+ description: '自动允许 OpenCode 请求的工具权限。'
145
+ }
146
+ ],
147
+ commands: [
148
+ {
149
+ id: 'compact',
150
+ runner: 'opencode',
151
+ label: '/compact',
152
+ description: '通过 OpenCode 压缩当前会话上下文。',
153
+ inputHint: '/compact',
154
+ source: 'OPENCODE_SERVER',
155
+ interaction: 'IMMEDIATE_ACTION',
156
+ available: true,
157
+ reasonCode: null
158
+ },
159
+ {
160
+ id: 'plan',
161
+ runner: 'opencode',
162
+ label: '/plan',
163
+ stateLabel: 'Plan Mode',
164
+ description: '开启或关闭 OpenCode 原生 Plan Agent。',
165
+ inputHint: '/plan',
166
+ source: 'OPENCODE_SERVER',
167
+ interaction: 'TOGGLE',
168
+ available: true,
169
+ reasonCode: null
170
+ }
171
+ ]
126
172
  }
127
173
  };
128
174
  export function declaredRunnerProfiles(input) {
129
175
  return [
130
176
  withCapabilities(definitions.codex, input.codexAvailable),
131
- withCapabilities(definitions['claude-code'], input.claudeCodeAvailable)
177
+ withCapabilities(claudeProfile(input.environment ?? process.env), input.claudeCodeAvailable),
178
+ withCapabilities(definitions.opencode, input.openCodeAvailable === true)
132
179
  ];
133
180
  }
134
181
  export function supportsRunnerConfiguration(input) {
135
- const profile = definitions[input.runner];
182
+ const profile = input.runner === 'claude-code'
183
+ ? claudeProfile(input.environment ?? process.env)
184
+ : definitions[input.runner];
136
185
  const model = profile.models.find((candidate) => candidate.id === input.model);
137
186
  return (model !== undefined &&
138
187
  model.supportedEfforts.includes(input.effort) &&
139
188
  profile.accessOptions.some((candidate) => candidate.id === input.access));
140
189
  }
190
+ export function claudeDefaultModel(environment = process.env) {
191
+ return configuredClaudeModel('sonnet', environment) ?? 'sonnet';
192
+ }
193
+ function claudeProfile(environment) {
194
+ const profile = definitions['claude-code'];
195
+ const models = profile.models.map((model) => {
196
+ const configured = configuredClaudeModel(model.id, environment);
197
+ return configured === undefined ? model : { ...model, id: configured, label: configured };
198
+ });
199
+ return {
200
+ ...profile,
201
+ models: models.filter((model, index) => models.findIndex((candidate) => candidate.id === model.id) === index)
202
+ };
203
+ }
204
+ function configuredClaudeModel(alias, environment) {
205
+ const environmentName = CLAUDE_MODEL_ENVIRONMENT[alias];
206
+ return (validModelName(environmentName === undefined ? undefined : environment[environmentName]) ??
207
+ (alias === 'sonnet' ? validModelName(environment['ANTHROPIC_MODEL']) : undefined));
208
+ }
209
+ function validModelName(value) {
210
+ const normalized = value?.trim();
211
+ return normalized !== undefined && normalized.length > 0 && normalized.length <= 128
212
+ ? normalized
213
+ : undefined;
214
+ }
141
215
  function withCapabilities(profile, available) {
142
216
  return {
143
217
  ...profile,
@@ -147,4 +221,3 @@ function withCapabilities(profile, available) {
147
221
  : []
148
222
  };
149
223
  }
150
- //# sourceMappingURL=runner-profiles.js.map
@@ -24,8 +24,8 @@ export declare class RunnerUsageReader {
24
24
  private readonly now;
25
25
  private readonly cache;
26
26
  constructor(fetcher?: FetchLike, homePath?: string, now?: () => number);
27
- status(runner: RunnerName, runnerAvailable: boolean): Promise<RunnerUsageStatus>;
28
- read(runner: RunnerName, runnerAvailable: boolean): Promise<RunnerUsage>;
27
+ status(runner: RunnerName): Promise<RunnerUsageStatus>;
28
+ read(runner: RunnerName): Promise<RunnerUsage>;
29
29
  private credentials;
30
30
  private readCodex;
31
31
  private readClaude;
@@ -18,15 +18,13 @@ export class RunnerUsageReader {
18
18
  this.homePath = homePath;
19
19
  this.now = now;
20
20
  }
21
- async status(runner, runnerAvailable) {
22
- if (!runnerAvailable)
23
- return unavailable(runner, 'RUNNER_UNAVAILABLE');
21
+ async status(runner) {
24
22
  return (await this.credentials(runner)) === undefined
25
23
  ? unavailable(runner, USAGE_OAUTH_REQUIRED)
26
24
  : { runner, available: true, reasonCode: null };
27
25
  }
28
- async read(runner, runnerAvailable) {
29
- const status = await this.status(runner, runnerAvailable);
26
+ async read(runner) {
27
+ const status = await this.status(runner);
30
28
  if (!status.available)
31
29
  throw new Error(status.reasonCode ?? 'USAGE_UNAVAILABLE');
32
30
  const cached = this.cache.get(runner);
@@ -190,4 +188,3 @@ function timestamp(value) {
190
188
  const parsed = Date.parse(value);
191
189
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
192
190
  }
193
- //# sourceMappingURL=runner-usage.js.map