@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,365 @@
1
+ import { CodexAppServerClient } from '../codex-app-server.js';
2
+ import { AbstractRunner } from './abstract-runner.js';
3
+ import { declaredRunnerProfiles } from '../runner-profiles.js';
4
+ import { homedir } from 'node:os';
5
+ import { resolve } from 'node:path';
6
+ import { createHash } from 'node:crypto';
7
+ import { discoverAllNativeSessions } from '../native-session-history.js';
8
+ import { readCodexNativeContextUsage } from '../native-session-history.js';
9
+ import { codexActiveTurnId, codexOfficialConversationPage, codexThreadCwd, codexThreadActivity, deduplicateDirectSessions, externalResumeFailureDetail, extractCodexThreads, isWithinWorkspace, latestNativeActivity } from './codex/conversation-parser.js';
10
+ import { codexSessionControl } from '../codex-app-server.js';
11
+ import { extractId } from '../util/runner-native-session-parsers.js';
12
+ export class CodexRunner extends AbstractRunner {
13
+ client;
14
+ name = 'codex';
15
+ discoveryScope = 'ALL_WORKSPACES';
16
+ execution = new CodexExecutionState();
17
+ fastEnabled = false;
18
+ environmentFingerprint;
19
+ environmentRuns = new Set();
20
+ constructor(client = new CodexAppServerClient()) {
21
+ super();
22
+ this.client = client;
23
+ }
24
+ profile(capabilities) {
25
+ return 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
+ }
31
+ available(capabilities) {
32
+ return capabilities.codex.available;
33
+ }
34
+ executeGlobalCommand(commandId, input) {
35
+ if (commandId !== 'fast' || !/^\/fast(?:\s+(on|off|status))?\s*$/i.test(input))
36
+ throw new Error('RUNNER_COMMAND_INVALID');
37
+ const argument = input.trim().split(/\s+/, 2)[1]?.toLowerCase() ?? 'status';
38
+ if (argument === 'on')
39
+ this.fastEnabled = true;
40
+ else if (argument === 'off')
41
+ this.fastEnabled = false;
42
+ return { command: 'fast', enabled: this.fastEnabled };
43
+ }
44
+ serviceTier() {
45
+ return this.fastEnabled ? 'fast' : undefined;
46
+ }
47
+ usesRunnerTitleForRename() {
48
+ return true;
49
+ }
50
+ async renameNative(session, input, database) {
51
+ if (session.externalSessionId === null)
52
+ return;
53
+ if (input.title !== undefined) {
54
+ await this.start();
55
+ await this.setThreadName(session.externalSessionId, input.title ?? '');
56
+ }
57
+ database.saveNativeSessionMetadata({
58
+ runner: this.name,
59
+ externalSessionId: session.externalSessionId,
60
+ workspaceId: session.workspaceId,
61
+ ...(input.title === undefined ? {} : { runnerTitle: input.title }),
62
+ ...(input.title === undefined
63
+ ? {}
64
+ : { runnerTitlePending: input.title !== null && input.title.trim().length > 0 }),
65
+ ...(input.pinned === undefined ? {} : { pinned: input.pinned })
66
+ });
67
+ }
68
+ projectedRunnerTitle(metadata, input, database) {
69
+ const cached = metadata?.runnerTitle ?? null;
70
+ if (input.titleOrigin !== 'OFFICIAL' || input.title === undefined)
71
+ return cached ?? input.title ?? null;
72
+ if (metadata?.runnerTitlePending === true) {
73
+ if (input.title === cached)
74
+ database?.saveNativeSessionMetadata({
75
+ runner: this.name,
76
+ externalSessionId: input.externalSessionId,
77
+ workspaceId: input.workspaceId,
78
+ runnerTitle: cached,
79
+ runnerTitlePending: false
80
+ });
81
+ return cached ?? input.title;
82
+ }
83
+ return input.title;
84
+ }
85
+ async readOfficialConversation(session, input, managedTurn) {
86
+ if (session.externalSessionId === null)
87
+ return undefined;
88
+ try {
89
+ await this.start();
90
+ return codexOfficialConversationPage(session, await this.readThread(session.externalSessionId), input, managedTurn);
91
+ }
92
+ catch {
93
+ return undefined;
94
+ }
95
+ }
96
+ async readExternalActivity(session) {
97
+ if (session.nativeControl !== 'EXTERNAL' || session.externalSessionId === null)
98
+ return undefined;
99
+ try {
100
+ await this.start();
101
+ return codexThreadActivity(await this.readThread(session.externalSessionId, false));
102
+ }
103
+ catch {
104
+ return 'UNAVAILABLE';
105
+ }
106
+ }
107
+ acceptsTruncatedNativeHistory() {
108
+ return false;
109
+ }
110
+ async readManagedRunnerTitle(session, nativeSessionId) {
111
+ void session;
112
+ await this.start();
113
+ return extractCodexThreads(await this.listThreads()).find((thread) => thread.id === nativeSessionId)?.title;
114
+ }
115
+ nativeImageRoot(session) {
116
+ return session.externalSessionId === null
117
+ ? undefined
118
+ : resolve(homedir(), '.codex', 'generated_images', session.externalSessionId);
119
+ }
120
+ managedRunForNativeTurn(nativeTurnId) {
121
+ return this.execution.managedTurns.get(nativeTurnId)?.runId;
122
+ }
123
+ profileForValidation() {
124
+ return this.profile({
125
+ platform: 'linux',
126
+ architecture: 'validation',
127
+ nodeAppVersion: 'validation',
128
+ nodeVersion: 'validation',
129
+ codex: { available: true },
130
+ claudeCode: { available: false },
131
+ openCode: { available: false }
132
+ });
133
+ }
134
+ defaultConfiguration() {
135
+ return { runner: this.name, model: 'gpt-5.6-sol', effort: 'medium', access: 'on-request' };
136
+ }
137
+ async resumeExternal(external, context, force = false) {
138
+ if (external.runner !== this.name ||
139
+ external.nativeControl !== 'EXTERNAL' ||
140
+ external.externalSessionId === null ||
141
+ !context.available)
142
+ return { failureCode: 'RUNNER_UNAVAILABLE' };
143
+ try {
144
+ const detail = await this.readThread(external.externalSessionId, force);
145
+ const activity = codexThreadActivity(detail);
146
+ if (activity === 'UNAVAILABLE')
147
+ return { activity, failureCode: 'RUNNER_UNAVAILABLE' };
148
+ if (activity === 'EXTERNAL_ACTIVE' && !force)
149
+ return { activity, failureCode: 'EXTERNAL_SESSION_ACTIVE' };
150
+ if (activity === 'EXTERNAL_ACTIVE') {
151
+ const turnId = codexActiveTurnId(detail);
152
+ if (turnId === undefined)
153
+ return { activity, failureCode: 'RUNNER_TURN_NOT_INTERRUPTIBLE' };
154
+ await this.interruptTurn(external.externalSessionId, turnId);
155
+ }
156
+ await this.resumeThread(external.externalSessionId, external.cwd);
157
+ const projection = context.projectionFor(external);
158
+ if (projection !== undefined) {
159
+ const promoted = context.promote(projection.id);
160
+ if (promoted !== undefined)
161
+ return { activity: 'IDLE', session: context.ensureConfiguration(promoted) };
162
+ }
163
+ return { activity: 'IDLE', session: context.create(external) };
164
+ }
165
+ catch (error) {
166
+ return {
167
+ activity: 'UNAVAILABLE',
168
+ failureCode: 'RUNNER_RESUME_FAILED',
169
+ failureDetail: externalResumeFailureDetail(error)
170
+ };
171
+ }
172
+ }
173
+ async discoverSessions(context, workspace) {
174
+ let threads = [];
175
+ try {
176
+ await this.start();
177
+ threads = extractCodexThreads(await this.listThreads());
178
+ }
179
+ catch {
180
+ // Native transcripts remain a valid read-only discovery source.
181
+ }
182
+ const sessions = [];
183
+ for (const thread of threads) {
184
+ if (thread.source !== 'cli' && thread.source !== 'vscode')
185
+ continue;
186
+ let cwd;
187
+ try {
188
+ cwd = codexThreadCwd(thread.cwd);
189
+ }
190
+ catch {
191
+ continue;
192
+ }
193
+ const target = workspace ?? context.workspaces.find((candidate) => isWithinWorkspace(cwd, candidate.path));
194
+ if (target === undefined || !isWithinWorkspace(cwd, target.path))
195
+ continue;
196
+ sessions.push(context.createProjection({
197
+ workspace: target,
198
+ runner: this.name,
199
+ externalSessionId: thread.id,
200
+ cwd,
201
+ ...(thread.title === undefined ? {} : { title: thread.title }),
202
+ titleOrigin: 'OFFICIAL'
203
+ }));
204
+ }
205
+ for (const entry of await discoverAllNativeSessions(this.name)) {
206
+ const target = workspace ??
207
+ context.workspaces.find((candidate) => isWithinWorkspace(entry.cwd, candidate.path));
208
+ if (target === undefined || !isWithinWorkspace(entry.cwd, target.path))
209
+ continue;
210
+ const native = context.createProjection({
211
+ workspace: target,
212
+ runner: this.name,
213
+ externalSessionId: entry.externalSessionId,
214
+ cwd: entry.cwd,
215
+ ...(entry.title === undefined ? {} : { title: entry.title }),
216
+ titleOrigin: 'NATIVE',
217
+ lastActivityAt: latestNativeActivity(entry)
218
+ });
219
+ const index = sessions.findIndex((candidate) => candidate.id === native.id);
220
+ if (index < 0)
221
+ sessions.push(native);
222
+ else {
223
+ const official = sessions[index];
224
+ sessions[index] =
225
+ official.runnerTitle === null
226
+ ? native
227
+ : {
228
+ ...native,
229
+ runnerTitle: official.runnerTitle,
230
+ titleSource: official.titleSource
231
+ };
232
+ }
233
+ }
234
+ return deduplicateDirectSessions(sessions);
235
+ }
236
+ readContextUsage(session) {
237
+ return session.externalSessionId === null
238
+ ? Promise.resolve(undefined)
239
+ : readCodexNativeContextUsage(session.cwd, session.externalSessionId);
240
+ }
241
+ presentSession(session, capabilities, context) {
242
+ const activeTurn = [...this.execution.runs.values()].some((run) => run.sessionId === session.id && run.turnId !== undefined);
243
+ const control = codexSessionControl({
244
+ available: capabilities.codex.available,
245
+ nativeThreadId: session.externalSessionId,
246
+ nativeControl: session.nativeControl,
247
+ activeTurn
248
+ });
249
+ const activity = !capabilities.codex.available
250
+ ? 'UNAVAILABLE'
251
+ : context.managedActive
252
+ ? 'MANAGED_ACTIVE'
253
+ : session.nativeControl === 'EXTERNAL'
254
+ ? (context.externalActivity ?? 'IDLE')
255
+ : 'IDLE';
256
+ return {
257
+ control,
258
+ activity,
259
+ commandStates: context.commandStates,
260
+ runtime: {
261
+ level: control.level,
262
+ capabilities: [
263
+ ...control.capabilities,
264
+ ...(control.level === 'MANAGED' ? ['turn.queue', 'run.cancel', 'approval.respond'] : []),
265
+ 'session.rename',
266
+ 'session.remove',
267
+ 'session.pin',
268
+ ...(session.externalSessionId === null ? [] : ['session.rewind'])
269
+ ],
270
+ reasonCode: control.level === 'UNAVAILABLE' ? 'RUNNER_UNAVAILABLE' : null
271
+ }
272
+ };
273
+ }
274
+ onNotification(listener) {
275
+ return this.client.onNotification(listener);
276
+ }
277
+ start() {
278
+ return this.client.start();
279
+ }
280
+ prepareEnvironment(environment, runId) {
281
+ if (Object.keys(environment).length === 0 && this.environmentFingerprint === undefined) {
282
+ this.environmentRuns.add(runId);
283
+ return;
284
+ }
285
+ const fingerprint = createHash('sha256')
286
+ .update(JSON.stringify(Object.entries(environment).sort(([left], [right]) => left.localeCompare(right))))
287
+ .digest('base64url');
288
+ if (fingerprint === this.environmentFingerprint) {
289
+ this.environmentRuns.add(runId);
290
+ return;
291
+ }
292
+ if (this.environmentRuns.size > 0 || this.execution.runs.size > 0)
293
+ throw new Error('CODEX_SECRET_ENVIRONMENT_CONFLICT');
294
+ this.client.stop();
295
+ this.client.configureEnvironment(environment);
296
+ this.environmentFingerprint = fingerprint;
297
+ this.environmentRuns.add(runId);
298
+ }
299
+ releaseEnvironment(runId) {
300
+ this.environmentRuns.delete(runId);
301
+ if (this.environmentRuns.size !== 0 || this.environmentFingerprint === undefined)
302
+ return;
303
+ this.client.stop();
304
+ this.client.clearEnvironment();
305
+ this.environmentFingerprint = undefined;
306
+ }
307
+ stop() {
308
+ this.client.stop();
309
+ if (this.environmentFingerprint !== undefined)
310
+ this.client.clearEnvironment();
311
+ this.environmentFingerprint = undefined;
312
+ this.environmentRuns.clear();
313
+ }
314
+ request(method, params) {
315
+ return this.client.request(method, params);
316
+ }
317
+ respond(requestId, result) {
318
+ this.client.respond(requestId, result);
319
+ }
320
+ listThreads() {
321
+ return this.client.listThreads();
322
+ }
323
+ readThread(threadId, includeTurns) {
324
+ return this.client.readThread(threadId, includeTurns);
325
+ }
326
+ startThread(cwd, configuration) {
327
+ return this.client.startThread(cwd, configuration);
328
+ }
329
+ forkThread(threadId, cwd, lastTurnId, configuration) {
330
+ return this.client.forkThread(threadId, cwd, lastTurnId, configuration);
331
+ }
332
+ async forkNative(session, boundary, title) {
333
+ if (session.externalSessionId === null)
334
+ throw new Error('SESSION_REWIND_UNAVAILABLE');
335
+ await this.start();
336
+ const configuration = { model: session.model, effort: session.effort, access: session.access };
337
+ const result = boundary === null
338
+ ? await this.startThread(session.cwd, configuration)
339
+ : await this.forkThread(session.externalSessionId, session.cwd, boundary, configuration);
340
+ const threadId = extractId(result, 'thread');
341
+ await this.setThreadName(threadId, title);
342
+ return threadId;
343
+ }
344
+ resumeThread(threadId, cwd, configuration) {
345
+ return this.client.resumeThread(threadId, cwd, configuration);
346
+ }
347
+ startTurn(threadId, text, cwd, configuration, attachments) {
348
+ return this.client.startTurn(threadId, text, cwd, configuration, attachments);
349
+ }
350
+ interruptTurn(threadId, turnId) {
351
+ return this.client.interruptTurn(threadId, turnId);
352
+ }
353
+ setThreadName(threadId, name) {
354
+ return this.client.setThreadName(threadId, name);
355
+ }
356
+ listCollaborationModes() {
357
+ return this.client.listCollaborationModes();
358
+ }
359
+ }
360
+ export class CodexExecutionState {
361
+ runs = new Map();
362
+ managedTurns = new Map();
363
+ approvals = new Map();
364
+ userInputs = new Map();
365
+ }
@@ -0,0 +1,26 @@
1
+ import { FakeRunner, type FakeRunnerCallbacks, type FakeRunnerStep } from '../fake-runner.js';
2
+ import { AbstractRunner } from './abstract-runner.js';
3
+ import type { RunnerDefaultConfiguration, RunnerProfile } from '@myagentroam/protocol';
4
+ import type { NodeAgentSession } from '../database.js';
5
+ import type { ExternalResumeResult } from './abstract-runner.js';
6
+ /** Registered only by tests; never contributes a production Runner Profile. */
7
+ export declare class FakeTestRunner extends AbstractRunner<'fake'> {
8
+ private readonly runner;
9
+ readonly name: "fake";
10
+ readonly discoveryScope: "WORKSPACE_REQUIRED";
11
+ readonly advertised = false;
12
+ readonly interceptsRuns = true;
13
+ constructor(runner: FakeRunner);
14
+ stop(): void;
15
+ profile(): undefined;
16
+ available(): boolean;
17
+ protected profileForValidation(): RunnerProfile;
18
+ defaultConfiguration(): RunnerDefaultConfiguration;
19
+ resumeExternal(): Promise<ExternalResumeResult>;
20
+ discoverSessions(): Promise<readonly NodeAgentSession[]>;
21
+ readContextUsage(): Promise<undefined>;
22
+ start(runId: string, script: readonly FakeRunnerStep[], callbacks: FakeRunnerCallbacks): Promise<void>;
23
+ decide(runId: string, approvalId: string, decision: 'APPROVED' | 'REJECTED'): boolean;
24
+ interrupt(runId: string): boolean;
25
+ cancel(runId: string): boolean;
26
+ }
@@ -0,0 +1,47 @@
1
+ import { AbstractRunner } from './abstract-runner.js';
2
+ /** Registered only by tests; never contributes a production Runner Profile. */
3
+ export class FakeTestRunner extends AbstractRunner {
4
+ runner;
5
+ name = 'fake';
6
+ discoveryScope = 'WORKSPACE_REQUIRED';
7
+ advertised = false;
8
+ interceptsRuns = true;
9
+ constructor(runner) {
10
+ super();
11
+ this.runner = runner;
12
+ }
13
+ stop() { }
14
+ profile() {
15
+ return undefined;
16
+ }
17
+ available() {
18
+ return false;
19
+ }
20
+ profileForValidation() {
21
+ throw new Error('FAKE_RUNNER_NOT_ADVERTISED');
22
+ }
23
+ defaultConfiguration() {
24
+ throw new Error('FAKE_RUNNER_NOT_ADVERTISED');
25
+ }
26
+ resumeExternal() {
27
+ return Promise.resolve({ failureCode: 'RUNNER_CAPABILITY_UNSUPPORTED' });
28
+ }
29
+ discoverSessions() {
30
+ return Promise.resolve([]);
31
+ }
32
+ readContextUsage() {
33
+ return Promise.resolve(undefined);
34
+ }
35
+ start(runId, script, callbacks) {
36
+ return this.runner.start(runId, script, callbacks);
37
+ }
38
+ decide(runId, approvalId, decision) {
39
+ return this.runner.decide(runId, approvalId, decision);
40
+ }
41
+ interrupt(runId) {
42
+ return this.runner.interrupt(runId);
43
+ }
44
+ cancel(runId) {
45
+ return this.runner.cancel(runId);
46
+ }
47
+ }
@@ -0,0 +1,27 @@
1
+ import type { NodeAgentSession, NodeConversationTurn } from '../../database.js';
2
+ export declare function openCodeConversationPage(session: NodeAgentSession, messages: readonly unknown[], input: {
3
+ readonly cursor?: string;
4
+ readonly limit?: number;
5
+ }, managedTurn: (nativeTurnId: string) => NodeConversationTurn | undefined): {
6
+ readonly turns: readonly NodeConversationTurn[];
7
+ readonly nextCursor: string | null;
8
+ } | undefined;
9
+ export declare function openCodeQuestions(value: unknown): {
10
+ id: string;
11
+ prompt: string;
12
+ input: string;
13
+ options: {
14
+ value: string;
15
+ label: string;
16
+ description: string | null;
17
+ }[];
18
+ allowOther: boolean;
19
+ multiSelect: boolean;
20
+ }[];
21
+ export declare function openCodePatchChanges(value: unknown): {
22
+ path: string;
23
+ change: string;
24
+ additions: null;
25
+ deletions: null;
26
+ diffAvailable: boolean;
27
+ }[];
@@ -0,0 +1,217 @@
1
+ import { basename } from 'node:path';
2
+ import { boundedConversationItemId, compactRunnerText, compactRunnerValue } from '../codex/conversation-parser.js';
3
+ export function openCodeConversationPage(session, messages, input, managedTurn) {
4
+ const records = messages.flatMap((value) => {
5
+ const entry = record(value);
6
+ const info = record(entry?.info);
7
+ return info === undefined || typeof info.id !== 'string'
8
+ ? []
9
+ : [{ info, parts: array(entry?.parts) }];
10
+ });
11
+ const parsedTurns = records
12
+ .filter(({ info }) => info.role === 'user')
13
+ .flatMap(({ info, parts }, sequence) => {
14
+ const assistants = records.filter((candidate) => candidate.info.role === 'assistant' && candidate.info.parentID === info.id);
15
+ const existing = managedTurn(info.id);
16
+ const turn = openCodeTurn(session, info, parts, assistants, sequence, existing);
17
+ const after = assistants.at(-1)?.info.id ?? info.id;
18
+ return turn === undefined || typeof after !== 'string' ? [] : [{ after, turn }];
19
+ });
20
+ const turns = parsedTurns.map(({ after, turn }, index) => ({
21
+ ...turn,
22
+ rewind: { before: parsedTurns[index - 1]?.after ?? null, after }
23
+ }));
24
+ if (turns.length === 0)
25
+ return undefined;
26
+ const limit = Math.min(Math.max(input.limit ?? 30, 1), 500);
27
+ const end = pageEnd(input.cursor, session.id, turns.length);
28
+ const start = Math.max(0, end - limit);
29
+ return {
30
+ turns: turns.slice(start, end),
31
+ nextCursor: start === 0
32
+ ? null
33
+ : Buffer.from(JSON.stringify({ sessionId: session.id, end: start })).toString('base64url')
34
+ };
35
+ }
36
+ function openCodeTurn(session, user, userParts, assistants, sequence, managed) {
37
+ const nativeTurnId = user.id;
38
+ const startedAt = number(record(user.time)?.created);
39
+ const items = [];
40
+ const userText = userParts
41
+ .flatMap((part) => {
42
+ const value = record(part);
43
+ return value?.type === 'text' && typeof value.text === 'string' ? [value.text] : [];
44
+ })
45
+ .join('\n');
46
+ const localUser = managed?.items.find((item) => item.kind === 'user_message');
47
+ if (localUser !== undefined)
48
+ items.push({ ...localUser, turnId: managed.id });
49
+ else if (userText.length > 0)
50
+ items.push(item(session, nativeTurnId, `user-${nativeTurnId}`, 'user_message', 'COMPLETED', {
51
+ text: compactRunnerText(userText, 20_000),
52
+ attachments: []
53
+ }, startedAt, startedAt, sequence));
54
+ for (const assistant of assistants) {
55
+ const completedAt = number(record(assistant.info.time)?.completed);
56
+ for (const part of assistant.parts) {
57
+ const projected = partItem(session, nativeTurnId, part, assistant.info.mode === 'plan', startedAt, completedAt, sequence);
58
+ if (projected !== undefined)
59
+ items.push(projected);
60
+ }
61
+ }
62
+ if (items.length === 0)
63
+ return undefined;
64
+ const completedAt = Math.max(startedAt ?? 0, ...assistants.map(({ info }) => number(record(info.time)?.completed) ?? 0)) || startedAt;
65
+ const id = managed?.id ?? `${session.id}:official:${nativeTurnId}`;
66
+ const runId = managed?.runId ?? null;
67
+ return {
68
+ id,
69
+ sessionId: session.id,
70
+ runId,
71
+ userItemId: items.find((value) => value.kind === 'user_message')?.id ?? null,
72
+ status: assistants.some(({ info }) => info.error !== undefined) ? 'FAILED' : 'SUCCEEDED',
73
+ model: assistants.find(({ info }) => typeof info.modelID === 'string')?.info.modelID ?? null,
74
+ effort: managed?.effort ?? null,
75
+ access: managed?.access ?? null,
76
+ items: items.map((value) => ({ ...value, turnId: id, runId })),
77
+ startedAt,
78
+ completedAt
79
+ };
80
+ }
81
+ function partItem(session, turnId, raw, planMode, startedAt, completedAt, sequence) {
82
+ const part = record(raw);
83
+ if (typeof part?.id !== 'string' || typeof part.type !== 'string')
84
+ return undefined;
85
+ if ((part.type === 'text' || part.type === 'reasoning') && typeof part.text === 'string')
86
+ return item(session, turnId, part.id, part.type === 'text' ? (planMode ? 'plan' : 'assistant_message') : 'reasoning_summary', completedAt === null ? 'IN_PROGRESS' : 'COMPLETED', part.type === 'text'
87
+ ? planMode
88
+ ? { explanation: compactRunnerText(part.text, 20_000), steps: [] }
89
+ : { text: compactRunnerText(part.text, 20_000), format: 'markdown', model: null }
90
+ : { sections: [{ index: 0, text: compactRunnerText(part.text, 10_000) }] }, startedAt, completedAt, sequence);
91
+ if (part.type === 'tool' && typeof part.tool === 'string') {
92
+ const state = record(part.state);
93
+ if (part.tool === 'question' && typeof part.callID === 'string') {
94
+ const questions = openCodeQuestions(record(state?.input)?.questions);
95
+ if (questions.length > 0)
96
+ return item(session, turnId, part.id, 'user_input_request', 'COMPLETED', { requestId: `native:${part.callID}`, questions }, startedAt, completedAt, sequence);
97
+ }
98
+ const status = state?.status === 'completed'
99
+ ? 'COMPLETED'
100
+ : state?.status === 'error'
101
+ ? 'FAILED'
102
+ : 'IN_PROGRESS';
103
+ return item(session, turnId, part.id, 'tool_call', status, {
104
+ namespace: 'opencode',
105
+ toolName: compactRunnerText(part.tool, 128),
106
+ title: compactRunnerText(typeof state?.title === 'string' ? state.title : part.tool, 500),
107
+ inputSummary: compactRunnerValue(state?.input, 10_000).text || null,
108
+ outputSummary: typeof state?.output === 'string' ? compactRunnerText(state.output, 10_000) : null,
109
+ progressLabel: null,
110
+ errorCode: status === 'FAILED' ? 'OPENCODE_TOOL_FAILED' : null,
111
+ truncated: false
112
+ }, startedAt, completedAt, sequence);
113
+ }
114
+ if (part.type === 'compaction')
115
+ return item(session, turnId, part.id, 'context_compaction', 'COMPLETED', {
116
+ phase: 'COMPLETED',
117
+ summary: '上下文已整理'
118
+ }, startedAt, completedAt, sequence);
119
+ if (part.type === 'patch')
120
+ return item(session, turnId, part.id, 'file_change', 'COMPLETED', { changes: openCodePatchChanges(part.files) }, startedAt, completedAt, sequence);
121
+ if (part.type === 'step-start' || part.type === 'step-finish')
122
+ return undefined;
123
+ if (part.type === 'text' || part.type === 'reasoning')
124
+ return undefined;
125
+ return item(session, turnId, part.id, 'unknown', completedAt === null ? 'IN_PROGRESS' : 'COMPLETED', {
126
+ sourceEventType: `opencode:${part.type}`,
127
+ label: `OpenCode ${compactRunnerText(part.type, 128)}`
128
+ }, startedAt, completedAt, sequence);
129
+ }
130
+ export function openCodeQuestions(value) {
131
+ if (!Array.isArray(value) || value.length === 0 || value.length > 4)
132
+ return [];
133
+ return value.flatMap((value, index) => {
134
+ const question = record(value);
135
+ if (typeof question?.question !== 'string')
136
+ return [];
137
+ const options = (Array.isArray(question.options) ? question.options : []).flatMap((value) => {
138
+ const option = record(value);
139
+ const label = typeof option?.label === 'string' ? compactRunnerText(option.label, 500) : '';
140
+ return label.length === 0
141
+ ? []
142
+ : [
143
+ {
144
+ value: label,
145
+ label,
146
+ description: typeof option?.description === 'string'
147
+ ? compactRunnerText(option.description, 1_000)
148
+ : null
149
+ }
150
+ ];
151
+ });
152
+ const multiSelect = question.multiple === true;
153
+ return [
154
+ {
155
+ id: `question-${index + 1}`,
156
+ prompt: compactRunnerText(question.question, 5_000),
157
+ input: options.length === 0 ? 'SHORT_TEXT' : multiSelect ? 'MULTI_SELECT' : 'SINGLE_SELECT',
158
+ options,
159
+ allowOther: true,
160
+ multiSelect
161
+ }
162
+ ];
163
+ });
164
+ }
165
+ export function openCodePatchChanges(value) {
166
+ return (Array.isArray(value) ? value : []).flatMap((path) => typeof path === 'string' && path.length > 0
167
+ ? [
168
+ {
169
+ path: basename(path),
170
+ change: 'MODIFIED',
171
+ additions: null,
172
+ deletions: null,
173
+ diffAvailable: false
174
+ }
175
+ ]
176
+ : []);
177
+ }
178
+ function item(session, turnId, nativeId, kind, status, payload, startedAt, completedAt, sequence) {
179
+ return {
180
+ id: boundedConversationItemId(`${session.id}:official:${turnId}`, nativeId),
181
+ sessionId: session.id,
182
+ turnId: `${session.id}:official:${turnId}`,
183
+ runId: null,
184
+ parentItemId: null,
185
+ sourceSequence: sequence,
186
+ revision: 1,
187
+ kind,
188
+ status,
189
+ payload,
190
+ startedAt,
191
+ completedAt
192
+ };
193
+ }
194
+ function pageEnd(cursor, sessionId, fallback) {
195
+ if (cursor === undefined)
196
+ return fallback;
197
+ try {
198
+ const parsed = record(JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8')));
199
+ return parsed?.sessionId === sessionId && Number.isInteger(parsed.end)
200
+ ? Math.min(Math.max(Number(parsed.end), 0), fallback)
201
+ : fallback;
202
+ }
203
+ catch {
204
+ return fallback;
205
+ }
206
+ }
207
+ function record(value) {
208
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
209
+ ? value
210
+ : undefined;
211
+ }
212
+ function array(value) {
213
+ return Array.isArray(value) ? value : [];
214
+ }
215
+ function number(value) {
216
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
217
+ }