@myagentroam/node 0.1.4 → 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 (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
package/dist/main.js CHANGED
@@ -4,6 +4,7 @@ import { NodeConnector } from './connector.js';
4
4
  import { loadNodeConfig, nodeConfigPath, nodeDatabasePath } from './config.js';
5
5
  import { getNodeHealth } from './health.js';
6
6
  import { removeServiceDefinition, writeServiceDefinition } from './service.js';
7
+ import { superviseNode } from './supervisor.js';
7
8
  const [command = 'status', ...args] = process.argv.slice(2);
8
9
  const config = readOption(args, '--config') ?? nodeConfigPath();
9
10
  const output = readOption(args, '--output') ?? defaultServicePath();
@@ -14,6 +15,11 @@ if (command === 'run') {
14
15
  process.once(signal, () => connector.stop());
15
16
  }
16
17
  }
18
+ else if (command === 'supervise') {
19
+ if (process.argv[1] === undefined)
20
+ throw new Error('NODE_ENTRYPOINT_UNAVAILABLE');
21
+ await superviseNode(config, process.argv[1]);
22
+ }
17
23
  else if (command === 'install') {
18
24
  await writeCurrentServiceDefinition(output, config);
19
25
  process.stdout.write(`${output}\n`);
@@ -44,4 +50,3 @@ async function writeCurrentServiceDefinition(output, configPath) {
44
50
  const nodeConfig = await loadNodeConfig(configPath);
45
51
  await writeServiceDefinition(output, process.execPath, configPath, process.platform, process.argv[1], [dirname(nodeDatabasePath(nodeConfig, configPath)), ...nodeConfig.allowedRoots]);
46
52
  }
47
- //# sourceMappingURL=main.js.map
@@ -246,4 +246,3 @@ export function assertNodeSchema(database) {
246
246
  throw new Error('NODE_DATABASE_BASELINE_MISMATCH:approvals');
247
247
  }
248
248
  }
249
- //# sourceMappingURL=v001.js.map
@@ -141,4 +141,3 @@ function integer(row, key) {
141
141
  function nullableInteger(row, key) {
142
142
  return row[key] === null || row[key] === undefined ? null : integer(row, key);
143
143
  }
144
- //# sourceMappingURL=v002.js.map
@@ -0,0 +1,4 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ export declare const NODE_SCHEMA_VERSION_V3 = 3;
3
+ export declare function migrateNodeSchemaV3(database: DatabaseSync): void;
4
+ export declare function assertNodeSchemaV3(database: DatabaseSync): void;
@@ -0,0 +1,44 @@
1
+ export const NODE_SCHEMA_VERSION_V3 = 3;
2
+ export function migrateNodeSchemaV3(database) {
3
+ database.exec('PRAGMA foreign_keys = OFF');
4
+ database.exec('BEGIN IMMEDIATE');
5
+ try {
6
+ database.exec(`ALTER TABLE agent_sessions RENAME TO agent_sessions_v2;
7
+ CREATE TABLE agent_sessions (
8
+ id TEXT PRIMARY KEY,
9
+ workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
10
+ runner TEXT NOT NULL CHECK (runner IN ('codex', 'claude-code', 'opencode')),
11
+ pin_order INTEGER CHECK (pin_order IS NULL OR pin_order >= 0),
12
+ metadata TEXT NOT NULL CHECK (json_valid(metadata))
13
+ ) STRICT;
14
+ INSERT INTO agent_sessions SELECT * FROM agent_sessions_v2;
15
+ DROP TABLE agent_sessions_v2;
16
+ ALTER TABLE runner_defaults RENAME TO runner_defaults_v2;
17
+ CREATE TABLE runner_defaults (
18
+ runner TEXT PRIMARY KEY CHECK (runner IN ('codex', 'claude-code', 'opencode')),
19
+ model TEXT NOT NULL, effort TEXT NOT NULL, access TEXT NOT NULL
20
+ ) STRICT;
21
+ INSERT INTO runner_defaults SELECT * FROM runner_defaults_v2;
22
+ DROP TABLE runner_defaults_v2;`);
23
+ database
24
+ .prepare('INSERT INTO node_schema_migrations (version, applied_at) VALUES (?, ?)')
25
+ .run(NODE_SCHEMA_VERSION_V3, Date.now());
26
+ database.exec('COMMIT');
27
+ }
28
+ catch (error) {
29
+ database.exec('ROLLBACK');
30
+ throw error;
31
+ }
32
+ finally {
33
+ database.exec('PRAGMA foreign_keys = ON');
34
+ }
35
+ }
36
+ export function assertNodeSchemaV3(database) {
37
+ for (const table of ['agent_sessions', 'runner_defaults']) {
38
+ const row = database
39
+ .prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?")
40
+ .get(table);
41
+ if (typeof row?.sql !== 'string' || !row.sql.includes("'opencode'"))
42
+ throw new Error(`NODE_DATABASE_SCHEMA_INVALID:${table}:runner`);
43
+ }
44
+ }
@@ -95,4 +95,3 @@ async function readAt(handle, size, position) {
95
95
  function isRecord(value) {
96
96
  return typeof value === 'object' && value !== null && !Array.isArray(value);
97
97
  }
98
- //# sourceMappingURL=native-jsonl-reader.js.map
@@ -4,6 +4,7 @@ export interface NativeTranscriptMessage {
4
4
  readonly role: 'USER' | 'ASSISTANT';
5
5
  readonly text: string;
6
6
  readonly createdAt: number | null;
7
+ readonly nativeId?: string;
7
8
  }
8
9
  export interface NativeTranscriptToolCall {
9
10
  readonly kind: 'tool_call';
@@ -18,6 +19,23 @@ export interface NativeTranscriptToolCall {
18
19
  readonly imageAttachments?: readonly NativeTranscriptImageAttachment[];
19
20
  readonly failed: boolean;
20
21
  readonly createdAt: number | null;
22
+ readonly nativeId?: string;
23
+ }
24
+ export interface NativeTranscriptUnknown {
25
+ readonly kind: 'unknown';
26
+ readonly sourceEventType: string;
27
+ readonly label: string;
28
+ readonly createdAt: number | null;
29
+ readonly nativeId?: string;
30
+ }
31
+ export interface NativeTranscriptReasoningSummary {
32
+ readonly kind: 'reasoning_summary';
33
+ readonly sections: readonly {
34
+ readonly index: number;
35
+ readonly text: string;
36
+ }[];
37
+ readonly createdAt: number | null;
38
+ readonly nativeId?: string;
21
39
  }
22
40
  export interface NativeTranscriptImageAttachment {
23
41
  readonly name: string;
@@ -25,7 +43,7 @@ export interface NativeTranscriptImageAttachment {
25
43
  /** Codex-owned local artifact path; never sent to Web. */
26
44
  readonly localPath?: string;
27
45
  }
28
- export type NativeTranscriptItem = NativeTranscriptMessage | NativeTranscriptToolCall;
46
+ export type NativeTranscriptItem = NativeTranscriptMessage | NativeTranscriptToolCall | NativeTranscriptReasoningSummary | NativeTranscriptUnknown;
29
47
  export interface NativeSessionHistory {
30
48
  readonly runner: RunnerName;
31
49
  readonly externalSessionId: string;
@@ -261,7 +261,8 @@ async function readTranscript(path, runner, byteLimit) {
261
261
  const generatedImage = codexGeneratedImageEvent(value);
262
262
  if (generatedImage !== undefined)
263
263
  items.push(generatedImage);
264
- for (const item of transcriptItems(value)) {
264
+ for (const parsedItem of transcriptItems(value)) {
265
+ const item = recordId === undefined ? parsedItem : { ...parsedItem, nativeId: recordId };
265
266
  if (item.kind === 'tool_result') {
266
267
  const toolIndex = toolIndexes.get(item.toolUseId);
267
268
  if (toolIndex === undefined)
@@ -395,7 +396,10 @@ function transcriptItems(record) {
395
396
  return results;
396
397
  const values = [
397
398
  ...toolUsesOf(record, createdAt),
398
- ...codexToolUsesOf(record, createdAt)
399
+ ...codexToolUsesOf(record, createdAt),
400
+ ...codexReasoningSummariesOf(record, createdAt),
401
+ ...unsupportedCodexResponseItems(record, createdAt),
402
+ ...unsupportedTranscriptItems(record, createdAt)
399
403
  ];
400
404
  const role = roleOf(record);
401
405
  if (role === undefined)
@@ -411,6 +415,65 @@ function transcriptItems(record) {
411
415
  values.unshift({ kind: 'message', role, text, createdAt });
412
416
  return values;
413
417
  }
418
+ function unsupportedCodexResponseItems(record, createdAt) {
419
+ const payload = codexToolPayload(record);
420
+ const type = typeof payload?.type === 'string' ? payload.type : undefined;
421
+ if (type === undefined ||
422
+ typeof payload?.role === 'string' ||
423
+ type === 'reasoning' ||
424
+ type === 'custom_tool_call' ||
425
+ type === 'custom_tool_call_output' ||
426
+ type === 'function_call' ||
427
+ type === 'function_call_output')
428
+ return [];
429
+ return [
430
+ {
431
+ kind: 'unknown',
432
+ sourceEventType: `codex:${type}`,
433
+ label: `Codex ${compact(type).slice(0, 128)}`,
434
+ createdAt
435
+ }
436
+ ];
437
+ }
438
+ function codexReasoningSummariesOf(record, createdAt) {
439
+ const payload = codexToolPayload(record);
440
+ if (payload?.type !== 'reasoning')
441
+ return [];
442
+ const sections = Array.isArray(payload.summary)
443
+ ? payload.summary.flatMap((value, index) => {
444
+ if (typeof value !== 'string')
445
+ return [];
446
+ const text = compact(value).slice(0, 10_000);
447
+ return text.length === 0 ? [] : [{ index, text }];
448
+ })
449
+ : [];
450
+ return [{ kind: 'reasoning_summary', sections, createdAt }];
451
+ }
452
+ function unsupportedTranscriptItems(record, createdAt) {
453
+ return contentBlocks(record).flatMap((block) => {
454
+ const type = typeof block.type === 'string' ? block.type : undefined;
455
+ if (type === undefined ||
456
+ type === 'text' ||
457
+ type === 'input_text' ||
458
+ type === 'output_text' ||
459
+ type === 'image' ||
460
+ type === 'input_image' ||
461
+ type === 'thinking' ||
462
+ type === 'redacted_thinking' ||
463
+ type === 'encrypted_thinking' ||
464
+ type === 'tool_use' ||
465
+ type === 'tool_result')
466
+ return [];
467
+ return [
468
+ {
469
+ kind: 'unknown',
470
+ sourceEventType: `transcript:${type}`,
471
+ label: `Agent ${compact(type).slice(0, 128)}`,
472
+ createdAt
473
+ }
474
+ ];
475
+ });
476
+ }
414
477
  function toolUsesOf(record, createdAt) {
415
478
  return contentBlocks(record).flatMap((block) => {
416
479
  if (block['type'] !== 'tool_use' ||
@@ -768,4 +831,3 @@ function indexKey(runner, cwd, externalSessionId) {
768
831
  function isRecord(value) {
769
832
  return typeof value === 'object' && value !== null && !Array.isArray(value);
770
833
  }
771
- //# sourceMappingURL=native-session-history.js.map
@@ -0,0 +1,33 @@
1
+ export interface OpenCodeProbe {
2
+ readonly available: boolean;
3
+ readonly compatible: boolean;
4
+ readonly version?: string;
5
+ }
6
+ export interface OpenCodeProviderCatalog {
7
+ readonly connected?: readonly string[];
8
+ readonly default?: Readonly<Record<string, string>>;
9
+ readonly all?: readonly {
10
+ readonly id?: string;
11
+ readonly name?: string;
12
+ readonly source?: 'env' | 'config' | 'custom' | 'api';
13
+ readonly models?: Readonly<Record<string, {
14
+ readonly id?: string;
15
+ readonly name?: string;
16
+ readonly variants?: Readonly<Record<string, {
17
+ readonly disabled?: boolean;
18
+ }>>;
19
+ readonly limit?: {
20
+ readonly context?: number;
21
+ };
22
+ }>>;
23
+ }[];
24
+ }
25
+ export interface OpenCodeProfileModel {
26
+ readonly id: string;
27
+ readonly label: string;
28
+ readonly supportedEfforts: string[];
29
+ }
30
+ export declare function probeOpenCode(command?: string): OpenCodeProbe;
31
+ export declare function probeOpenCodeAsync(command?: string): Promise<OpenCodeProbe>;
32
+ export declare function openCodeProfileModels(catalog: OpenCodeProviderCatalog): OpenCodeProfileModel[];
33
+ export declare function openCodeContextLimits(catalog: OpenCodeProviderCatalog): ReadonlyMap<string, number>;
@@ -0,0 +1,80 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { minimalRunnerEnvironment, platformCliInvocation, runRunnerProbe } from './operational.js';
3
+ const PROBE_TIMEOUT_MS = 3_000;
4
+ export function probeOpenCode(command = 'opencode') {
5
+ const invocation = platformCliInvocation(command, ['--version']);
6
+ const result = spawnSync(invocation.command, invocation.args, {
7
+ windowsHide: true,
8
+ env: minimalRunnerEnvironment(),
9
+ timeout: PROBE_TIMEOUT_MS,
10
+ encoding: 'utf8'
11
+ });
12
+ if (result.status !== 0 || result.error !== undefined)
13
+ return { available: false, compatible: false };
14
+ return openCodeProbeResult(`${result.stdout}${result.stderr}`);
15
+ }
16
+ export async function probeOpenCodeAsync(command = 'opencode') {
17
+ const invocation = platformCliInvocation(command, ['--version']);
18
+ const result = await runRunnerProbe(invocation.command, invocation.args, {
19
+ env: minimalRunnerEnvironment(),
20
+ timeoutMs: PROBE_TIMEOUT_MS
21
+ });
22
+ return result.status === 0
23
+ ? openCodeProbeResult(`${result.stdout}${result.stderr}`)
24
+ : { available: false, compatible: false };
25
+ }
26
+ function openCodeProbeResult(output) {
27
+ const version = output.trim().replace(/^v/i, '').slice(0, 128);
28
+ return {
29
+ available: version.length > 0,
30
+ compatible: /^1\.18\./.test(version),
31
+ ...(version.length === 0 ? {} : { version })
32
+ };
33
+ }
34
+ export function openCodeProfileModels(catalog) {
35
+ const connected = new Set(catalog.connected ?? []);
36
+ return (catalog.all ?? [])
37
+ .filter((provider) => typeof provider.id === 'string' &&
38
+ connected.has(provider.id) &&
39
+ provider.source === 'config')
40
+ .flatMap((provider) => {
41
+ const providerId = provider.id;
42
+ return Object.entries(provider.models ?? {}).flatMap(([key, model]) => {
43
+ const modelId = boundedString(model.id) ?? boundedString(key);
44
+ if (modelId === undefined)
45
+ return [];
46
+ const variants = Object.entries(model.variants ?? {})
47
+ .filter(([, value]) => value.disabled !== true)
48
+ .map(([id]) => id)
49
+ .filter((id) => id.length > 0 && id.length <= 64)
50
+ .slice(0, 32);
51
+ return [
52
+ {
53
+ id: `${providerId}/${modelId}`.slice(0, 128),
54
+ label: (boundedString(model.name) ?? modelId).slice(0, 128),
55
+ supportedEfforts: variants
56
+ }
57
+ ];
58
+ });
59
+ });
60
+ }
61
+ export function openCodeContextLimits(catalog) {
62
+ const connected = new Set(catalog.connected ?? []);
63
+ return new Map((catalog.all ?? [])
64
+ .filter((provider) => typeof provider.id === 'string' &&
65
+ connected.has(provider.id) &&
66
+ provider.source === 'config')
67
+ .flatMap((provider) => Object.entries(provider.models ?? {}).flatMap(([key, model]) => {
68
+ const modelId = boundedString(model.id) ?? boundedString(key);
69
+ const context = model.limit?.context;
70
+ return modelId !== undefined && Number.isSafeInteger(context) && (context ?? 0) > 0
71
+ ? [[`${provider.id}/${modelId}`, context]]
72
+ : [];
73
+ })));
74
+ }
75
+ function boundedString(value) {
76
+ if (typeof value !== 'string')
77
+ return undefined;
78
+ const trimmed = value.trim();
79
+ return trimmed.length > 0 && trimmed.length <= 4096 ? trimmed : undefined;
80
+ }
@@ -0,0 +1,47 @@
1
+ import { spawn } from 'node:child_process';
2
+ import type { OpenCodeProviderCatalog } from './opencode-runtime.js';
3
+ export interface OpenCodeServerOptions {
4
+ readonly command?: string;
5
+ readonly startTimeoutMs?: number;
6
+ readonly spawnProcess?: typeof spawn;
7
+ }
8
+ export declare class OpenCodeServerClient {
9
+ private readonly options;
10
+ private child;
11
+ private client;
12
+ private starting;
13
+ private environment;
14
+ constructor(options?: OpenCodeServerOptions);
15
+ configureEnvironment(environment: Readonly<Record<string, string>>): void;
16
+ start(): Promise<void>;
17
+ private startServer;
18
+ providers(directory: string): Promise<OpenCodeProviderCatalog>;
19
+ sessions(directory: string, limit?: number): Promise<readonly unknown[]>;
20
+ sessionStatus(directory: string): Promise<Readonly<Record<string, unknown>>>;
21
+ createSession(directory: string, title?: string): Promise<unknown>;
22
+ forkSession(directory: string, sessionID: string, messageID: string): Promise<unknown>;
23
+ messages(directory: string, sessionID: string, limit?: number): Promise<readonly unknown[]>;
24
+ updateSession(directory: string, sessionID: string, title: string): Promise<unknown>;
25
+ deleteSession(directory: string, sessionID: string): Promise<unknown>;
26
+ abort(directory: string, sessionID: string): Promise<unknown>;
27
+ summarize(directory: string, sessionID: string, model: string): Promise<unknown>;
28
+ replyPermission(directory: string, requestID: string, decision: 'APPROVED' | 'REJECTED'): Promise<unknown>;
29
+ replyQuestion(directory: string, requestID: string, answers: readonly (readonly string[])[]): Promise<unknown>;
30
+ rejectQuestion(directory: string, requestID: string): Promise<unknown>;
31
+ prompt(input: {
32
+ readonly directory: string;
33
+ readonly sessionID: string;
34
+ readonly text: string;
35
+ readonly model: string;
36
+ readonly variant?: string;
37
+ readonly agent?: 'plan' | 'build';
38
+ readonly attachments?: readonly {
39
+ readonly name: string;
40
+ readonly mime: 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp';
41
+ readonly dataBase64: string;
42
+ }[];
43
+ }): Promise<unknown>;
44
+ subscribe(directory: string, signal: AbortSignal, onEvent: (event: unknown) => void, onReady?: () => void): Promise<void>;
45
+ stop(): Promise<void>;
46
+ private requireClient;
47
+ }
@@ -0,0 +1,250 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { randomBytes } from 'node:crypto';
3
+ import { createServer } from 'node:net';
4
+ import { createOpencodeClient } from '@opencode-ai/sdk/v2/client';
5
+ import { minimalRunnerEnvironment, platformCliInvocation } from './operational.js';
6
+ import { terminateProcessTree } from './process-tree.js';
7
+ const START_TIMEOUT_MS = 10_000;
8
+ export class OpenCodeServerClient {
9
+ options;
10
+ child;
11
+ client;
12
+ starting;
13
+ environment = {};
14
+ constructor(options = {}) {
15
+ this.options = options;
16
+ }
17
+ configureEnvironment(environment) {
18
+ if (this.child !== undefined)
19
+ throw new Error('OPENCODE_SERVER_ALREADY_STARTED');
20
+ this.environment = { ...environment };
21
+ }
22
+ async start() {
23
+ if (this.client !== undefined)
24
+ return;
25
+ if (this.starting !== undefined)
26
+ return this.starting;
27
+ const starting = this.startServer();
28
+ this.starting = starting;
29
+ try {
30
+ await starting;
31
+ }
32
+ finally {
33
+ if (this.starting === starting)
34
+ this.starting = undefined;
35
+ }
36
+ }
37
+ async startServer() {
38
+ const port = await availablePort();
39
+ const password = randomBytes(24).toString('base64url');
40
+ const invocation = platformCliInvocation(this.options.command ?? 'opencode', [
41
+ 'serve',
42
+ '--hostname',
43
+ '127.0.0.1',
44
+ '--port',
45
+ String(port)
46
+ ]);
47
+ const spawnProcess = this.options.spawnProcess ?? spawn;
48
+ const child = spawnProcess(invocation.command, invocation.args, {
49
+ windowsHide: true,
50
+ detached: process.platform !== 'win32',
51
+ env: {
52
+ ...minimalRunnerEnvironment(),
53
+ ...this.environment,
54
+ OPENCODE_SERVER_USERNAME: 'opencode',
55
+ OPENCODE_SERVER_PASSWORD: password,
56
+ OPENCODE_DISABLE_AUTOUPDATE: 'true'
57
+ },
58
+ stdio: ['ignore', 'pipe', 'pipe']
59
+ });
60
+ this.child = child;
61
+ const authorization = `Basic ${Buffer.from(`opencode:${password}`).toString('base64')}`;
62
+ const client = createOpencodeClient({
63
+ baseUrl: `http://127.0.0.1:${port}`,
64
+ headers: { authorization },
65
+ throwOnError: true
66
+ });
67
+ try {
68
+ await waitUntilHealthy(`http://127.0.0.1:${port}/global/health`, authorization, child, this.options.startTimeoutMs ?? START_TIMEOUT_MS);
69
+ this.client = client;
70
+ }
71
+ catch (error) {
72
+ await this.stop();
73
+ throw error;
74
+ }
75
+ }
76
+ async providers(directory) {
77
+ const client = await this.requireClient();
78
+ return unwrap(await client.provider.list({ directory }));
79
+ }
80
+ async sessions(directory, limit = 100) {
81
+ const client = await this.requireClient();
82
+ return unwrap(await client.session.list({ directory, limit }));
83
+ }
84
+ async sessionStatus(directory) {
85
+ const client = await this.requireClient();
86
+ return unwrap(await client.session.status({ directory }));
87
+ }
88
+ async createSession(directory, title) {
89
+ const client = await this.requireClient();
90
+ return unwrap(await client.session.create({ directory, ...(title === undefined ? {} : { title }) }));
91
+ }
92
+ async forkSession(directory, sessionID, messageID) {
93
+ const client = await this.requireClient();
94
+ return unwrap(await client.session.fork({ directory, sessionID, messageID }));
95
+ }
96
+ async messages(directory, sessionID, limit = 100) {
97
+ const client = await this.requireClient();
98
+ return unwrap(await client.session.messages({ directory, sessionID, limit }));
99
+ }
100
+ async updateSession(directory, sessionID, title) {
101
+ const client = await this.requireClient();
102
+ return unwrap(await client.session.update({ directory, sessionID, title }));
103
+ }
104
+ async deleteSession(directory, sessionID) {
105
+ const client = await this.requireClient();
106
+ return unwrap(await client.session.delete({ directory, sessionID }));
107
+ }
108
+ async abort(directory, sessionID) {
109
+ const client = await this.requireClient();
110
+ return unwrap(await client.session.abort({ directory, sessionID }));
111
+ }
112
+ async summarize(directory, sessionID, model) {
113
+ const client = await this.requireClient();
114
+ const separator = model.indexOf('/');
115
+ if (separator <= 0 || separator === model.length - 1)
116
+ throw new Error('OPENCODE_MODEL_INVALID');
117
+ return unwrap(await client.session.summarize({
118
+ directory,
119
+ sessionID,
120
+ providerID: model.slice(0, separator),
121
+ modelID: model.slice(separator + 1)
122
+ }));
123
+ }
124
+ async replyPermission(directory, requestID, decision) {
125
+ const client = await this.requireClient();
126
+ return unwrap(await client.permission.reply({
127
+ directory,
128
+ requestID,
129
+ reply: decision === 'APPROVED' ? 'once' : 'reject'
130
+ }));
131
+ }
132
+ async replyQuestion(directory, requestID, answers) {
133
+ const client = await this.requireClient();
134
+ return unwrap(await client.question.reply({
135
+ directory,
136
+ requestID,
137
+ answers: answers.map((answer) => [...answer])
138
+ }));
139
+ }
140
+ async rejectQuestion(directory, requestID) {
141
+ const client = await this.requireClient();
142
+ return unwrap(await client.question.reject({ directory, requestID }));
143
+ }
144
+ async prompt(input) {
145
+ const client = await this.requireClient();
146
+ const separator = input.model.indexOf('/');
147
+ if (separator <= 0 || separator === input.model.length - 1)
148
+ throw new Error('OPENCODE_MODEL_INVALID');
149
+ return unwrap(await client.session.promptAsync({
150
+ directory: input.directory,
151
+ sessionID: input.sessionID,
152
+ model: {
153
+ providerID: input.model.slice(0, separator),
154
+ modelID: input.model.slice(separator + 1)
155
+ },
156
+ ...(input.variant === undefined || input.variant.length === 0
157
+ ? {}
158
+ : { variant: input.variant }),
159
+ ...(input.agent === undefined ? {} : { agent: input.agent }),
160
+ parts: [
161
+ { type: 'text', text: input.text },
162
+ ...(input.attachments ?? []).map((attachment) => ({
163
+ type: 'file',
164
+ mime: attachment.mime,
165
+ filename: attachment.name,
166
+ url: `data:${attachment.mime};base64,${attachment.dataBase64}`
167
+ }))
168
+ ]
169
+ }));
170
+ }
171
+ async subscribe(directory, signal, onEvent, onReady) {
172
+ const client = await this.requireClient();
173
+ const stream = await client.event.subscribe({ directory }, { signal });
174
+ onReady?.();
175
+ for await (const event of stream.stream)
176
+ onEvent(event);
177
+ }
178
+ async stop() {
179
+ this.client = undefined;
180
+ const child = this.child;
181
+ this.child = undefined;
182
+ if (child === undefined)
183
+ return;
184
+ if (child.pid === undefined) {
185
+ child.kill('SIGTERM');
186
+ return;
187
+ }
188
+ try {
189
+ await terminateProcessTree({
190
+ pid: child.pid,
191
+ kill: (signal) => child.kill(signal)
192
+ });
193
+ }
194
+ catch {
195
+ child.kill('SIGTERM');
196
+ }
197
+ }
198
+ async requireClient() {
199
+ await this.start();
200
+ return this.client;
201
+ }
202
+ }
203
+ async function availablePort() {
204
+ return new Promise((resolve, reject) => {
205
+ const server = createServer();
206
+ server.once('error', reject);
207
+ server.listen(0, '127.0.0.1', () => {
208
+ const address = server.address();
209
+ const port = typeof address === 'object' && address !== null ? address.port : undefined;
210
+ server.close((error) => {
211
+ if (error !== undefined)
212
+ reject(error);
213
+ else if (port === undefined)
214
+ reject(new Error('OPENCODE_PORT_UNAVAILABLE'));
215
+ else
216
+ resolve(port);
217
+ });
218
+ });
219
+ });
220
+ }
221
+ async function waitUntilHealthy(url, authorization, child, timeoutMs) {
222
+ const deadline = Date.now() + timeoutMs;
223
+ while (Date.now() < deadline) {
224
+ if (child.exitCode !== null)
225
+ throw new Error('OPENCODE_SERVER_EXITED');
226
+ try {
227
+ const response = await fetch(url, {
228
+ headers: { authorization },
229
+ signal: AbortSignal.timeout(Math.min(1_000, Math.max(1, deadline - Date.now())))
230
+ });
231
+ if (response.ok)
232
+ return;
233
+ }
234
+ catch {
235
+ // The child may still be binding its loopback socket.
236
+ }
237
+ await new Promise((resolve) => setTimeout(resolve, 50));
238
+ }
239
+ throw new Error('OPENCODE_SERVER_START_TIMEOUT');
240
+ }
241
+ function unwrap(result) {
242
+ if (typeof result !== 'object' || result === null || !('data' in result))
243
+ return result;
244
+ const wrapped = result;
245
+ if (wrapped.error !== undefined)
246
+ throw new Error('OPENCODE_REQUEST_FAILED');
247
+ if (wrapped.data === undefined)
248
+ throw new Error('OPENCODE_RESPONSE_INVALID');
249
+ return wrapped.data;
250
+ }
@@ -101,12 +101,19 @@ export function platformCliInvocation(command, args, platform = process.platform
101
101
  const cliCommand = platformCliCommand(command, platform);
102
102
  if (platform !== 'win32' || !/\.(?:cmd|bat)$/i.test(cliCommand))
103
103
  return { command: cliCommand, args };
104
- if (![cliCommand, ...args].every((value) => /^[A-Za-z0-9_./:\\-]+$/.test(value)))
104
+ if (![cliCommand, ...args].every((value) => /^[A-Za-z0-9_@./:\\ -]+$/.test(value)))
105
105
  throw new Error('NODE_CLI_COMMAND_INVALID');
106
- return { command: comspec, args: ['/d', '/s', '/c', [cliCommand, ...args].join(' ')] };
106
+ return {
107
+ command: comspec,
108
+ args: [
109
+ '/d',
110
+ '/s',
111
+ '/c',
112
+ [cliCommand, ...args].map((value) => (value.includes(' ') ? `"${value}"` : value)).join(' ')
113
+ ]
114
+ };
107
115
  }
108
116
  export function nodeLog(event, fields = {}) {
109
117
  const safeFields = redactLogValue(fields);
110
118
  process.stdout.write(`${JSON.stringify({ timestamp: new Date().toISOString(), level: 'info', component: 'mar-node', event, ...safeFields })}\n`);
111
119
  }
112
- //# sourceMappingURL=operational.js.map
@@ -16,4 +16,3 @@ export async function terminateProcessTree(child, platform = process.platform, r
16
16
  child.kill('SIGTERM');
17
17
  }
18
18
  }
19
- //# sourceMappingURL=process-tree.js.map