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