@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,76 @@
1
+ import type { NodeEnvelope, RunnerCommandDescriptor, RunnerCommandState } from '@myagentroam/protocol';
2
+ import type { NodeAgentSession } from '../../database.js';
3
+ import type { FakeRunnerStatus } from '../../fake-runner.js';
4
+ import type { RunnerImageAttachment } from '../../service/run-attachment-service.js';
5
+ import { OpenCodeRunner } from '../opencode-runner.js';
6
+ interface OpenCodeManagedRunHost {
7
+ intercepted(): boolean;
8
+ readonly active: Set<string>;
9
+ parseAttachments(value: unknown): readonly RunnerImageAttachment[];
10
+ emit(runId: string, eventType: string, payload: unknown, status?: FakeRunnerStatus): void;
11
+ emitItem(runId: string, item: OpenCodePublicItem): void;
12
+ adopt(input: {
13
+ runId: string;
14
+ sessionId: string;
15
+ runner: 'opencode';
16
+ cwd: string;
17
+ externalSessionId?: string;
18
+ }): void;
19
+ promote(sessionId: string, nativeSessionId: string): void;
20
+ rememberNative(nativeSessionId: string, sessionId: string): void;
21
+ send(type: string, payload: unknown): void;
22
+ captureSnapshot(runId: string, cwd: string): Promise<void>;
23
+ createApproval(input: {
24
+ readonly runId: string;
25
+ readonly approvalId: string;
26
+ readonly payload: unknown;
27
+ readonly expiresAt: number;
28
+ }): void;
29
+ publishApproval(input: {
30
+ readonly runId: string;
31
+ readonly approvalId: string;
32
+ readonly payload: unknown;
33
+ readonly expiresAt: number;
34
+ }): void;
35
+ expireApproval(approvalId: string): void;
36
+ commandState(sessionId: string, commandId: string): RunnerCommandState | undefined;
37
+ setCommandState(sessionId: string, state: RunnerCommandState): void;
38
+ clearCommandState(sessionId: string, commandId: string): void;
39
+ commandStates(sessionId: string): readonly RunnerCommandState[];
40
+ }
41
+ type OpenCodePublicItem = {
42
+ readonly itemId: string;
43
+ readonly kind: 'assistant_message' | 'plan' | 'reasoning_summary' | 'tool_call' | 'user_input_request' | 'context_compaction' | 'file_change' | 'unknown';
44
+ readonly status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED';
45
+ readonly payload: Record<string, unknown>;
46
+ readonly merge?: boolean;
47
+ };
48
+ export declare class OpenCodeManagedRunController {
49
+ private readonly runner;
50
+ private readonly host;
51
+ private readonly interactionTimeoutMs;
52
+ private readonly permissions;
53
+ private readonly questions;
54
+ private readonly startingCwds;
55
+ private readonly promptAdmitted;
56
+ private readonly pendingIdle;
57
+ private readonly startingClients;
58
+ constructor(runner: OpenCodeRunner, host: OpenCodeManagedRunHost, interactionTimeoutMs?: number);
59
+ executeCommand(session: NodeAgentSession, command: RunnerCommandDescriptor, context: {
60
+ readonly secretEnvironment: Readonly<Record<string, string>>;
61
+ }): Promise<unknown>;
62
+ start(envelope: NodeEnvelope): void;
63
+ cancel(runId: string, intent: 'CANCEL' | 'INTERRUPT'): boolean;
64
+ decideApproval(input: {
65
+ readonly runId: string;
66
+ readonly approvalId: string;
67
+ readonly decision: 'APPROVED' | 'REJECTED';
68
+ }): boolean;
69
+ respondUserInput(runId: string, requestId: string, answers: Record<string, unknown>): boolean;
70
+ private run;
71
+ private event;
72
+ private requestPermission;
73
+ private requestQuestion;
74
+ private finish;
75
+ }
76
+ export {};
@@ -0,0 +1,451 @@
1
+ import { parseSecretEnvironment } from '../../util/secret-environment.js';
2
+ import { boundedConversationItemId, compactRunnerText, compactRunnerValue } from '../codex/conversation-parser.js';
3
+ import { openCodePatchChanges, openCodeQuestions } from './conversation-parser.js';
4
+ export class OpenCodeManagedRunController {
5
+ runner;
6
+ host;
7
+ interactionTimeoutMs;
8
+ permissions = new Map();
9
+ questions = new Map();
10
+ startingCwds = new Map();
11
+ promptAdmitted = new Set();
12
+ pendingIdle = new Set();
13
+ startingClients = new Map();
14
+ constructor(runner, host, interactionTimeoutMs = 10 * 60_000) {
15
+ this.runner = runner;
16
+ this.host = host;
17
+ this.interactionTimeoutMs = interactionTimeoutMs;
18
+ }
19
+ async executeCommand(session, command, context) {
20
+ if (command.id === 'compact')
21
+ return this.runner.executeCompact(session, context.secretEnvironment);
22
+ if (command.id !== 'plan')
23
+ throw new Error('RUNNER_COMMAND_UNAVAILABLE');
24
+ const current = this.host.commandState(session.id, 'plan');
25
+ if (current?.active)
26
+ this.host.clearCommandState(session.id, 'plan');
27
+ else
28
+ this.host.setCommandState(session.id, {
29
+ commandId: 'plan',
30
+ active: true,
31
+ label: 'Plan Mode',
32
+ detail: '后续消息将由 OpenCode 原生 Plan Agent 只读分析并制定计划。',
33
+ closable: true,
34
+ closeInput: '/plan',
35
+ continuation: { label: 'Implement', prompt: '请根据上述计划开始实施。' }
36
+ });
37
+ return { command: command.id, states: this.host.commandStates(session.id) };
38
+ }
39
+ start(envelope) {
40
+ const payload = envelope.payload;
41
+ if (payload.runner !== 'opencode' ||
42
+ typeof payload.runId !== 'string' ||
43
+ typeof payload.sessionId !== 'string' ||
44
+ typeof payload.input !== 'string' ||
45
+ typeof payload.cwd !== 'string' ||
46
+ typeof payload.model !== 'string' ||
47
+ this.host.intercepted() ||
48
+ this.host.active.has(payload.runId))
49
+ return;
50
+ let environment;
51
+ let attachments;
52
+ try {
53
+ environment = parseSecretEnvironment(payload.secretEnvironment);
54
+ attachments = this.host.parseAttachments(payload.attachments);
55
+ }
56
+ catch (error) {
57
+ this.host.emit(payload.runId, 'run.rejected', { code: error instanceof Error ? error.message : 'RUNNER_INPUT_INVALID' }, 'FAILED');
58
+ return;
59
+ }
60
+ void this.run({
61
+ runId: payload.runId,
62
+ sessionId: payload.sessionId,
63
+ input: payload.input,
64
+ cwd: payload.cwd,
65
+ model: payload.model,
66
+ effort: typeof payload.effort === 'string' ? payload.effort : '',
67
+ ...(typeof payload.externalSessionId === 'string'
68
+ ? { externalSessionId: payload.externalSessionId }
69
+ : {}),
70
+ environment,
71
+ attachments,
72
+ access: typeof payload.access === 'string' ? payload.access : 'default',
73
+ agent: payload.collaborationMode === 'plan' ? 'plan' : 'build'
74
+ });
75
+ }
76
+ cancel(runId, intent) {
77
+ const execution = this.runner.execution.get(runId);
78
+ if (execution === undefined)
79
+ return false;
80
+ void execution.client.abort(execution.cwd, execution.nativeSessionId).finally(() => {
81
+ this.finish(runId, intent === 'INTERRUPT' ? 'INTERRUPTED' : 'CANCELLED', 'run.completed', {
82
+ status: intent === 'INTERRUPT' ? 'INTERRUPTED' : 'CANCELLED'
83
+ });
84
+ });
85
+ return true;
86
+ }
87
+ decideApproval(input) {
88
+ const pending = this.permissions.get(input.approvalId);
89
+ if (pending === undefined || pending.runId !== input.runId)
90
+ return false;
91
+ clearTimeout(pending.timer);
92
+ this.permissions.delete(input.approvalId);
93
+ void pending.client.replyPermission(pending.cwd, pending.requestId, input.decision);
94
+ return true;
95
+ }
96
+ respondUserInput(runId, requestId, answers) {
97
+ const pending = this.questions.get(requestId);
98
+ if (pending === undefined || pending.runId !== runId)
99
+ return false;
100
+ const normalized = pending.questionIds.map((id) => {
101
+ const value = answers[id];
102
+ const values = Array.isArray(value) ? value : typeof value === 'string' ? [value] : [];
103
+ if (values.length === 0 ||
104
+ values.length > 8 ||
105
+ values.some((item) => typeof item !== 'string' || item.length === 0 || item.length > 500))
106
+ throw new Error('USER_INPUT_INVALID');
107
+ return values;
108
+ });
109
+ clearTimeout(pending.timer);
110
+ this.questions.delete(requestId);
111
+ void pending.client.replyQuestion(pending.cwd, pending.requestId, normalized);
112
+ this.host.emitItem(runId, {
113
+ itemId: boundedConversationItemId('opencode-input', requestId),
114
+ kind: 'user_input_request',
115
+ status: 'COMPLETED',
116
+ payload: {},
117
+ merge: true
118
+ });
119
+ return true;
120
+ }
121
+ async run(input) {
122
+ const client = this.runner.managedClient(input.environment);
123
+ this.startingClients.set(input.runId, client);
124
+ this.startingCwds.set(input.runId, input.cwd);
125
+ this.host.active.add(input.runId);
126
+ this.host.emit(input.runId, 'run.started', {}, 'STARTING');
127
+ try {
128
+ const nativeSessionId = input.externalSessionId ?? openCodeSessionId(await client.createSession(input.cwd));
129
+ if (nativeSessionId === undefined)
130
+ throw new Error('OPENCODE_SESSION_INVALID');
131
+ this.host.adopt({
132
+ runId: input.runId,
133
+ sessionId: input.sessionId,
134
+ runner: 'opencode',
135
+ cwd: input.cwd,
136
+ externalSessionId: nativeSessionId
137
+ });
138
+ this.host.rememberNative(nativeSessionId, input.sessionId);
139
+ if (input.externalSessionId === undefined) {
140
+ this.host.promote(input.sessionId, nativeSessionId);
141
+ this.host.send('session.external-id', {
142
+ sessionId: input.sessionId,
143
+ externalSessionId: nativeSessionId
144
+ });
145
+ }
146
+ const abort = new AbortController();
147
+ this.runner.execution.set(input.runId, {
148
+ runId: input.runId,
149
+ sessionId: input.sessionId,
150
+ nativeSessionId,
151
+ cwd: input.cwd,
152
+ client,
153
+ abort,
154
+ access: input.access,
155
+ agent: input.agent
156
+ });
157
+ let subscriptionStarted = false;
158
+ let ready;
159
+ let rejectReady;
160
+ const subscriptionReady = new Promise((resolve, reject) => {
161
+ ready = () => {
162
+ subscriptionStarted = true;
163
+ resolve();
164
+ };
165
+ rejectReady = reject;
166
+ });
167
+ void client
168
+ .subscribe(input.cwd, abort.signal, (event) => this.event(input.runId, nativeSessionId, event), ready)
169
+ .catch((error) => {
170
+ if (abort.signal.aborted)
171
+ return;
172
+ if (!subscriptionStarted) {
173
+ rejectReady(error);
174
+ return;
175
+ }
176
+ this.finish(input.runId, 'FAILED', 'run.failed', {
177
+ code: error instanceof Error ? error.message : 'OPENCODE_EVENT_FAILED'
178
+ });
179
+ });
180
+ await subscriptionReady;
181
+ this.host.emit(input.runId, 'run.running', {}, 'RUNNING');
182
+ // The initial SSE snapshot can contain an idle event from before this prompt.
183
+ this.pendingIdle.delete(input.runId);
184
+ await client.prompt({
185
+ directory: input.cwd,
186
+ sessionID: nativeSessionId,
187
+ text: input.input,
188
+ model: input.model,
189
+ variant: input.effort,
190
+ agent: input.agent,
191
+ attachments: input.attachments
192
+ });
193
+ this.promptAdmitted.add(input.runId);
194
+ if (this.pendingIdle.delete(input.runId))
195
+ this.finish(input.runId, 'SUCCEEDED', 'run.completed', { status: 'SUCCEEDED' });
196
+ }
197
+ catch (error) {
198
+ this.finish(input.runId, 'FAILED', 'run.failed', {
199
+ code: error instanceof Error ? error.message : 'OPENCODE_RUN_START_FAILED'
200
+ });
201
+ }
202
+ }
203
+ event(runId, nativeSessionId, value) {
204
+ const event = record(value);
205
+ const properties = record(event?.properties);
206
+ const nested = record(properties?.part) ?? record(properties?.info);
207
+ const sessionID = typeof properties?.sessionID === 'string'
208
+ ? properties.sessionID
209
+ : typeof nested?.sessionID === 'string'
210
+ ? nested.sessionID
211
+ : undefined;
212
+ if (sessionID !== nativeSessionId)
213
+ return;
214
+ if (event?.type === 'session.idle') {
215
+ if (!this.promptAdmitted.has(runId)) {
216
+ this.pendingIdle.add(runId);
217
+ return;
218
+ }
219
+ this.finish(runId, 'SUCCEEDED', 'run.completed', { status: 'SUCCEEDED' });
220
+ return;
221
+ }
222
+ if (event?.type === 'session.error') {
223
+ this.finish(runId, 'FAILED', 'run.failed', { code: 'OPENCODE_SESSION_ERROR' });
224
+ return;
225
+ }
226
+ if (event?.type === 'message.part.updated') {
227
+ const part = record(properties?.part);
228
+ const item = openCodePart(part, this.runner.execution.get(runId)?.agent === 'plan');
229
+ if (item !== undefined)
230
+ this.host.emitItem(runId, item);
231
+ return;
232
+ }
233
+ if (event?.type === 'message.updated') {
234
+ const info = record(properties?.info);
235
+ if (info?.role === 'assistant' && typeof info.parentID === 'string')
236
+ this.runner.managedTurns.set(info.parentID, runId);
237
+ return;
238
+ }
239
+ if (event?.type === 'permission.asked')
240
+ this.requestPermission(runId, properties);
241
+ if (event?.type === 'question.asked')
242
+ this.requestQuestion(runId, properties);
243
+ }
244
+ requestPermission(runId, properties) {
245
+ const execution = this.runner.execution.get(runId);
246
+ const requestId = typeof properties?.id === 'string' ? properties.id : undefined;
247
+ if (execution === undefined || requestId === undefined)
248
+ return;
249
+ if (execution.access === 'auto') {
250
+ void execution.client.replyPermission(execution.cwd, requestId, 'APPROVED');
251
+ return;
252
+ }
253
+ const approvalId = `opencode:${requestId}`;
254
+ const expiresAt = Date.now() + this.interactionTimeoutMs;
255
+ const payload = {
256
+ toolName: typeof properties?.permission === 'string' ? properties.permission : 'OpenCode permission',
257
+ input: { patterns: Array.isArray(properties?.patterns) ? properties.patterns : [] }
258
+ };
259
+ try {
260
+ this.host.createApproval({ runId, approvalId, payload, expiresAt });
261
+ }
262
+ catch {
263
+ void execution.client.replyPermission(execution.cwd, requestId, 'REJECTED');
264
+ return;
265
+ }
266
+ const expire = () => {
267
+ const pending = this.permissions.get(approvalId);
268
+ if (pending === undefined)
269
+ return;
270
+ try {
271
+ this.host.expireApproval(approvalId);
272
+ }
273
+ catch (error) {
274
+ if (error instanceof Error && error.message === 'APPROVAL_NOT_EXPIRED') {
275
+ const timer = setTimeout(expire, Math.max(1, expiresAt - Date.now()));
276
+ this.permissions.set(approvalId, { ...pending, timer });
277
+ return;
278
+ }
279
+ }
280
+ this.permissions.delete(approvalId);
281
+ this.host.emit(runId, 'approval.expired', { approvalId });
282
+ void execution.client.replyPermission(execution.cwd, requestId, 'REJECTED');
283
+ };
284
+ const timer = setTimeout(expire, this.interactionTimeoutMs);
285
+ this.permissions.set(approvalId, {
286
+ runId,
287
+ requestId,
288
+ client: execution.client,
289
+ cwd: execution.cwd,
290
+ timer
291
+ });
292
+ this.host.publishApproval({ runId, approvalId, payload, expiresAt });
293
+ }
294
+ requestQuestion(runId, properties) {
295
+ const execution = this.runner.execution.get(runId);
296
+ const requestId = typeof properties?.id === 'string' ? properties.id : undefined;
297
+ const rawQuestions = properties?.questions;
298
+ if (execution === undefined || requestId === undefined)
299
+ return;
300
+ const questions = openCodeQuestions(rawQuestions);
301
+ if (questions.length === 0 || questions.length > 4 || this.questions.has(requestId))
302
+ return;
303
+ const timer = setTimeout(() => {
304
+ const pending = this.questions.get(requestId);
305
+ if (pending === undefined)
306
+ return;
307
+ this.questions.delete(requestId);
308
+ void pending.client.rejectQuestion(pending.cwd, pending.requestId);
309
+ this.host.emitItem(runId, {
310
+ itemId: boundedConversationItemId('opencode-input', requestId),
311
+ kind: 'user_input_request',
312
+ status: 'FAILED',
313
+ payload: { requestId, questions, errorCode: 'USER_INPUT_EXPIRED' },
314
+ merge: true
315
+ });
316
+ }, this.interactionTimeoutMs);
317
+ this.questions.set(requestId, {
318
+ runId,
319
+ requestId,
320
+ client: execution.client,
321
+ cwd: execution.cwd,
322
+ questionIds: questions.map((question) => question.id),
323
+ timer
324
+ });
325
+ this.host.emitItem(runId, {
326
+ itemId: boundedConversationItemId('opencode-input', requestId),
327
+ kind: 'user_input_request',
328
+ status: 'PENDING',
329
+ payload: { requestId, questions }
330
+ });
331
+ }
332
+ finish(runId, status, eventType, payload) {
333
+ const execution = this.runner.execution.get(runId);
334
+ const cwd = execution?.cwd ?? this.startingCwds.get(runId);
335
+ const client = execution?.client ?? this.startingClients.get(runId);
336
+ if (execution === undefined && cwd === undefined && client === undefined)
337
+ return;
338
+ execution?.abort.abort();
339
+ this.runner.execution.delete(runId);
340
+ this.startingCwds.delete(runId);
341
+ this.startingClients.delete(runId);
342
+ this.promptAdmitted.delete(runId);
343
+ this.pendingIdle.delete(runId);
344
+ this.host.active.delete(runId);
345
+ for (const [id, pending] of this.permissions)
346
+ if (pending.runId === runId) {
347
+ clearTimeout(pending.timer);
348
+ this.permissions.delete(id);
349
+ }
350
+ for (const [id, pending] of this.questions)
351
+ if (pending.runId === runId) {
352
+ clearTimeout(pending.timer);
353
+ this.questions.delete(id);
354
+ }
355
+ this.host.emit(runId, eventType, payload, status);
356
+ if (client !== undefined)
357
+ this.runner.releaseManagedClient(client);
358
+ if (cwd !== undefined)
359
+ void this.host.captureSnapshot(runId, cwd);
360
+ }
361
+ }
362
+ function openCodePart(part, planMode = false) {
363
+ const id = typeof part?.id === 'string' ? part.id : undefined;
364
+ if (id === undefined || typeof part?.type !== 'string')
365
+ return undefined;
366
+ const itemId = boundedConversationItemId('opencode', id);
367
+ if (part.type === 'text' && typeof part.text === 'string')
368
+ return {
369
+ itemId,
370
+ kind: planMode ? 'plan' : 'assistant_message',
371
+ status: record(part.time)?.end === undefined ? 'IN_PROGRESS' : 'COMPLETED',
372
+ payload: planMode
373
+ ? { explanation: compactRunnerText(part.text, 20_000), steps: [] }
374
+ : { text: part.text, format: 'markdown', model: null },
375
+ merge: true
376
+ };
377
+ if (part.type === 'reasoning' && typeof part.text === 'string')
378
+ return {
379
+ itemId,
380
+ kind: 'reasoning_summary',
381
+ status: record(part.time)?.end === undefined ? 'IN_PROGRESS' : 'COMPLETED',
382
+ payload: { sections: [{ index: 0, text: part.text }] },
383
+ merge: true
384
+ };
385
+ if (part.type === 'tool' && typeof part.tool === 'string') {
386
+ // `question.asked` carries the canonical request ID required for reply;
387
+ // the mirrored tool Part is presentation/history metadata only.
388
+ if (part.tool === 'question')
389
+ return undefined;
390
+ const state = record(part.state);
391
+ const status = state?.status === 'completed'
392
+ ? 'COMPLETED'
393
+ : state?.status === 'error'
394
+ ? 'FAILED'
395
+ : 'IN_PROGRESS';
396
+ return {
397
+ itemId,
398
+ kind: 'tool_call',
399
+ status,
400
+ payload: {
401
+ namespace: 'opencode',
402
+ toolName: compactRunnerText(part.tool, 128),
403
+ title: compactRunnerText(typeof state?.title === 'string' ? state.title : part.tool, 500),
404
+ inputSummary: compactRunnerValue(state?.input, 10_000).text || null,
405
+ outputSummary: typeof state?.output === 'string' ? compactRunnerText(state.output, 10_000) : null,
406
+ progressLabel: null,
407
+ errorCode: status === 'FAILED' ? 'OPENCODE_TOOL_FAILED' : null,
408
+ truncated: false
409
+ },
410
+ merge: true
411
+ };
412
+ }
413
+ if (part.type === 'compaction')
414
+ return {
415
+ itemId,
416
+ kind: 'context_compaction',
417
+ status: 'COMPLETED',
418
+ payload: { phase: 'COMPLETED', summary: '上下文已整理' }
419
+ };
420
+ if (part.type === 'patch')
421
+ return {
422
+ itemId,
423
+ kind: 'file_change',
424
+ status: 'COMPLETED',
425
+ payload: { changes: openCodePatchChanges(part.files) },
426
+ merge: true
427
+ };
428
+ if (part.type === 'step-start' || part.type === 'step-finish')
429
+ return undefined;
430
+ if (part.type === 'text' || part.type === 'reasoning')
431
+ return undefined;
432
+ return {
433
+ itemId,
434
+ kind: 'unknown',
435
+ status: record(part.time)?.end === undefined ? 'IN_PROGRESS' : 'COMPLETED',
436
+ payload: {
437
+ sourceEventType: `opencode:${part.type}`,
438
+ label: `OpenCode ${compactRunnerText(part.type, 128)}`
439
+ },
440
+ merge: true
441
+ };
442
+ }
443
+ function openCodeSessionId(value) {
444
+ const session = record(value);
445
+ return typeof session?.id === 'string' ? session.id : undefined;
446
+ }
447
+ function record(value) {
448
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
449
+ ? value
450
+ : undefined;
451
+ }
@@ -0,0 +1,60 @@
1
+ import type { NodeCapabilities, RunnerDefaultConfiguration, RunnerProfile } from '@myagentroam/protocol';
2
+ import type { NodeAgentSession, NodeDatabase, NodeWorkspace } from '../database.js';
3
+ import { OpenCodeServerClient } from '../opencode-server.js';
4
+ import { AbstractRunner, type ExternalResumeContext, type ExternalResumeResult, type RunnerDiscoveryContext, type RunnerSessionPresentation, type RunnerSessionPresentationContext } from './abstract-runner.js';
5
+ export interface OpenCodeManagedExecution {
6
+ readonly runId: string;
7
+ readonly sessionId: string;
8
+ readonly nativeSessionId: string;
9
+ readonly cwd: string;
10
+ readonly client: OpenCodeServerClient;
11
+ readonly abort: AbortController;
12
+ readonly access: string;
13
+ readonly agent: 'plan' | 'build';
14
+ }
15
+ export declare class OpenCodeRunner extends AbstractRunner<'opencode'> {
16
+ private readonly client;
17
+ private readonly clientFactory;
18
+ readonly name: "opencode";
19
+ readonly discoveryScope: "WORKSPACE_REQUIRED";
20
+ private readonly modelsByWorkspace;
21
+ private readonly contextLimitsByWorkspace;
22
+ readonly execution: Map<string, OpenCodeManagedExecution>;
23
+ readonly managedTurns: Map<string, string>;
24
+ private readonly environmentClients;
25
+ private readonly stoppingClients;
26
+ constructor(client?: OpenCodeServerClient, clientFactory?: () => OpenCodeServerClient);
27
+ profile(capabilities: NodeCapabilities): RunnerProfile;
28
+ refreshProfile(capabilities: NodeCapabilities, workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): Promise<RunnerProfile>;
29
+ available(capabilities: NodeCapabilities): boolean;
30
+ protected profileForValidation(): RunnerProfile;
31
+ supportsConfiguration(configuration: RunnerDefaultConfiguration, workspace?: NodeWorkspace): boolean;
32
+ defaultConfiguration(workspace?: NodeWorkspace): RunnerDefaultConfiguration;
33
+ discoverSessions(context: RunnerDiscoveryContext, workspace?: NodeWorkspace): Promise<readonly NodeAgentSession[]>;
34
+ resumeExternal(external: NodeAgentSession, context: ExternalResumeContext): Promise<ExternalResumeResult>;
35
+ readExternalActivity(session: NodeAgentSession): Promise<"UNAVAILABLE" | "EXTERNAL_ACTIVE" | "IDLE">;
36
+ readOfficialConversation(session: NodeAgentSession, input: {
37
+ readonly cursor?: string;
38
+ readonly limit?: number;
39
+ }, managedTurn: (nativeTurnId: string) => import('../database.js').NodeConversationTurn | undefined): Promise<{
40
+ readonly turns: readonly import("../database.js").NodeConversationTurn[];
41
+ readonly nextCursor: string | null;
42
+ } | undefined>;
43
+ managedRunForNativeTurn(nativeTurnId: string): string | undefined;
44
+ presentSession(session: NodeAgentSession, capabilities: NodeCapabilities, context: RunnerSessionPresentationContext): RunnerSessionPresentation;
45
+ renameNative(session: NodeAgentSession, input: {
46
+ readonly title?: string | null;
47
+ readonly pinned?: boolean;
48
+ }, database: NodeDatabase): Promise<void>;
49
+ removeNative(session: NodeAgentSession, nativeSessionId: string): Promise<boolean>;
50
+ forkNative(session: NodeAgentSession, boundary: string | null, title: string): Promise<string>;
51
+ executeCompact(session: NodeAgentSession, environment: Readonly<Record<string, string>>): Promise<unknown>;
52
+ readContextUsage(session: NodeAgentSession): Promise<{
53
+ model: string | null;
54
+ usedTokens: number;
55
+ maxTokens: number;
56
+ } | undefined>;
57
+ stop(): Promise<void>;
58
+ managedClient(environment: Readonly<Record<string, string>>): OpenCodeServerClient;
59
+ releaseManagedClient(client: OpenCodeServerClient): void;
60
+ }