@myagentroam/node 0.1.4 → 0.1.7

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 (210) 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 +3 -1
  5. package/dist/claude-agent-sdk.js +9 -2
  6. package/dist/claude-channel.js +0 -1
  7. package/dist/codex-app-server.d.ts +1 -0
  8. package/dist/codex-app-server.js +8 -1
  9. package/dist/config.d.ts +3 -0
  10. package/dist/config.js +18 -2
  11. package/dist/connector/node-connector-options.d.ts +3 -0
  12. package/dist/connector/node-connector-options.js +0 -1
  13. package/dist/connector/node-operation-router.js +0 -1
  14. package/dist/connector.d.ts +3 -0
  15. package/dist/connector.js +109 -7
  16. package/dist/control-outbound-scheduler.js +0 -1
  17. package/dist/database.d.ts +4 -0
  18. package/dist/database.js +14 -9
  19. package/dist/event-buffer.js +0 -1
  20. package/dist/fake-runner.js +0 -1
  21. package/dist/health.js +0 -1
  22. package/dist/main.js +6 -1
  23. package/dist/migrations/v001.js +0 -1
  24. package/dist/migrations/v002.js +0 -1
  25. package/dist/migrations/v003.d.ts +4 -0
  26. package/dist/migrations/v003.js +44 -0
  27. package/dist/native-jsonl-reader.js +0 -1
  28. package/dist/native-session-history.d.ts +19 -1
  29. package/dist/native-session-history.js +65 -3
  30. package/dist/opencode-runtime.d.ts +33 -0
  31. package/dist/opencode-runtime.js +80 -0
  32. package/dist/opencode-server.d.ts +47 -0
  33. package/dist/opencode-server.js +250 -0
  34. package/dist/operational.js +10 -3
  35. package/dist/process-tree.js +0 -1
  36. package/dist/runner/abstract-runner.d.ts +7 -2
  37. package/dist/runner/abstract-runner.js +17 -2
  38. package/dist/runner/claude/managed-run-controller.js +20 -1
  39. package/dist/runner/claude-code-runner.d.ts +3 -1
  40. package/dist/runner/claude-code-runner.js +26 -8
  41. package/dist/runner/codex/conversation-parser.d.ts +1 -3
  42. package/dist/runner/codex/conversation-parser.js +174 -34
  43. package/dist/runner/codex/managed-run-controller.d.ts +2 -1
  44. package/dist/runner/codex/managed-run-controller.js +13 -4
  45. package/dist/runner/codex-runner.d.ts +4 -0
  46. package/dist/runner/codex-runner.js +27 -4
  47. package/dist/runner/fake-test-runner.js +0 -1
  48. package/dist/runner/opencode/conversation-parser.d.ts +27 -0
  49. package/dist/runner/opencode/conversation-parser.js +217 -0
  50. package/dist/runner/opencode/managed-run-controller.d.ts +76 -0
  51. package/dist/runner/opencode/managed-run-controller.js +451 -0
  52. package/dist/runner/opencode-runner.d.ts +60 -0
  53. package/dist/runner/opencode-runner.js +330 -0
  54. package/dist/runner/runner-registry.d.ts +3 -1
  55. package/dist/runner/runner-registry.js +12 -3
  56. package/dist/runner-command-engine.js +0 -1
  57. package/dist/runner-profiles.d.ts +4 -0
  58. package/dist/runner-profiles.js +76 -3
  59. package/dist/runner-usage.js +0 -1
  60. package/dist/runtime-state.js +20 -9
  61. package/dist/service/attachment-domain-state.js +0 -1
  62. package/dist/service/conversation-history-service.js +0 -1
  63. package/dist/service/external-session-resume-service.js +0 -1
  64. package/dist/service/fake-managed-run-service.js +0 -1
  65. package/dist/service/managed-runner-session-service.d.ts +3 -1
  66. package/dist/service/managed-runner-session-service.js +8 -4
  67. package/dist/service/native-session-projection-service.d.ts +2 -1
  68. package/dist/service/native-session-projection-service.js +8 -5
  69. package/dist/service/native-session-watch-service.js +0 -1
  70. package/dist/service/node-connection-lifecycle-service.js +3 -2
  71. package/dist/service/node-control-channel.d.ts +1 -1
  72. package/dist/service/node-control-channel.js +5 -4
  73. package/dist/service/node-control-message-service.js +0 -1
  74. package/dist/service/node-request-service.js +0 -1
  75. package/dist/service/node-run-event-bridge.d.ts +2 -0
  76. package/dist/service/node-run-event-bridge.js +4 -1
  77. package/dist/service/node-terminal-channel.js +16 -10
  78. package/dist/service/node-upgrade-coordinator.d.ts +29 -0
  79. package/dist/service/node-upgrade-coordinator.js +137 -0
  80. package/dist/service/node-workspace-coordinator.js +0 -1
  81. package/dist/service/run-attachment-service.js +0 -1
  82. package/dist/service/run-domain-state.js +0 -1
  83. package/dist/service/run-event-service.js +0 -1
  84. package/dist/service/run-workbench-service.js +0 -1
  85. package/dist/service/runner-channel-message-service.js +0 -1
  86. package/dist/service/runner-interaction-service.js +2 -1
  87. package/dist/service/runner-service.d.ts +5 -4
  88. package/dist/service/runner-service.js +19 -6
  89. package/dist/service/session-catalog-service.d.ts +16 -1
  90. package/dist/service/session-catalog-service.js +82 -3
  91. package/dist/service/session-command-service.js +0 -1
  92. package/dist/service/session-context-service.js +0 -1
  93. package/dist/service/session-domain-state.js +0 -1
  94. package/dist/service/session-identity-service.js +0 -1
  95. package/dist/service/session-lifecycle-service.js +13 -7
  96. package/dist/service/session-message-service.js +0 -1
  97. package/dist/service/session-presentation-service.js +0 -1
  98. package/dist/service/session-query-service.js +0 -1
  99. package/dist/service/terminal-relay-state.js +0 -1
  100. package/dist/service/terminal-service.js +0 -1
  101. package/dist/service/workbench-event-service.js +0 -1
  102. package/dist/service/workbench-manifest-service.d.ts +5 -4
  103. package/dist/service/workbench-manifest-service.js +3 -2
  104. package/dist/service/workspace-change-service.js +2 -2
  105. package/dist/service/workspace-content-search-service.d.ts +14 -0
  106. package/dist/service/workspace-content-search-service.js +477 -0
  107. package/dist/service/workspace-domain-state.js +0 -1
  108. package/dist/service/workspace-file-service.d.ts +3 -0
  109. package/dist/service/workspace-file-service.js +35 -3
  110. package/dist/service/workspace-queue-workbench-service.js +0 -1
  111. package/dist/service/workspace-service.js +23 -10
  112. package/dist/service/workspace-session-service.js +0 -1
  113. package/dist/service/workspace-upload-service.js +0 -1
  114. package/dist/service/workspace-watch-service.js +0 -1
  115. package/dist/service/workspace-workbench-service.d.ts +9 -0
  116. package/dist/service/workspace-workbench-service.js +69 -2
  117. package/dist/service.js +2 -3
  118. package/dist/storage.js +0 -1
  119. package/dist/supervisor.d.ts +1 -0
  120. package/dist/supervisor.js +154 -0
  121. package/dist/terminal.js +0 -1
  122. package/dist/util/node-operation-parsers.d.ts +1 -0
  123. package/dist/util/node-operation-parsers.js +3 -1
  124. package/dist/util/runner-native-session-parsers.js +117 -73
  125. package/dist/util/secret-environment.js +0 -1
  126. package/dist/workspace.d.ts +62 -2
  127. package/dist/workspace.js +539 -57
  128. package/package.json +3 -2
  129. package/dist/capabilities.js.map +0 -1
  130. package/dist/claude-agent-sdk.js.map +0 -1
  131. package/dist/claude-channel.js.map +0 -1
  132. package/dist/codex-app-server.js.map +0 -1
  133. package/dist/config.js.map +0 -1
  134. package/dist/connector/node-connector-options.js.map +0 -1
  135. package/dist/connector/node-operation-router.js.map +0 -1
  136. package/dist/connector.js.map +0 -1
  137. package/dist/control-outbound-scheduler.js.map +0 -1
  138. package/dist/database.js.map +0 -1
  139. package/dist/event-buffer.js.map +0 -1
  140. package/dist/fake-runner.js.map +0 -1
  141. package/dist/health.js.map +0 -1
  142. package/dist/main.js.map +0 -1
  143. package/dist/migrations/v001.js.map +0 -1
  144. package/dist/migrations/v002.js.map +0 -1
  145. package/dist/native-jsonl-reader.js.map +0 -1
  146. package/dist/native-session-history.js.map +0 -1
  147. package/dist/operational.js.map +0 -1
  148. package/dist/process-tree.js.map +0 -1
  149. package/dist/runner/abstract-runner.js.map +0 -1
  150. package/dist/runner/claude/managed-run-controller.js.map +0 -1
  151. package/dist/runner/claude-code-runner.js.map +0 -1
  152. package/dist/runner/codex/conversation-parser.js.map +0 -1
  153. package/dist/runner/codex/managed-run-controller.js.map +0 -1
  154. package/dist/runner/codex-runner.js.map +0 -1
  155. package/dist/runner/fake-test-runner.js.map +0 -1
  156. package/dist/runner/runner-registry.js.map +0 -1
  157. package/dist/runner-command-engine.js.map +0 -1
  158. package/dist/runner-profiles.js.map +0 -1
  159. package/dist/runner-usage.js.map +0 -1
  160. package/dist/runtime-state.js.map +0 -1
  161. package/dist/service/attachment-domain-state.js.map +0 -1
  162. package/dist/service/conversation-history-service.js.map +0 -1
  163. package/dist/service/external-session-resume-service.js.map +0 -1
  164. package/dist/service/fake-managed-run-service.js.map +0 -1
  165. package/dist/service/managed-runner-session-service.js.map +0 -1
  166. package/dist/service/native-session-projection-service.js.map +0 -1
  167. package/dist/service/native-session-watch-service.js.map +0 -1
  168. package/dist/service/node-connection-lifecycle-service.js.map +0 -1
  169. package/dist/service/node-control-channel.js.map +0 -1
  170. package/dist/service/node-control-message-service.js.map +0 -1
  171. package/dist/service/node-request-service.js.map +0 -1
  172. package/dist/service/node-run-event-bridge.js.map +0 -1
  173. package/dist/service/node-terminal-channel.js.map +0 -1
  174. package/dist/service/node-workspace-coordinator.js.map +0 -1
  175. package/dist/service/run-attachment-service.js.map +0 -1
  176. package/dist/service/run-domain-state.js.map +0 -1
  177. package/dist/service/run-event-service.js.map +0 -1
  178. package/dist/service/run-workbench-service.js.map +0 -1
  179. package/dist/service/runner-channel-message-service.js.map +0 -1
  180. package/dist/service/runner-interaction-service.js.map +0 -1
  181. package/dist/service/runner-service.js.map +0 -1
  182. package/dist/service/session-catalog-service.js.map +0 -1
  183. package/dist/service/session-command-service.js.map +0 -1
  184. package/dist/service/session-context-service.js.map +0 -1
  185. package/dist/service/session-domain-state.js.map +0 -1
  186. package/dist/service/session-identity-service.js.map +0 -1
  187. package/dist/service/session-lifecycle-service.js.map +0 -1
  188. package/dist/service/session-message-service.js.map +0 -1
  189. package/dist/service/session-presentation-service.js.map +0 -1
  190. package/dist/service/session-query-service.js.map +0 -1
  191. package/dist/service/terminal-relay-state.js.map +0 -1
  192. package/dist/service/terminal-service.js.map +0 -1
  193. package/dist/service/workbench-event-service.js.map +0 -1
  194. package/dist/service/workbench-manifest-service.js.map +0 -1
  195. package/dist/service/workspace-change-service.js.map +0 -1
  196. package/dist/service/workspace-domain-state.js.map +0 -1
  197. package/dist/service/workspace-file-service.js.map +0 -1
  198. package/dist/service/workspace-queue-workbench-service.js.map +0 -1
  199. package/dist/service/workspace-service.js.map +0 -1
  200. package/dist/service/workspace-session-service.js.map +0 -1
  201. package/dist/service/workspace-upload-service.js.map +0 -1
  202. package/dist/service/workspace-watch-service.js.map +0 -1
  203. package/dist/service/workspace-workbench-service.js.map +0 -1
  204. package/dist/service.js.map +0 -1
  205. package/dist/storage.js.map +0 -1
  206. package/dist/terminal.js.map +0 -1
  207. package/dist/util/node-operation-parsers.js.map +0 -1
  208. package/dist/util/runner-native-session-parsers.js.map +0 -1
  209. package/dist/util/secret-environment.js.map +0 -1
  210. 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
+ }
@@ -9,9 +9,11 @@ export declare class RunnerRegistry {
9
9
  advertised(): readonly AbstractRunner[];
10
10
  product(name: unknown): AbstractRunner<RunnerName> | undefined;
11
11
  profiles(capabilities: NodeCapabilities): readonly RunnerProfile[];
12
- supportsConfiguration(configuration: RunnerDefaultConfiguration): boolean;
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;
13
14
  startManagedRun(envelope: NodeEnvelope): boolean;
14
15
  cancelManagedRun(runId: string, intent: RunnerCancellationIntent): boolean;
15
16
  decideApproval(input: RunnerApprovalDecision): boolean;
17
+ respondUserInput(runId: string, requestId: string, answers: Record<string, unknown>): boolean;
16
18
  stop(): Promise<void>;
17
19
  }
@@ -34,8 +34,12 @@ export class RunnerRegistry {
34
34
  return profile === undefined ? [] : [profile];
35
35
  });
36
36
  }
37
- supportsConfiguration(configuration) {
38
- return this.product(configuration.runner)?.supportsConfiguration(configuration) === true;
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);
39
43
  }
40
44
  startManagedRun(envelope) {
41
45
  const runnerName = envelope.payload.runner;
@@ -59,8 +63,13 @@ export class RunnerRegistry {
59
63
  handled = runner.decideApproval(input) || handled;
60
64
  return handled;
61
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
+ }
62
72
  async stop() {
63
73
  await Promise.allSettled([...this.runners.values()].map((runner) => runner.stop()));
64
74
  }
65
75
  }
66
- //# sourceMappingURL=runner-registry.js.map
@@ -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
@@ -188,4 +188,3 @@ function timestamp(value) {
188
188
  const parsed = Date.parse(value);
189
189
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
190
190
  }
191
- //# sourceMappingURL=runner-usage.js.map
@@ -754,15 +754,18 @@ export class NodeRuntimeState {
754
754
  if (event.eventType === 'text.delta' &&
755
755
  isRecord(event.payload) &&
756
756
  typeof event.payload.text === 'string') {
757
- const existing = turn.items.find((item) => item.id === `${event.runId}:assistant`);
757
+ const lastItem = turn.items.at(-1);
758
+ const existing = lastItem?.kind === 'assistant_message' && activeConversationItem(lastItem.status)
759
+ ? lastItem
760
+ : undefined;
758
761
  const text = `${existing === undefined ? '' : readableText(existing.payload)}${event.payload.text}`;
759
762
  const item = {
760
- id: `${event.runId}:assistant`,
763
+ id: existing?.id ?? `${event.runId}:assistant:${event.sequence}`,
761
764
  sessionId: run.sessionId,
762
765
  turnId: turn.id,
763
766
  runId: event.runId,
764
767
  parentItemId: null,
765
- sourceSequence: event.sequence,
768
+ sourceSequence: existing?.sourceSequence ?? event.sequence,
766
769
  revision: event.sequence,
767
770
  kind: 'assistant_message',
768
771
  status: 'IN_PROGRESS',
@@ -774,12 +777,20 @@ export class NodeRuntimeState {
774
777
  ...turn,
775
778
  status: 'RUNNING',
776
779
  startedAt: turn.startedAt ?? event.createdAt,
777
- items: [...turn.items.filter((value) => value.id !== item.id), item]
780
+ items: existing === undefined
781
+ ? [...turn.items, item]
782
+ : turn.items.map((value) => (value.id === item.id ? item : value))
778
783
  };
779
784
  }
780
785
  const projected = conversationItemFromEvent(event);
781
786
  if (projected !== undefined) {
782
- const existing = turn.items.find((item) => item.id === `${event.runId}:${projected.itemId}`);
787
+ const trailingItem = turn.items.at(-1);
788
+ const orderedItems = trailingItem?.kind === 'assistant_message' && activeConversationItem(trailingItem.status)
789
+ ? turn.items.map((item) => item.id === trailingItem.id
790
+ ? { ...item, status: 'COMPLETED', completedAt: event.createdAt }
791
+ : item)
792
+ : turn.items;
793
+ const existing = orderedItems.find((item) => item.id === `${event.runId}:${projected.itemId}`);
783
794
  const status = projected.status;
784
795
  const item = {
785
796
  id: `${event.runId}:${projected.itemId}`,
@@ -787,7 +798,7 @@ export class NodeRuntimeState {
787
798
  turnId: turn.id,
788
799
  runId: event.runId,
789
800
  parentItemId: null,
790
- sourceSequence: event.sequence,
801
+ sourceSequence: existing?.sourceSequence ?? event.sequence,
791
802
  revision: (existing?.revision ?? -1) + 1,
792
803
  kind: projected.kind,
793
804
  status,
@@ -800,8 +811,8 @@ export class NodeRuntimeState {
800
811
  status: terminal(run.status) ? run.status : 'RUNNING',
801
812
  startedAt: turn.startedAt ?? event.createdAt,
802
813
  items: existing === undefined
803
- ? [...turn.items, item]
804
- : turn.items.map((value) => (value.id === item.id ? item : value))
814
+ ? [...orderedItems, item]
815
+ : orderedItems.map((value) => (value.id === item.id ? item : value))
805
816
  };
806
817
  }
807
818
  if (event.eventType === 'run.started' || event.eventType === 'run.running')
@@ -874,6 +885,7 @@ function projectableConversationKind(value) {
874
885
  value === 'plan' ||
875
886
  value === 'file_change' ||
876
887
  value === 'context_compaction' ||
888
+ value === 'unknown' ||
877
889
  value === 'warning' ||
878
890
  value === 'error');
879
891
  }
@@ -955,4 +967,3 @@ function decodeCursor(cursor, scope) {
955
967
  return 0;
956
968
  }
957
969
  }
958
- //# sourceMappingURL=runtime-state.js.map
@@ -3,4 +3,3 @@ export class AttachmentDomainState {
3
3
  runImages = new Map();
4
4
  nativeHistoryImages = new Map();
5
5
  }
6
- //# sourceMappingURL=attachment-domain-state.js.map
@@ -156,4 +156,3 @@ export class ConversationHistoryService {
156
156
  };
157
157
  }
158
158
  }
159
- //# sourceMappingURL=conversation-history-service.js.map
@@ -96,4 +96,3 @@ export class ExternalSessionResumeService {
96
96
  this.options.state.resumeFailureDetails.set(sessionId, detail);
97
97
  }
98
98
  }
99
- //# sourceMappingURL=external-session-resume-service.js.map
@@ -60,4 +60,3 @@ export class FakeManagedRunService {
60
60
  .finally(() => this.options.state.active.delete(runId));
61
61
  }
62
62
  }
63
- //# sourceMappingURL=fake-managed-run-service.js.map
@@ -1,10 +1,12 @@
1
1
  import type { NodeRuntimeState } from '../runtime-state.js';
2
2
  import type { RunnerRegistry } from '../runner/runner-registry.js';
3
+ import type { NodeAgentSession } from '../database.js';
3
4
  /** Owns managed native-session identity and best-effort native title refresh. */
4
5
  export declare class ManagedRunnerSessionService {
5
6
  private readonly runtime;
6
7
  private readonly runners;
7
- constructor(runtime: NodeRuntimeState, runners: RunnerRegistry);
8
+ private readonly emitSession;
9
+ constructor(runtime: NodeRuntimeState, runners: RunnerRegistry, emitSession: (session: NodeAgentSession) => void);
8
10
  remember(nativeSessionId: string, sessionId: string): void;
9
11
  refreshTitle(sessionId: string, externalSessionId: string, cwd: string): Promise<void>;
10
12
  }
@@ -2,9 +2,11 @@
2
2
  export class ManagedRunnerSessionService {
3
3
  runtime;
4
4
  runners;
5
- constructor(runtime, runners) {
5
+ emitSession;
6
+ constructor(runtime, runners, emitSession) {
6
7
  this.runtime = runtime;
7
8
  this.runners = runners;
9
+ this.emitSession = emitSession;
8
10
  }
9
11
  remember(nativeSessionId, sessionId) {
10
12
  const session = this.runtime.getAgentSession(sessionId);
@@ -20,12 +22,14 @@ export class ManagedRunnerSessionService {
20
22
  const title = await this.runners
21
23
  .require(session.runner)
22
24
  .readManagedRunnerTitle({ ...session, cwd }, externalSessionId);
23
- if (title !== undefined)
24
- this.runtime.setRunnerTitleFromNative(session.id, title);
25
+ if (title === undefined)
26
+ return;
27
+ const updated = this.runtime.setRunnerTitleFromNative(session.id, title);
28
+ if (updated.runnerTitle !== session.runnerTitle)
29
+ this.emitSession(updated);
25
30
  }
26
31
  catch {
27
32
  // Native history is a best-effort source and cannot alter the completed Run result.
28
33
  }
29
34
  }
30
35
  }
31
- //# sourceMappingURL=managed-runner-session-service.js.map