@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,113 @@
1
+ import { isTerminalRunStatus } from '../util/node-operation-parsers.js';
2
+ import { isPlainRecord } from '../runner/codex/conversation-parser.js';
3
+ export class RunWorkbenchService {
4
+ options;
5
+ constructor(options) {
6
+ this.options = options;
7
+ }
8
+ operations() {
9
+ return {
10
+ 'run.get': (data) => this.get(record(data)),
11
+ 'run.snapshot': (data) => this.snapshot(record(data)),
12
+ 'run.image.read': (data) => this.image(record(data)),
13
+ 'run.user-input.respond': (data) => this.respond(record(data)),
14
+ 'session.interrupt': (data) => this.interruptSession(record(data)),
15
+ 'run.cancel': (data) => this.cancelOrInterrupt('run.cancel', record(data)),
16
+ 'run.interrupt': (data) => this.cancelOrInterrupt('run.interrupt', record(data)),
17
+ 'run.approvals': (data) => this.approvals(record(data)),
18
+ 'run.approval.decide': (data) => this.decideApproval(record(data))
19
+ };
20
+ }
21
+ get(input) {
22
+ return { run: this.requireRun(input.runId) };
23
+ }
24
+ snapshot(input) {
25
+ const run = this.requireRun(input.runId);
26
+ return { snapshot: this.options.runtime.getSnapshot(run.id) ?? null };
27
+ }
28
+ async image(input) {
29
+ if (typeof input.runId !== 'string' ||
30
+ typeof input.imageIndex !== 'number' ||
31
+ !Number.isInteger(input.imageIndex) ||
32
+ input.imageIndex < 0)
33
+ throw new Error('RUN_IMAGE_INVALID');
34
+ const image = await this.options.readImage(input.runId, input.imageIndex);
35
+ if (image === undefined)
36
+ throw new Error('RUN_IMAGE_NOT_FOUND');
37
+ return { image };
38
+ }
39
+ respond(input) {
40
+ if (typeof input.runId !== 'string' ||
41
+ typeof input.requestId !== 'string' ||
42
+ !isPlainRecord(input.answers))
43
+ throw new Error('USER_INPUT_INVALID');
44
+ this.options.respondUserInput(input.runId, input.requestId, input.answers);
45
+ return { accepted: true };
46
+ }
47
+ interruptSession(input) {
48
+ if (typeof input.sessionId !== 'string')
49
+ throw new Error('SESSION_NOT_FOUND');
50
+ const session = this.options.runtime.getAgentSession(input.sessionId);
51
+ if (session === undefined)
52
+ throw new Error('SESSION_NOT_FOUND');
53
+ const run = this.options.runtime.activeSessionRun(session.id);
54
+ if (run === undefined || isTerminalRunStatus(run.status))
55
+ throw new Error('RUN_NOT_FOUND');
56
+ return this.cancelOrInterrupt('run.interrupt', { runId: run.id });
57
+ }
58
+ cancelOrInterrupt(operation, input) {
59
+ const run = this.requireRun(input.runId);
60
+ if (operation === 'run.cancel' &&
61
+ run.status === 'QUEUED' &&
62
+ this.options.runtime.isQueuedRun(run.id)) {
63
+ this.options.runtime.deleteQueuedRun(run.id);
64
+ this.options.deleteQueuedStart(run.id);
65
+ this.options.clearImages(run.id);
66
+ this.options.emitQueue(run.workspaceId, run.sessionId);
67
+ return { cancelled: true };
68
+ }
69
+ const next = this.options.runtime.transitionRun(run.id, operation === 'run.cancel' && run.status === 'QUEUED' ? 'CANCELLED' : 'CANCELLING');
70
+ if (operation === 'run.cancel' && next.status === 'CANCELLED') {
71
+ this.options.emitRun(run.id, 'run.completed', { status: 'CANCELLED' }, 'CANCELLED');
72
+ return { run: next };
73
+ }
74
+ if (operation === 'run.interrupt') {
75
+ this.options.runtime.pauseSessionQueue(run.sessionId);
76
+ this.options.emitQueue(run.workspaceId, run.sessionId);
77
+ this.options.markInterrupted(run.id);
78
+ }
79
+ this.options.control(operation, { runId: run.id });
80
+ return { run: next };
81
+ }
82
+ approvals(input) {
83
+ const run = this.requireRun(input.runId);
84
+ return { approvals: this.options.runtime.listApprovals(run.id) };
85
+ }
86
+ decideApproval(input) {
87
+ if (typeof input.approvalId !== 'string' ||
88
+ (input.decision !== 'APPROVED' && input.decision !== 'REJECTED'))
89
+ throw new Error('APPROVAL_DECISION_INVALID');
90
+ const approval = this.options.runtime.decideApproval(input.approvalId, input.decision);
91
+ this.options.emitRun(approval.runId, 'approval.decided', {
92
+ approvalId: approval.id,
93
+ decision: input.decision
94
+ });
95
+ this.options.control('run.approval.decision', {
96
+ runId: approval.runId,
97
+ approvalId: approval.id,
98
+ decision: input.decision
99
+ });
100
+ return { approval };
101
+ }
102
+ requireRun(runId) {
103
+ if (typeof runId !== 'string')
104
+ throw new Error('RUN_NOT_FOUND');
105
+ const run = this.options.runtime.getRun(runId);
106
+ if (run === undefined)
107
+ throw new Error('RUN_NOT_FOUND');
108
+ return run;
109
+ }
110
+ }
111
+ function record(value) {
112
+ return isPlainRecord(value) ? value : {};
113
+ }
@@ -0,0 +1,13 @@
1
+ import type { NodeEnvelope } from '@myagentroam/protocol';
2
+ import type { RunnerInteractionService } from './runner-interaction-service.js';
3
+ interface RunnerChannelMessageServiceOptions {
4
+ readonly interactions: RunnerInteractionService;
5
+ readonly send: (type: string, payload: unknown, replyTo?: string) => void;
6
+ }
7
+ /** Validates shared-control channel messages before delivery to a Runner. */
8
+ export declare class RunnerChannelMessageService {
9
+ private readonly options;
10
+ constructor(options: RunnerChannelMessageServiceOptions);
11
+ handle(envelope: NodeEnvelope): void;
12
+ }
13
+ export {};
@@ -0,0 +1,20 @@
1
+ /** Validates shared-control channel messages before delivery to a Runner. */
2
+ export class RunnerChannelMessageService {
3
+ options;
4
+ constructor(options) {
5
+ this.options = options;
6
+ }
7
+ handle(envelope) {
8
+ const payload = envelope.payload;
9
+ if (typeof payload.sessionId !== 'string' ||
10
+ typeof payload.id !== 'string' ||
11
+ typeof payload.senderId !== 'string' ||
12
+ typeof payload.token !== 'string' ||
13
+ typeof payload.content !== 'string') {
14
+ this.options.send('error', { code: 'MESSAGE_INVALID' }, envelope.id);
15
+ return;
16
+ }
17
+ const delivery = this.options.interactions.deliverChannel('claude-code', payload.sessionId, payload.id, payload.senderId, payload.token, payload.content);
18
+ this.options.send('ack', { type: envelope.type, delivery }, envelope.id);
19
+ }
20
+ }
@@ -0,0 +1,40 @@
1
+ import type { NodeRuntimeState } from '../runtime-state.js';
2
+ import type { NodeAgentSession } from '../database.js';
3
+ import type { RunnerRegistry } from '../runner/runner-registry.js';
4
+ import type { ClaudeCodeRunner } from '../runner/claude-code-runner.js';
5
+ import type { CodexRunner } from '../runner/codex-runner.js';
6
+ import { type PublicConversationItem } from '../runner/codex/conversation-parser.js';
7
+ import type { RunDomainState } from './run-domain-state.js';
8
+ interface RunnerInteractionServiceOptions {
9
+ readonly runtime: NodeRuntimeState;
10
+ readonly state: RunDomainState;
11
+ readonly codex: () => CodexRunner;
12
+ readonly claude: () => ClaudeCodeRunner;
13
+ readonly runners: RunnerRegistry;
14
+ readonly emitRun: (runId: string, eventType: string, payload: unknown, status?: 'FAILED' | 'CANCELLED' | 'INTERRUPTED') => void;
15
+ readonly emitItem: (runId: string, item: PublicConversationItem) => void;
16
+ readonly captureSnapshot: (runId: string, cwd: string) => Promise<void>;
17
+ readonly approvalTimeoutMs: number;
18
+ readonly publishApproval: (input: {
19
+ readonly runId: string;
20
+ readonly approvalId: string;
21
+ readonly payload: unknown;
22
+ readonly expiresAt: number;
23
+ }) => void;
24
+ }
25
+ export declare class RunnerInteractionService {
26
+ private readonly options;
27
+ constructor(options: RunnerInteractionServiceOptions);
28
+ requestClaudeApproval(runId: string, toolName: string, toolInput: Record<string, unknown>): Promise<import("@anthropic-ai/claude-agent-sdk").PermissionResult>;
29
+ requestClaudeUserInput(runId: string, toolInput: Record<string, unknown>, toolUseId: string): Promise<import("@anthropic-ai/claude-agent-sdk").PermissionResult>;
30
+ deliverChannel(runner: NodeAgentSession['runner'], sessionId: string, messageId: string, senderId: string, token: string, content: string): import("../claude-channel.js").ChannelDelivery;
31
+ decideCodexApproval(approvalId: string, decision: 'APPROVED' | 'REJECTED'): boolean;
32
+ decideClaudeApproval(approvalId: string, decision: 'APPROVED' | 'REJECTED'): boolean;
33
+ cancelClaudeRun(runId: string, intent: 'CANCEL' | 'INTERRUPT'): boolean;
34
+ cancelCodexTurn(runId: string, threadId: string, turnId: string, cwd: string, terminalStatus: 'CANCELLED' | 'INTERRUPTED'): Promise<void>;
35
+ respondUserInput(runId: string, requestId: string, rawAnswers: Record<string, unknown>): void;
36
+ completeCancelledRun(runId: string, cwd: string | undefined): boolean;
37
+ private respondCodexUserInput;
38
+ private answers;
39
+ }
40
+ export {};
@@ -0,0 +1,246 @@
1
+ import { denyPermission } from '../claude-agent-sdk.js';
2
+ import { boundedConversationItemId, claudeUserInputQuestions, isTerminalRunStatus } from '../runner/codex/conversation-parser.js';
3
+ export class RunnerInteractionService {
4
+ options;
5
+ constructor(options) {
6
+ this.options = options;
7
+ }
8
+ requestClaudeApproval(runId, toolName, toolInput) {
9
+ const approvalId = `claude:${crypto.randomUUID()}`;
10
+ return new Promise((resolve) => {
11
+ const expiresAt = Date.now() + this.options.approvalTimeoutMs;
12
+ const settle = (decision) => resolve(decision === 'APPROVED'
13
+ ? { behavior: 'allow', updatedInput: toolInput }
14
+ : denyPermission());
15
+ try {
16
+ this.options.runtime.createApproval({
17
+ runId,
18
+ approvalId,
19
+ payload: { toolName, input: toolInput },
20
+ expiresAt
21
+ });
22
+ }
23
+ catch {
24
+ resolve(denyPermission('APPROVAL_STORE_FAILED'));
25
+ return;
26
+ }
27
+ const expire = () => {
28
+ const approvals = this.options.claude().execution.approvals;
29
+ const active = approvals.get(approvalId);
30
+ if (active === undefined)
31
+ return;
32
+ try {
33
+ this.options.runtime.expireApproval(approvalId);
34
+ }
35
+ catch (error) {
36
+ if (error instanceof Error && error.message === 'APPROVAL_NOT_EXPIRED') {
37
+ const timer = setTimeout(expire, Math.max(1, expiresAt - Date.now()));
38
+ approvals.set(approvalId, { ...active, timer });
39
+ return;
40
+ }
41
+ }
42
+ approvals.delete(approvalId);
43
+ this.options.emitRun(active.runId, 'approval.expired', { approvalId });
44
+ resolve(denyPermission('APPROVAL_EXPIRED'));
45
+ };
46
+ const timer = setTimeout(expire, this.options.approvalTimeoutMs);
47
+ this.options.claude().execution.approvals.set(approvalId, {
48
+ runId,
49
+ timer,
50
+ resolve: settle
51
+ });
52
+ this.options.publishApproval({
53
+ runId,
54
+ approvalId,
55
+ payload: { toolName, input: toolInput },
56
+ expiresAt
57
+ });
58
+ });
59
+ }
60
+ requestClaudeUserInput(runId, toolInput, toolUseId) {
61
+ const questions = claudeUserInputQuestions(toolInput);
62
+ if (questions.length === 0)
63
+ return Promise.resolve(denyPermission('USER_INPUT_INVALID'));
64
+ const requestId = `claude:${runId}:${toolUseId}`;
65
+ return new Promise((resolve) => {
66
+ const input = { ...toolInput };
67
+ const timer = setTimeout(() => {
68
+ const pending = this.options.claude().execution.userInputs.get(requestId);
69
+ if (pending === undefined)
70
+ return;
71
+ this.options.claude().execution.userInputs.delete(requestId);
72
+ pending.reject('USER_INPUT_EXPIRED');
73
+ this.options.emitItem(runId, {
74
+ itemId: boundedConversationItemId('claude-input', requestId),
75
+ kind: 'user_input_request',
76
+ status: 'FAILED',
77
+ payload: { requestId, questions },
78
+ merge: true
79
+ });
80
+ }, this.options.approvalTimeoutMs);
81
+ this.options.claude().execution.userInputs.set(requestId, {
82
+ runId,
83
+ requestId,
84
+ questions,
85
+ input,
86
+ resolve: (answers) => resolve({ behavior: 'allow', updatedInput: { ...input, answers } }),
87
+ reject: (message = 'USER_INPUT_EXPIRED') => resolve(denyPermission(message)),
88
+ timer
89
+ });
90
+ this.options.emitItem(runId, {
91
+ itemId: boundedConversationItemId('claude-input', requestId),
92
+ kind: 'user_input_request',
93
+ status: 'PENDING',
94
+ payload: { requestId, questions }
95
+ });
96
+ });
97
+ }
98
+ deliverChannel(runner, sessionId, messageId, senderId, token, content) {
99
+ return this.options.runners
100
+ .require(runner)
101
+ .deliverChannel(sessionId, messageId, senderId, token, content, (runId, deliveredMessageId, delivery) => this.options.emitRun(runId, 'channel.delivery', {
102
+ messageId: deliveredMessageId,
103
+ delivery
104
+ }));
105
+ }
106
+ decideCodexApproval(approvalId, decision) {
107
+ const codex = this.options.codex();
108
+ const requestId = codex.execution.approvals.get(approvalId);
109
+ if (requestId === undefined)
110
+ return false;
111
+ try {
112
+ codex.respond(requestId, { decision: decision === 'APPROVED' ? 'accept' : 'decline' });
113
+ }
114
+ catch {
115
+ // A disconnected App Server cannot safely receive a late approval.
116
+ }
117
+ codex.execution.approvals.delete(approvalId);
118
+ return true;
119
+ }
120
+ decideClaudeApproval(approvalId, decision) {
121
+ const approvals = this.options.claude().execution.approvals;
122
+ const approval = approvals.get(approvalId);
123
+ if (approval === undefined)
124
+ return false;
125
+ clearTimeout(approval.timer);
126
+ approvals.delete(approvalId);
127
+ approval.resolve(decision);
128
+ return true;
129
+ }
130
+ cancelClaudeRun(runId, intent) {
131
+ const claudeRun = this.options.claude().execution.runs.get(runId);
132
+ if (claudeRun === undefined)
133
+ return false;
134
+ if (intent === 'CANCEL')
135
+ claudeRun.handle.cancel();
136
+ else
137
+ void claudeRun.handle.interrupt().then(() => {
138
+ const run = this.options.runtime.getRun(runId);
139
+ if (run !== undefined && !isTerminalRunStatus(run.status))
140
+ this.options.emitRun(runId, 'run.completed', { status: 'INTERRUPTED' }, 'INTERRUPTED');
141
+ }, () => {
142
+ const run = this.options.runtime.getRun(runId);
143
+ if (run !== undefined && !isTerminalRunStatus(run.status))
144
+ this.options.emitRun(runId, 'run.failed', { code: 'CLAUDE_INTERRUPT_FAILED' }, 'FAILED');
145
+ });
146
+ return true;
147
+ }
148
+ async cancelCodexTurn(runId, threadId, turnId, cwd, terminalStatus) {
149
+ const codex = this.options.codex();
150
+ try {
151
+ await codex.interruptTurn(threadId, turnId);
152
+ const run = this.options.runtime.getRun(runId);
153
+ if (run !== undefined && !isTerminalRunStatus(run.status))
154
+ this.options.emitRun(runId, 'run.completed', { status: terminalStatus }, terminalStatus);
155
+ }
156
+ catch {
157
+ const run = this.options.runtime.getRun(runId);
158
+ if (run !== undefined && !isTerminalRunStatus(run.status))
159
+ this.options.emitRun(runId, 'run.failed', { code: 'CODEX_INTERRUPT_FAILED' }, 'FAILED');
160
+ }
161
+ finally {
162
+ codex.execution.runs.delete(runId);
163
+ codex.releaseEnvironment(runId);
164
+ this.options.state.active.delete(runId);
165
+ void this.options.captureSnapshot(runId, cwd);
166
+ }
167
+ }
168
+ respondUserInput(runId, requestId, rawAnswers) {
169
+ if (this.options.runners.respondUserInput(runId, requestId, rawAnswers))
170
+ return;
171
+ if (this.options.codex().execution.userInputs.has(requestId)) {
172
+ this.respondCodexUserInput(runId, requestId, rawAnswers);
173
+ return;
174
+ }
175
+ const userInputs = this.options.claude().execution.userInputs;
176
+ const pending = userInputs.get(requestId);
177
+ if (pending === undefined || pending.runId !== runId)
178
+ throw new Error('USER_INPUT_NOT_FOUND');
179
+ const answers = {};
180
+ for (const question of pending.questions) {
181
+ const normalized = this.answers(rawAnswers[question.id], question.allowOther, (value) => question.options.some((option) => option.value === value));
182
+ if (!question.multiSelect && normalized.length !== 1)
183
+ throw new Error('USER_INPUT_INVALID');
184
+ answers[question.prompt] = normalized.join(', ');
185
+ }
186
+ clearTimeout(pending.timer);
187
+ userInputs.delete(requestId);
188
+ pending.resolve(answers);
189
+ this.options.emitItem(runId, {
190
+ itemId: boundedConversationItemId('claude-input', requestId),
191
+ kind: 'user_input_request',
192
+ status: 'COMPLETED',
193
+ payload: { requestId, questions: pending.questions },
194
+ merge: true
195
+ });
196
+ }
197
+ completeCancelledRun(runId, cwd) {
198
+ const run = this.options.runtime.getRun(runId);
199
+ if (run === undefined)
200
+ return false;
201
+ if (run.status === 'CANCELLING') {
202
+ const status = this.options.state.interruptRequested.has(runId) ? 'INTERRUPTED' : 'CANCELLED';
203
+ this.options.emitRun(runId, 'run.completed', { status }, status);
204
+ }
205
+ else if (!isTerminalRunStatus(run.status))
206
+ return false;
207
+ this.options.codex().execution.runs.delete(runId);
208
+ this.options.state.active.delete(runId);
209
+ if (cwd !== undefined)
210
+ void this.options.captureSnapshot(runId, cwd);
211
+ return true;
212
+ }
213
+ respondCodexUserInput(runId, requestId, rawAnswers) {
214
+ const codex = this.options.codex();
215
+ const pending = codex.execution.userInputs.get(requestId);
216
+ if (pending === undefined ||
217
+ pending.runId !== runId ||
218
+ this.options.runtime.getRun(runId) === undefined)
219
+ throw new Error('USER_INPUT_NOT_FOUND');
220
+ const answers = {};
221
+ for (const question of pending.questions)
222
+ answers[question.id] = {
223
+ answers: this.answers(rawAnswers[question.id], question.allowOther, (value) => question.options.includes(value))
224
+ };
225
+ codex.respond(pending.requestId, { answers });
226
+ codex.execution.userInputs.delete(requestId);
227
+ this.options.emitItem(runId, {
228
+ itemId: boundedConversationItemId('codex-input', requestId),
229
+ kind: 'user_input_request',
230
+ status: 'COMPLETED',
231
+ payload: { requestId, questions: [] },
232
+ merge: true
233
+ });
234
+ }
235
+ answers(raw, allowOther, supported) {
236
+ if (!Array.isArray(raw) || raw.length === 0 || raw.length > 8)
237
+ throw new Error('USER_INPUT_INVALID');
238
+ return raw.map((value) => {
239
+ if (typeof value !== 'string' || value.length === 0 || value.length > 500)
240
+ throw new Error('USER_INPUT_INVALID');
241
+ if (!allowOther && !supported(value))
242
+ throw new Error('USER_INPUT_INVALID');
243
+ return value;
244
+ });
245
+ }
246
+ }
@@ -0,0 +1,44 @@
1
+ import { type NodeCapabilities } from '@myagentroam/protocol';
2
+ import type { NodeDatabase } from '../database.js';
3
+ import type { NodeOperationHandler } from '../connector/node-operation-router.js';
4
+ import type { RunnerRegistry } from '../runner/runner-registry.js';
5
+ import type { RunnerUsageReader } from '../runner-usage.js';
6
+ export declare class RunnerService {
7
+ private readonly database;
8
+ private readonly capabilities;
9
+ private readonly runners;
10
+ private readonly usage;
11
+ constructor(database: () => NodeDatabase, capabilities: () => NodeCapabilities, runners: RunnerRegistry, usage: RunnerUsageReader);
12
+ operations(): Readonly<Record<string, NodeOperationHandler>>;
13
+ profiles(): readonly {
14
+ runner: "codex" | "claude-code" | "opencode";
15
+ capabilities: string[];
16
+ models: {
17
+ id: string;
18
+ label: string;
19
+ supportedEfforts: string[];
20
+ }[];
21
+ accessOptions: {
22
+ id: string;
23
+ label: string;
24
+ description: string;
25
+ }[];
26
+ commands: {
27
+ id: string;
28
+ available: boolean;
29
+ runner: "codex" | "claude-code" | "opencode";
30
+ reasonCode: string | null;
31
+ label: string;
32
+ description: string;
33
+ inputHint: string;
34
+ source: "CODEX_APP_SERVER" | "CLAUDE_AGENT_SDK" | "OPENCODE_SERVER";
35
+ interaction: "RUNNER_TEXT" | "IMMEDIATE_ACTION" | "TOGGLE" | "VALUE";
36
+ stateLabel?: string | undefined;
37
+ }[];
38
+ }[];
39
+ private refreshProfiles;
40
+ private usageStatuses;
41
+ private readUsage;
42
+ private listProfiles;
43
+ private saveDefaults;
44
+ }
@@ -0,0 +1,68 @@
1
+ import { runnerDefaultConfigurationSchema } from '@myagentroam/protocol';
2
+ import { parseSecretEnvironment } from '../util/secret-environment.js';
3
+ export class RunnerService {
4
+ database;
5
+ capabilities;
6
+ runners;
7
+ usage;
8
+ constructor(database, capabilities, runners, usage) {
9
+ this.database = database;
10
+ this.capabilities = capabilities;
11
+ this.runners = runners;
12
+ this.usage = usage;
13
+ }
14
+ operations() {
15
+ return {
16
+ 'runner.usage.status': () => this.usageStatuses(),
17
+ 'runner.usage.read': (data) => this.readUsage(data),
18
+ 'runner.profiles.list': (data) => this.listProfiles(data),
19
+ 'runner.defaults.save': (data) => this.saveDefaults(data)
20
+ };
21
+ }
22
+ profiles() {
23
+ return this.runners.profiles(this.capabilities());
24
+ }
25
+ refreshProfiles(workspaceId, environment = {}) {
26
+ const workspace = workspaceId === undefined ? undefined : this.database().getWorkspace(workspaceId);
27
+ if (workspaceId !== undefined && workspace === undefined)
28
+ throw new Error('WORKSPACE_NOT_FOUND');
29
+ return this.runners.refreshProfiles(this.capabilities(), workspace, environment);
30
+ }
31
+ async usageStatuses() {
32
+ return {
33
+ statuses: await Promise.all(this.runners
34
+ .advertised()
35
+ .flatMap((runner) => runner.name === 'codex' || runner.name === 'claude-code'
36
+ ? [this.usage.status(runner.name)]
37
+ : []))
38
+ };
39
+ }
40
+ async readUsage(data) {
41
+ const input = data;
42
+ const runner = this.runners.product(input.runner);
43
+ if (runner === undefined || runner.name === 'opencode')
44
+ throw new Error('RUNNER_USAGE_INVALID');
45
+ return { usage: await this.usage.read(runner.name) };
46
+ }
47
+ async listProfiles(data) {
48
+ const input = data;
49
+ const workspaceId = typeof input.workspaceId === 'string' && input.workspaceId.length > 0
50
+ ? input.workspaceId
51
+ : undefined;
52
+ const environment = parseSecretEnvironment(input.secretEnvironment);
53
+ return {
54
+ profiles: await this.refreshProfiles(workspaceId, environment),
55
+ defaults: this.database()
56
+ .getRunnerDefaults()
57
+ .filter((value) => this.runners.supportsConfiguration(value))
58
+ };
59
+ }
60
+ saveDefaults(data) {
61
+ const parsed = runnerDefaultConfigurationSchema.safeParse(data);
62
+ if (!parsed.success)
63
+ throw new Error('RUNNER_DEFAULT_INVALID');
64
+ if (!this.runners.supportsConfiguration(parsed.data))
65
+ throw new Error('RUNNER_CONFIGURATION_UNSUPPORTED');
66
+ return { defaults: this.database().saveRunnerDefaults(parsed.data) };
67
+ }
68
+ }
@@ -0,0 +1,45 @@
1
+ import type { NodeOperationHandler } from '../connector/node-operation-router.js';
2
+ import type { NodeAgentSession, NodeConversationTurn, NodeDatabase } from '../database.js';
3
+ import type { NodeRuntimeState } from '../runtime-state.js';
4
+ import type { ExternalSessionResumeService } from './external-session-resume-service.js';
5
+ import type { NativeSessionProjectionService } from './native-session-projection-service.js';
6
+ import type { RunnerRegistry } from '../runner/runner-registry.js';
7
+ interface SessionCatalogServiceOptions {
8
+ readonly database: () => NodeDatabase;
9
+ readonly nodeId: () => string;
10
+ readonly runtime: NodeRuntimeState;
11
+ readonly runners: RunnerRegistry;
12
+ readonly projection: NativeSessionProjectionService;
13
+ readonly resume: ExternalSessionResumeService;
14
+ readonly cachedNative: () => readonly NodeAgentSession[];
15
+ readonly cachedWorkspace: (workspaceId: string) => Promise<readonly NodeAgentSession[]>;
16
+ readonly present: (session: NodeAgentSession) => NodeAgentSession;
17
+ readonly projectionForNative: (session: NodeAgentSession) => NodeAgentSession | undefined;
18
+ readonly nativeIds: (session: NodeAgentSession) => ReadonlySet<string>;
19
+ readonly forget: (session: NodeAgentSession, nativeIds: ReadonlySet<string>) => void;
20
+ readonly refreshActivity: (session: NodeAgentSession) => Promise<void>;
21
+ readonly externalActivity: (sessionId: string) => unknown;
22
+ readonly history: (session: NodeAgentSession, input: {
23
+ readonly cursor?: string;
24
+ readonly limit?: number;
25
+ }) => Promise<{
26
+ readonly turns: readonly NodeConversationTurn[];
27
+ readonly nextCursor: string | null;
28
+ }>;
29
+ readonly emitSession: (session: NodeAgentSession) => void;
30
+ }
31
+ export declare class SessionCatalogService {
32
+ private readonly options;
33
+ private rewindQueue;
34
+ constructor(options: SessionCatalogServiceOptions);
35
+ operations(): Readonly<Record<string, NodeOperationHandler>>;
36
+ private list;
37
+ private mergePersisted;
38
+ private resume;
39
+ private enqueueRewind;
40
+ private rewind;
41
+ private findTurn;
42
+ private remove;
43
+ }
44
+ export declare function rewindTitle(sourceTitle: string, existingTitles?: readonly string[]): string;
45
+ export {};