@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.
- package/README.md +9 -1
- package/dist/capabilities.d.ts +2 -0
- package/dist/capabilities.js +15 -4
- package/dist/claude-agent-sdk.d.ts +4 -16
- package/dist/claude-agent-sdk.js +18 -79
- package/dist/claude-channel.js +0 -1
- package/dist/codex-app-server.d.ts +5 -0
- package/dist/codex-app-server.js +26 -5
- package/dist/config.d.ts +3 -0
- package/dist/config.js +18 -2
- package/dist/connector/node-connector-options.d.ts +26 -0
- package/dist/connector/node-connector-options.js +1 -0
- package/dist/connector/node-operation-router.d.ts +9 -0
- package/dist/connector/node-operation-router.js +22 -0
- package/dist/connector.d.ts +62 -356
- package/dist/connector.js +714 -6176
- package/dist/control-outbound-scheduler.d.ts +26 -0
- package/dist/control-outbound-scheduler.js +121 -0
- package/dist/database.d.ts +4 -0
- package/dist/database.js +15 -10
- package/dist/event-buffer.js +0 -1
- package/dist/fake-runner.js +0 -1
- package/dist/health.js +0 -1
- package/dist/main.js +6 -1
- package/dist/migrations/v001.js +0 -1
- package/dist/migrations/v002.js +0 -1
- package/dist/migrations/v003.d.ts +4 -0
- package/dist/migrations/v003.js +44 -0
- package/dist/native-jsonl-reader.d.ts +14 -0
- package/dist/native-jsonl-reader.js +97 -0
- package/dist/native-session-history.d.ts +41 -4
- package/dist/native-session-history.js +340 -159
- package/dist/opencode-runtime.d.ts +33 -0
- package/dist/opencode-runtime.js +80 -0
- package/dist/opencode-server.d.ts +47 -0
- package/dist/opencode-server.js +250 -0
- package/dist/operational.js +10 -3
- package/dist/process-tree.js +0 -1
- package/dist/runner/abstract-runner.d.ts +153 -0
- package/dist/runner/abstract-runner.js +154 -0
- package/dist/runner/claude/managed-run-controller.d.ts +52 -0
- package/dist/runner/claude/managed-run-controller.js +326 -0
- package/dist/runner/claude-code-runner.d.ts +70 -0
- package/dist/runner/claude-code-runner.js +286 -0
- package/dist/runner/codex/conversation-parser.d.ts +59 -0
- package/dist/runner/codex/conversation-parser.js +816 -0
- package/dist/runner/codex/managed-run-controller.d.ts +80 -0
- package/dist/runner/codex/managed-run-controller.js +388 -0
- package/dist/runner/codex-runner.d.ts +85 -0
- package/dist/runner/codex-runner.js +365 -0
- package/dist/runner/fake-test-runner.d.ts +26 -0
- package/dist/runner/fake-test-runner.js +47 -0
- package/dist/runner/opencode/conversation-parser.d.ts +27 -0
- package/dist/runner/opencode/conversation-parser.js +217 -0
- package/dist/runner/opencode/managed-run-controller.d.ts +76 -0
- package/dist/runner/opencode/managed-run-controller.js +451 -0
- package/dist/runner/opencode-runner.d.ts +60 -0
- package/dist/runner/opencode-runner.js +330 -0
- package/dist/runner/runner-registry.d.ts +19 -0
- package/dist/runner/runner-registry.js +75 -0
- package/dist/runner-command-engine.js +0 -1
- package/dist/runner-profiles.d.ts +4 -0
- package/dist/runner-profiles.js +76 -3
- package/dist/runner-usage.d.ts +2 -2
- package/dist/runner-usage.js +3 -6
- package/dist/runtime-state.js +26 -14
- package/dist/service/attachment-domain-state.d.ts +5 -0
- package/dist/service/attachment-domain-state.js +5 -0
- package/dist/service/conversation-history-service.d.ts +34 -0
- package/dist/service/conversation-history-service.js +158 -0
- package/dist/service/external-session-resume-service.d.ts +32 -0
- package/dist/service/external-session-resume-service.js +98 -0
- package/dist/service/fake-managed-run-service.d.ts +29 -0
- package/dist/service/fake-managed-run-service.js +62 -0
- package/dist/service/managed-runner-session-service.d.ts +12 -0
- package/dist/service/managed-runner-session-service.js +35 -0
- package/dist/service/native-session-projection-service.d.ts +51 -0
- package/dist/service/native-session-projection-service.js +271 -0
- package/dist/service/native-session-watch-service.d.ts +37 -0
- package/dist/service/native-session-watch-service.js +150 -0
- package/dist/service/node-connection-lifecycle-service.d.ts +37 -0
- package/dist/service/node-connection-lifecycle-service.js +85 -0
- package/dist/service/node-control-channel.d.ts +32 -0
- package/dist/service/node-control-channel.js +85 -0
- package/dist/service/node-control-message-service.d.ts +28 -0
- package/dist/service/node-control-message-service.js +104 -0
- package/dist/service/node-request-service.d.ts +14 -0
- package/dist/service/node-request-service.js +50 -0
- package/dist/service/node-run-event-bridge.d.ts +40 -0
- package/dist/service/node-run-event-bridge.js +83 -0
- package/dist/service/node-terminal-channel.d.ts +29 -0
- package/dist/service/node-terminal-channel.js +182 -0
- package/dist/service/node-upgrade-coordinator.d.ts +29 -0
- package/dist/service/node-upgrade-coordinator.js +137 -0
- package/dist/service/node-workspace-coordinator.d.ts +32 -0
- package/dist/service/node-workspace-coordinator.js +80 -0
- package/dist/service/run-attachment-service.d.ts +37 -0
- package/dist/service/run-attachment-service.js +160 -0
- package/dist/service/run-domain-state.d.ts +4 -0
- package/dist/service/run-domain-state.js +4 -0
- package/dist/service/run-event-service.d.ts +31 -0
- package/dist/service/run-event-service.js +102 -0
- package/dist/service/run-workbench-service.d.ts +27 -0
- package/dist/service/run-workbench-service.js +113 -0
- package/dist/service/runner-channel-message-service.d.ts +13 -0
- package/dist/service/runner-channel-message-service.js +20 -0
- package/dist/service/runner-interaction-service.d.ts +40 -0
- package/dist/service/runner-interaction-service.js +246 -0
- package/dist/service/runner-service.d.ts +44 -0
- package/dist/service/runner-service.js +68 -0
- package/dist/service/session-catalog-service.d.ts +45 -0
- package/dist/service/session-catalog-service.js +224 -0
- package/dist/service/session-command-service.d.ts +23 -0
- package/dist/service/session-command-service.js +69 -0
- package/dist/service/session-context-service.d.ts +34 -0
- package/dist/service/session-context-service.js +87 -0
- package/dist/service/session-domain-state.d.ts +8 -0
- package/dist/service/session-domain-state.js +8 -0
- package/dist/service/session-identity-service.d.ts +24 -0
- package/dist/service/session-identity-service.js +86 -0
- package/dist/service/session-lifecycle-service.d.ts +31 -0
- package/dist/service/session-lifecycle-service.js +242 -0
- package/dist/service/session-message-service.d.ts +33 -0
- package/dist/service/session-message-service.js +157 -0
- package/dist/service/session-presentation-service.d.ts +33 -0
- package/dist/service/session-presentation-service.js +36 -0
- package/dist/service/session-query-service.d.ts +31 -0
- package/dist/service/session-query-service.js +77 -0
- package/dist/service/terminal-relay-state.d.ts +6 -0
- package/dist/service/terminal-relay-state.js +3 -0
- package/dist/service/terminal-service.d.ts +12 -0
- package/dist/service/terminal-service.js +52 -0
- package/dist/service/workbench-event-service.d.ts +21 -0
- package/dist/service/workbench-event-service.js +53 -0
- package/dist/service/workbench-manifest-service.d.ts +89 -0
- package/dist/service/workbench-manifest-service.js +70 -0
- package/dist/service/workspace-change-service.d.ts +9 -0
- package/dist/service/workspace-change-service.js +86 -0
- package/dist/service/workspace-content-search-service.d.ts +14 -0
- package/dist/service/workspace-content-search-service.js +477 -0
- package/dist/service/workspace-domain-state.d.ts +75 -0
- package/dist/service/workspace-domain-state.js +40 -0
- package/dist/service/workspace-file-service.d.ts +17 -0
- package/dist/service/workspace-file-service.js +109 -0
- package/dist/service/workspace-queue-workbench-service.d.ts +55 -0
- package/dist/service/workspace-queue-workbench-service.js +226 -0
- package/dist/service/workspace-service.d.ts +15 -0
- package/dist/service/workspace-service.js +67 -0
- package/dist/service/workspace-session-service.d.ts +23 -0
- package/dist/service/workspace-session-service.js +139 -0
- package/dist/service/workspace-upload-service.d.ts +37 -0
- package/dist/service/workspace-upload-service.js +240 -0
- package/dist/service/workspace-watch-service.d.ts +18 -0
- package/dist/service/workspace-watch-service.js +120 -0
- package/dist/service/workspace-workbench-service.d.ts +47 -0
- package/dist/service/workspace-workbench-service.js +223 -0
- package/dist/service.js +2 -3
- package/dist/storage.js +0 -1
- package/dist/supervisor.d.ts +1 -0
- package/dist/supervisor.js +154 -0
- package/dist/terminal.d.ts +1 -0
- package/dist/terminal.js +1 -2
- package/dist/util/node-operation-parsers.d.ts +92 -0
- package/dist/util/node-operation-parsers.js +382 -0
- package/dist/util/runner-native-session-parsers.d.ts +72 -0
- package/dist/util/runner-native-session-parsers.js +381 -0
- package/dist/util/secret-environment.d.ts +1 -0
- package/dist/util/secret-environment.js +25 -0
- package/dist/workspace.d.ts +62 -2
- package/dist/workspace.js +539 -57
- package/package.json +3 -2
- package/dist/capabilities.js.map +0 -1
- package/dist/claude-agent-sdk.js.map +0 -1
- package/dist/claude-channel.js.map +0 -1
- package/dist/codex-app-server.js.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/connector.js.map +0 -1
- package/dist/database.js.map +0 -1
- package/dist/event-buffer.js.map +0 -1
- package/dist/fake-runner.js.map +0 -1
- package/dist/health.js.map +0 -1
- package/dist/main.js.map +0 -1
- package/dist/migrations/v001.js.map +0 -1
- package/dist/migrations/v002.js.map +0 -1
- package/dist/native-session-history.js.map +0 -1
- package/dist/operational.js.map +0 -1
- package/dist/process-tree.js.map +0 -1
- package/dist/runner-command-engine.js.map +0 -1
- package/dist/runner-profiles.js.map +0 -1
- package/dist/runner-usage.js.map +0 -1
- package/dist/runtime-state.js.map +0 -1
- package/dist/service.js.map +0 -1
- package/dist/storage.js.map +0 -1
- package/dist/terminal.js.map +0 -1
- 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 {};
|