@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,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable lifecycle boundary for every Node Runner implementation.
|
|
3
|
+
*
|
|
4
|
+
* Vendor clients and payload decoders stay behind subclasses. Domain services
|
|
5
|
+
* depend on this boundary (or a capability interface implemented by it), never
|
|
6
|
+
* on an SDK/App Server client directly.
|
|
7
|
+
*/
|
|
8
|
+
export class AbstractRunner {
|
|
9
|
+
control;
|
|
10
|
+
commandControl;
|
|
11
|
+
/** Test-only runners are registered explicitly but never advertised. */
|
|
12
|
+
advertised = true;
|
|
13
|
+
interceptsRuns = false;
|
|
14
|
+
async refreshProfile(capabilities, workspace, environment = {}) {
|
|
15
|
+
void [workspace, environment];
|
|
16
|
+
return this.profile(capabilities);
|
|
17
|
+
}
|
|
18
|
+
supportsConfiguration(configuration, workspace) {
|
|
19
|
+
void workspace;
|
|
20
|
+
const profile = this.profileForValidation();
|
|
21
|
+
const model = profile.models.find((candidate) => candidate.id === configuration.model);
|
|
22
|
+
return (configuration.runner === this.name &&
|
|
23
|
+
model !== undefined &&
|
|
24
|
+
model.supportedEfforts.includes(configuration.effort) &&
|
|
25
|
+
profile.accessOptions.some((candidate) => candidate.id === configuration.access));
|
|
26
|
+
}
|
|
27
|
+
presentSession(session, capabilities, context) {
|
|
28
|
+
void [session, capabilities, context];
|
|
29
|
+
throw new RunnerCapabilityError(this.name, 'session.presentation');
|
|
30
|
+
}
|
|
31
|
+
prepareMessageInput(input, collaborationMode) {
|
|
32
|
+
void collaborationMode;
|
|
33
|
+
return input;
|
|
34
|
+
}
|
|
35
|
+
channelToken(sessionId) {
|
|
36
|
+
void sessionId;
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
deliverChannel(sessionId, messageId, senderId, token, content, notify) {
|
|
40
|
+
void [sessionId, messageId, senderId, token, content, notify];
|
|
41
|
+
return 'REJECTED';
|
|
42
|
+
}
|
|
43
|
+
executeGlobalCommand(commandId, input) {
|
|
44
|
+
void [commandId, input];
|
|
45
|
+
throw new RunnerCapabilityError(this.name, 'global-command');
|
|
46
|
+
}
|
|
47
|
+
serviceTier() {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
usesRunnerTitleForRename() {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
async renameNative(session, input, database) {
|
|
54
|
+
if (session.externalSessionId === null)
|
|
55
|
+
return;
|
|
56
|
+
database.saveNativeSessionMetadata({
|
|
57
|
+
runner: session.runner,
|
|
58
|
+
externalSessionId: session.externalSessionId,
|
|
59
|
+
workspaceId: session.workspaceId,
|
|
60
|
+
...(input.title === undefined ? {} : { customTitle: input.title }),
|
|
61
|
+
...(input.pinned === undefined ? {} : { pinned: input.pinned })
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async removeNative(session, nativeSessionId) {
|
|
65
|
+
void [session, nativeSessionId];
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
async forkNative(session, boundary, title) {
|
|
69
|
+
void [session, boundary, title];
|
|
70
|
+
throw new RunnerCapabilityError(this.name, 'session.rewind');
|
|
71
|
+
}
|
|
72
|
+
projectedRunnerTitle(metadata, input, database) {
|
|
73
|
+
void database;
|
|
74
|
+
return input.title ?? metadata?.runnerTitle ?? null;
|
|
75
|
+
}
|
|
76
|
+
managedAliasId(nativeSessionId) {
|
|
77
|
+
void nativeSessionId;
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
nativeAliasesFor(managedSessionId) {
|
|
81
|
+
void managedSessionId;
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
forgetNativeAlias(nativeSessionId) {
|
|
85
|
+
void nativeSessionId;
|
|
86
|
+
}
|
|
87
|
+
rememberManagedNative(session, nativeSessionId) {
|
|
88
|
+
void [session, nativeSessionId];
|
|
89
|
+
}
|
|
90
|
+
async readManagedRunnerTitle(session, nativeSessionId) {
|
|
91
|
+
void [session, nativeSessionId];
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
async readOfficialConversation(session, input, managedTurn) {
|
|
95
|
+
void [session, input, managedTurn];
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
async readExternalActivity(session) {
|
|
99
|
+
void session;
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
acceptsTruncatedNativeHistory() {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
nativeImageRoot(session) {
|
|
106
|
+
void session;
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
managedRunForNativeTurn(nativeTurnId) {
|
|
110
|
+
void nativeTurnId;
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
bindControl(control) {
|
|
114
|
+
if (this.control !== undefined)
|
|
115
|
+
throw new Error(`RUNNER_CONTROL_ALREADY_BOUND:${this.name}`);
|
|
116
|
+
this.control = control;
|
|
117
|
+
}
|
|
118
|
+
acceptsRun(runner) {
|
|
119
|
+
return this.interceptsRuns || runner === this.name;
|
|
120
|
+
}
|
|
121
|
+
startManagedRun(envelope) {
|
|
122
|
+
if (this.control === undefined)
|
|
123
|
+
throw new Error(`RUNNER_CONTROL_NOT_BOUND:${this.name}`);
|
|
124
|
+
this.control.start(envelope);
|
|
125
|
+
}
|
|
126
|
+
cancelManagedRun(runId, intent) {
|
|
127
|
+
return this.control?.cancel(runId, intent) === true;
|
|
128
|
+
}
|
|
129
|
+
decideApproval(input) {
|
|
130
|
+
return this.control?.decideApproval?.(input) === true;
|
|
131
|
+
}
|
|
132
|
+
respondUserInput(runId, requestId, answers) {
|
|
133
|
+
return this.control?.respondUserInput?.(runId, requestId, answers) === true;
|
|
134
|
+
}
|
|
135
|
+
bindCommandControl(control) {
|
|
136
|
+
if (this.commandControl !== undefined)
|
|
137
|
+
throw new Error(`RUNNER_COMMAND_CONTROL_ALREADY_BOUND:${this.name}`);
|
|
138
|
+
this.commandControl = control;
|
|
139
|
+
}
|
|
140
|
+
executeCommand(session, command, input, context) {
|
|
141
|
+
if (this.commandControl === undefined)
|
|
142
|
+
return Promise.reject(new Error('RUNNER_COMMAND_UNAVAILABLE'));
|
|
143
|
+
return this.commandControl.execute(session, command, input, context);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
export class RunnerCapabilityError extends Error {
|
|
147
|
+
runner;
|
|
148
|
+
capability;
|
|
149
|
+
constructor(runner, capability) {
|
|
150
|
+
super('RUNNER_CAPABILITY_UNSUPPORTED');
|
|
151
|
+
this.runner = runner;
|
|
152
|
+
this.capability = capability;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { NodeEnvelope, RunnerCommandDescriptor, RunnerCommandState } from '@myagentroam/protocol';
|
|
2
|
+
import { denyPermission } from '../../claude-agent-sdk.js';
|
|
3
|
+
import type { NodeAgentSession } from '../../database.js';
|
|
4
|
+
import type { FakeRunnerStatus } from '../../fake-runner.js';
|
|
5
|
+
import { type PublicConversationItem } from '../codex/conversation-parser.js';
|
|
6
|
+
import { ClaudeCodeRunner } from '../claude-code-runner.js';
|
|
7
|
+
export interface ClaudeRunnerImageAttachment {
|
|
8
|
+
readonly mime: 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp';
|
|
9
|
+
readonly dataBase64: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ClaudeManagedRunHost<TAttachment extends ClaudeRunnerImageAttachment> {
|
|
12
|
+
intercepted(): boolean;
|
|
13
|
+
readonly active: Set<string>;
|
|
14
|
+
parseAttachments(value: unknown): readonly TAttachment[];
|
|
15
|
+
emit(runId: string, eventType: string, payload: unknown, status?: FakeRunnerStatus): void;
|
|
16
|
+
adopt(input: {
|
|
17
|
+
runId: string;
|
|
18
|
+
sessionId: string;
|
|
19
|
+
runner: 'claude-code';
|
|
20
|
+
cwd: string;
|
|
21
|
+
externalSessionId?: string;
|
|
22
|
+
}): void;
|
|
23
|
+
run(runId: string): {
|
|
24
|
+
readonly status: string;
|
|
25
|
+
} | undefined;
|
|
26
|
+
hasActiveSessionLease(sessionId: string): boolean;
|
|
27
|
+
session(sessionId: string): NodeAgentSession | undefined;
|
|
28
|
+
completeCancelled(runId: string, cwd: string): boolean;
|
|
29
|
+
captureSnapshot(runId: string, cwd: string): Promise<void>;
|
|
30
|
+
requestUserInput(runId: string, input: Record<string, unknown>, toolUseId: string): Promise<ReturnType<typeof denyPermission>>;
|
|
31
|
+
requestApproval(runId: string, toolName: string, input: Record<string, unknown>): Promise<ReturnType<typeof denyPermission>>;
|
|
32
|
+
rememberNative(nativeSessionId: string, sessionId: string): void;
|
|
33
|
+
promote(sessionId: string, nativeSessionId: string): void;
|
|
34
|
+
send(type: string, payload: unknown): void;
|
|
35
|
+
emitItem(runId: string, item: PublicConversationItem): void;
|
|
36
|
+
syncTitle(sessionId: string, nativeSessionId: string, cwd: string): Promise<void>;
|
|
37
|
+
setChannelToken(sessionId: string, token: string): void;
|
|
38
|
+
commandState(sessionId: string, commandId: string): RunnerCommandState | undefined;
|
|
39
|
+
setCommandState(sessionId: string, state: RunnerCommandState): void;
|
|
40
|
+
clearCommandState(sessionId: string, commandId: string): void;
|
|
41
|
+
commandStates(sessionId: string): readonly RunnerCommandState[];
|
|
42
|
+
}
|
|
43
|
+
export declare class ClaudeManagedRunController<TAttachment extends ClaudeRunnerImageAttachment> {
|
|
44
|
+
private readonly runner;
|
|
45
|
+
private readonly host;
|
|
46
|
+
constructor(runner: ClaudeCodeRunner, host: ClaudeManagedRunHost<TAttachment>);
|
|
47
|
+
executeCommand(session: NodeAgentSession, command: RunnerCommandDescriptor, input: string, context: {
|
|
48
|
+
readonly secretEnvironment: Readonly<Record<string, string>>;
|
|
49
|
+
}): Promise<unknown>;
|
|
50
|
+
private compact;
|
|
51
|
+
start(envelope: NodeEnvelope): void;
|
|
52
|
+
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { createEnvelope } from '@myagentroam/protocol';
|
|
2
|
+
import { ClaudeChannelGate } from '../../claude-channel.js';
|
|
3
|
+
import { boundedConversationItemId, claudePlanText, compactRunnerText, compactRunnerValue, extractClaudeText, extractClaudeThinkingSummaries, extractClaudeToolResults, extractClaudeToolUses, isClaudeCommandTool } from '../codex/conversation-parser.js';
|
|
4
|
+
import { isTerminalRunStatus } from '../../util/node-operation-parsers.js';
|
|
5
|
+
import { claudeContentBlocks } from '../../util/node-operation-parsers.js';
|
|
6
|
+
import { parseSecretEnvironment } from '../../util/secret-environment.js';
|
|
7
|
+
export class ClaudeManagedRunController {
|
|
8
|
+
runner;
|
|
9
|
+
host;
|
|
10
|
+
constructor(runner, host) {
|
|
11
|
+
this.runner = runner;
|
|
12
|
+
this.host = host;
|
|
13
|
+
}
|
|
14
|
+
async executeCommand(session, command, input, context) {
|
|
15
|
+
if (command.id === 'compact')
|
|
16
|
+
return this.compact(session, input, context.secretEnvironment);
|
|
17
|
+
if (command.id !== 'plan' || session.access === 'plan')
|
|
18
|
+
throw new Error('RUNNER_COMMAND_UNAVAILABLE');
|
|
19
|
+
const current = this.host.commandState(session.id, 'plan');
|
|
20
|
+
if (current?.active)
|
|
21
|
+
this.host.clearCommandState(session.id, 'plan');
|
|
22
|
+
else
|
|
23
|
+
this.host.setCommandState(session.id, {
|
|
24
|
+
commandId: 'plan',
|
|
25
|
+
active: true,
|
|
26
|
+
label: 'Plan Mode',
|
|
27
|
+
detail: '后续消息将以标签式规划模式运行,只读规划并输出计划卡。',
|
|
28
|
+
closable: true,
|
|
29
|
+
closeInput: '/plan',
|
|
30
|
+
continuation: { label: 'Implement', prompt: '请根据上述计划开始实施。' }
|
|
31
|
+
});
|
|
32
|
+
return { command: command.id, states: this.host.commandStates(session.id) };
|
|
33
|
+
}
|
|
34
|
+
compact(session, input, secretEnvironment) {
|
|
35
|
+
if (session.externalSessionId === null ||
|
|
36
|
+
session.nativeControl !== 'MAR_MANAGED' ||
|
|
37
|
+
this.host.hasActiveSessionLease(session.id))
|
|
38
|
+
throw new Error('RUNNER_COMMAND_UNAVAILABLE');
|
|
39
|
+
const runId = crypto.randomUUID();
|
|
40
|
+
this.host.adopt({
|
|
41
|
+
runId,
|
|
42
|
+
sessionId: session.id,
|
|
43
|
+
runner: 'claude-code',
|
|
44
|
+
cwd: session.cwd,
|
|
45
|
+
externalSessionId: session.externalSessionId
|
|
46
|
+
});
|
|
47
|
+
this.host.rememberNative(session.externalSessionId, session.id);
|
|
48
|
+
this.start(createEnvelope('run.start', {
|
|
49
|
+
runId,
|
|
50
|
+
sessionId: session.id,
|
|
51
|
+
runner: 'claude-code',
|
|
52
|
+
input,
|
|
53
|
+
cwd: session.cwd,
|
|
54
|
+
externalSessionId: session.externalSessionId,
|
|
55
|
+
model: session.model,
|
|
56
|
+
effort: session.effort,
|
|
57
|
+
access: session.access,
|
|
58
|
+
secretEnvironment
|
|
59
|
+
}));
|
|
60
|
+
return {
|
|
61
|
+
command: 'compact',
|
|
62
|
+
run: this.host.run(runId),
|
|
63
|
+
states: this.host.commandStates(session.id)
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
start(envelope) {
|
|
67
|
+
const payload = envelope.payload;
|
|
68
|
+
if (payload.runner !== 'claude-code' ||
|
|
69
|
+
typeof payload.runId !== 'string' ||
|
|
70
|
+
typeof payload.sessionId !== 'string' ||
|
|
71
|
+
typeof payload.input !== 'string' ||
|
|
72
|
+
typeof payload.cwd !== 'string' ||
|
|
73
|
+
this.host.intercepted() ||
|
|
74
|
+
this.host.active.has(payload.runId))
|
|
75
|
+
return;
|
|
76
|
+
const runId = payload.runId;
|
|
77
|
+
const sessionId = payload.sessionId;
|
|
78
|
+
const cwd = payload.cwd;
|
|
79
|
+
let attachments;
|
|
80
|
+
let secretEnvironment;
|
|
81
|
+
try {
|
|
82
|
+
attachments = this.host.parseAttachments(payload.attachments);
|
|
83
|
+
secretEnvironment = parseSecretEnvironment(payload.secretEnvironment);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
this.host.emit(runId, 'run.rejected', { code: error instanceof Error ? error.message : 'MESSAGE_ATTACHMENTS_INVALID' }, 'FAILED');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
this.host.adopt({
|
|
90
|
+
runId,
|
|
91
|
+
sessionId,
|
|
92
|
+
runner: 'claude-code',
|
|
93
|
+
cwd,
|
|
94
|
+
...(typeof payload.externalSessionId === 'string'
|
|
95
|
+
? { externalSessionId: payload.externalSessionId }
|
|
96
|
+
: {})
|
|
97
|
+
});
|
|
98
|
+
// An insert request may interrupt a Claude Run while its SDK Channel is
|
|
99
|
+
// still starting. Do not start that stale Channel after the replacement
|
|
100
|
+
// message has been accepted; emit its terminal projection to release the
|
|
101
|
+
// replacement queued behind it.
|
|
102
|
+
if (this.host.run(runId)?.status === 'INTERRUPTED') {
|
|
103
|
+
this.host.emit(runId, 'run.completed', { status: 'INTERRUPTED' }, 'INTERRUPTED');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (this.host.completeCancelled(runId, cwd))
|
|
107
|
+
return;
|
|
108
|
+
if (typeof payload.externalSessionId === 'string' &&
|
|
109
|
+
!this.runner.execution.managedNativeIds.has(payload.externalSessionId)) {
|
|
110
|
+
this.host.emit(runId, 'run.rejected', { code: 'EXTERNAL_SESSION_OBSERVE_ONLY' }, 'FAILED');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.host.active.add(runId);
|
|
114
|
+
this.host.emit(runId, 'run.started', {}, 'STARTING');
|
|
115
|
+
let terminal = false;
|
|
116
|
+
let handle;
|
|
117
|
+
const toolKinds = new Map();
|
|
118
|
+
const finish = (status, eventType, eventPayload) => {
|
|
119
|
+
if (terminal)
|
|
120
|
+
return;
|
|
121
|
+
terminal = true;
|
|
122
|
+
this.host.emit(runId, eventType, eventPayload, status);
|
|
123
|
+
this.runner.execution.runs.delete(runId);
|
|
124
|
+
handle?.cancel();
|
|
125
|
+
this.host.active.delete(runId);
|
|
126
|
+
if (isTerminalRunStatus(status))
|
|
127
|
+
void this.host.captureSnapshot(runId, cwd);
|
|
128
|
+
};
|
|
129
|
+
try {
|
|
130
|
+
const gate = new ClaudeChannelGate(sessionId);
|
|
131
|
+
handle = this.runner.startChannel({
|
|
132
|
+
prompt: payload.input,
|
|
133
|
+
cwd,
|
|
134
|
+
...(typeof payload.externalSessionId === 'string'
|
|
135
|
+
? { resumeSessionId: payload.externalSessionId }
|
|
136
|
+
: {}),
|
|
137
|
+
...(typeof payload.model === 'string' ? { model: payload.model } : {}),
|
|
138
|
+
...(typeof payload.effort === 'string' ? { effort: payload.effort } : {}),
|
|
139
|
+
...(typeof payload.access === 'string' ? { access: payload.access } : {}),
|
|
140
|
+
environment: secretEnvironment,
|
|
141
|
+
...(attachments.length === 0
|
|
142
|
+
? {}
|
|
143
|
+
: {
|
|
144
|
+
attachments: attachments.map((attachment) => ({
|
|
145
|
+
mime: attachment.mime,
|
|
146
|
+
dataBase64: attachment.dataBase64
|
|
147
|
+
}))
|
|
148
|
+
}),
|
|
149
|
+
onPermission: (toolName, toolInput, options) => toolName === 'AskUserQuestion'
|
|
150
|
+
? (toolKinds.set(options.toolUseID, 'user_input_request'),
|
|
151
|
+
this.host.requestUserInput(runId, toolInput, options.toolUseID))
|
|
152
|
+
: this.host.requestApproval(runId, toolName, toolInput),
|
|
153
|
+
onChannelReply: (content) => this.host.emit(runId, 'channel.reply', { content }),
|
|
154
|
+
onMessage: (message) => {
|
|
155
|
+
const record = message;
|
|
156
|
+
const nativeSessionId = typeof record.session_id === 'string' ? record.session_id : undefined;
|
|
157
|
+
const current = this.runner.execution.runs.get(runId);
|
|
158
|
+
if (nativeSessionId !== undefined && current !== undefined) {
|
|
159
|
+
const firstNativeSessionId = current.nativeSessionId === undefined;
|
|
160
|
+
if (firstNativeSessionId)
|
|
161
|
+
this.runner.execution.runs.set(runId, { ...current, nativeSessionId });
|
|
162
|
+
const managed = this.host.session(sessionId);
|
|
163
|
+
if (managed?.nativeControl === 'MAR_MANAGED')
|
|
164
|
+
this.host.rememberNative(nativeSessionId, managed.id);
|
|
165
|
+
if (firstNativeSessionId && typeof payload.externalSessionId !== 'string') {
|
|
166
|
+
this.host.promote(sessionId, nativeSessionId);
|
|
167
|
+
this.host.send('session.external-id', {
|
|
168
|
+
sessionId,
|
|
169
|
+
externalSessionId: nativeSessionId
|
|
170
|
+
});
|
|
171
|
+
const managed = this.runner.execution.runs.get(runId);
|
|
172
|
+
if (managed !== undefined)
|
|
173
|
+
this.runner.execution.runs.set(runId, {
|
|
174
|
+
...managed,
|
|
175
|
+
marSessionId: nativeSessionId
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (record.type === 'assistant') {
|
|
180
|
+
const text = extractClaudeText(record);
|
|
181
|
+
const planText = claudePlanText(text);
|
|
182
|
+
if (planText !== undefined)
|
|
183
|
+
this.host.emitItem(runId, {
|
|
184
|
+
itemId: boundedConversationItemId('claude-plan', typeof record.uuid === 'string' ? record.uuid : `${runId}:plan`),
|
|
185
|
+
kind: 'plan',
|
|
186
|
+
status: 'COMPLETED',
|
|
187
|
+
payload: { explanation: compactRunnerText(planText, 20_000), steps: [] }
|
|
188
|
+
});
|
|
189
|
+
else if (text.length > 0)
|
|
190
|
+
this.host.emit(runId, 'text.delta', { text });
|
|
191
|
+
for (const [index, text] of extractClaudeThinkingSummaries(record).entries()) {
|
|
192
|
+
this.host.emitItem(runId, {
|
|
193
|
+
itemId: boundedConversationItemId('claude-reasoning', `${typeof record.uuid === 'string' ? record.uuid : runId}:${index}`),
|
|
194
|
+
kind: 'reasoning_summary',
|
|
195
|
+
status: 'COMPLETED',
|
|
196
|
+
payload: { sections: [{ index, text }] }
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
for (const tool of extractClaudeToolUses(record)) {
|
|
200
|
+
if (tool.name === 'AskUserQuestion') {
|
|
201
|
+
toolKinds.set(tool.id, 'user_input_request');
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const itemId = boundedConversationItemId('claude-tool', tool.id);
|
|
205
|
+
const kind = isClaudeCommandTool(tool.name) ? 'command_execution' : 'tool_call';
|
|
206
|
+
toolKinds.set(tool.id, kind);
|
|
207
|
+
this.host.emitItem(runId, {
|
|
208
|
+
itemId,
|
|
209
|
+
kind,
|
|
210
|
+
status: 'IN_PROGRESS',
|
|
211
|
+
payload: kind === 'command_execution'
|
|
212
|
+
? {
|
|
213
|
+
command: compactRunnerText(tool.command ?? tool.name, 10_000),
|
|
214
|
+
cwd: null,
|
|
215
|
+
outputPreview: '',
|
|
216
|
+
outputRef: null,
|
|
217
|
+
exitCode: null,
|
|
218
|
+
durationMs: null,
|
|
219
|
+
truncated: false
|
|
220
|
+
}
|
|
221
|
+
: {
|
|
222
|
+
namespace: 'claude-code',
|
|
223
|
+
toolName: compactRunnerText(tool.name, 128),
|
|
224
|
+
title: compactRunnerText(tool.name, 500),
|
|
225
|
+
inputSummary: compactRunnerValue(tool.input, 10_000).text || null,
|
|
226
|
+
outputSummary: null,
|
|
227
|
+
progressLabel: null,
|
|
228
|
+
errorCode: null,
|
|
229
|
+
truncated: tool.inputTruncated
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
for (const [index, block] of claudeContentBlocks(record).entries()) {
|
|
234
|
+
const type = typeof block.type === 'string' ? block.type : 'unknown';
|
|
235
|
+
if (type === 'text' ||
|
|
236
|
+
type === 'thinking' ||
|
|
237
|
+
type === 'redacted_thinking' ||
|
|
238
|
+
type === 'encrypted_thinking' ||
|
|
239
|
+
type === 'tool_use' ||
|
|
240
|
+
type === 'tool_result')
|
|
241
|
+
continue;
|
|
242
|
+
this.host.emitItem(runId, {
|
|
243
|
+
itemId: boundedConversationItemId('claude-item', `${typeof record.uuid === 'string' ? record.uuid : runId}:${index}`),
|
|
244
|
+
kind: 'unknown',
|
|
245
|
+
status: 'COMPLETED',
|
|
246
|
+
payload: {
|
|
247
|
+
sourceEventType: `claude-code:${type}`,
|
|
248
|
+
label: `Claude Code ${compactRunnerText(type, 128)}`
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (record.type === 'tool_progress' && typeof record.tool_use_id === 'string') {
|
|
254
|
+
const kind = toolKinds.get(record.tool_use_id);
|
|
255
|
+
if (kind === 'tool_call') {
|
|
256
|
+
const elapsed = typeof record.elapsed_time_seconds === 'number'
|
|
257
|
+
? `(${Math.max(0, Math.round(record.elapsed_time_seconds))} 秒)`
|
|
258
|
+
: '';
|
|
259
|
+
this.host.emitItem(runId, {
|
|
260
|
+
itemId: boundedConversationItemId('claude-tool', record.tool_use_id),
|
|
261
|
+
kind,
|
|
262
|
+
status: 'IN_PROGRESS',
|
|
263
|
+
payload: {
|
|
264
|
+
progressLabel: compactRunnerText(`${typeof record.tool_name === 'string' ? record.tool_name : '工具'}正在执行${elapsed}`, 500)
|
|
265
|
+
},
|
|
266
|
+
merge: true
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (record.type === 'user') {
|
|
271
|
+
for (const result of extractClaudeToolResults(record)) {
|
|
272
|
+
const kind = toolKinds.get(result.toolUseId) ?? 'tool_call';
|
|
273
|
+
if (kind === 'user_input_request')
|
|
274
|
+
continue;
|
|
275
|
+
this.host.emitItem(runId, {
|
|
276
|
+
itemId: boundedConversationItemId('claude-tool', result.toolUseId),
|
|
277
|
+
kind,
|
|
278
|
+
status: result.failed ? 'FAILED' : 'COMPLETED',
|
|
279
|
+
payload: kind === 'command_execution'
|
|
280
|
+
? {
|
|
281
|
+
outputPreview: result.output,
|
|
282
|
+
exitCode: null,
|
|
283
|
+
durationMs: null,
|
|
284
|
+
truncated: result.truncated
|
|
285
|
+
}
|
|
286
|
+
: {
|
|
287
|
+
outputSummary: result.output,
|
|
288
|
+
progressLabel: null,
|
|
289
|
+
errorCode: result.failed ? 'TOOL_EXECUTION_FAILED' : null,
|
|
290
|
+
truncated: result.truncated
|
|
291
|
+
},
|
|
292
|
+
merge: true
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (record.type === 'result') {
|
|
297
|
+
const subtype = typeof record.subtype === 'string' ? record.subtype : 'unknown';
|
|
298
|
+
const completedNativeSessionId = this.runner.execution.runs.get(runId)?.nativeSessionId ?? nativeSessionId;
|
|
299
|
+
this.host.emit(runId, 'turn.completed', {
|
|
300
|
+
subtype
|
|
301
|
+
});
|
|
302
|
+
// A Channel uses a long-lived input iterable. Receiving an SDK
|
|
303
|
+
// result completes this AgentRun even though that iterable can
|
|
304
|
+
// remain open waiting for another user turn; waiting only for
|
|
305
|
+
// stream closure would leave the WorkspaceLease permanently held.
|
|
306
|
+
finish(subtype === 'success' && record.is_error !== true ? 'SUCCEEDED' : 'FAILED', subtype === 'success' && record.is_error !== true ? 'run.completed' : 'run.failed', { subtype });
|
|
307
|
+
if (completedNativeSessionId !== undefined)
|
|
308
|
+
void this.host.syncTitle(sessionId, completedNativeSessionId, cwd);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
this.runner.execution.runs.set(runId, { marSessionId: sessionId, cwd, handle, gate });
|
|
313
|
+
this.host.setChannelToken(sessionId, gate.token);
|
|
314
|
+
this.host.send('session.channel.ready', { sessionId, token: gate.token });
|
|
315
|
+
this.host.emit(runId, 'run.running', {}, 'RUNNING');
|
|
316
|
+
void handle.completed.then(() => finish('SUCCEEDED', 'run.completed', { subtype: 'stream_closed' }), (error) => finish('FAILED', 'run.failed', {
|
|
317
|
+
code: error instanceof Error ? error.message : 'CLAUDE_RUN_FAILED'
|
|
318
|
+
}));
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
finish('FAILED', 'run.failed', {
|
|
322
|
+
code: error instanceof Error ? error.message : 'CLAUDE_RUN_START_FAILED'
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ClaudeAgentSdkAdapter, type ClaudeChannelRunHandle, type ClaudeQueryInput, type ClaudeRunHandle } from '../claude-agent-sdk.js';
|
|
2
|
+
import { AbstractRunner, type ExternalResumeContext, type ExternalResumeResult, type RunnerDiscoveryContext, type RunnerSessionPresentationContext, type RunnerSessionPresentation } from './abstract-runner.js';
|
|
3
|
+
import type { NodeCapabilities, RunnerDefaultConfiguration, RunnerProfile } from '@myagentroam/protocol';
|
|
4
|
+
import { ClaudeChannelGate } from '../claude-channel.js';
|
|
5
|
+
import type { ChannelDelivery } from '../claude-channel.js';
|
|
6
|
+
import { claudeUserInputQuestions } from '../util/runner-native-session-parsers.js';
|
|
7
|
+
import type { NodeAgentSession, NodeWorkspace } from '../database.js';
|
|
8
|
+
import { type NativeSessionHistory } from '../native-session-history.js';
|
|
9
|
+
export declare class ClaudeCodeRunner extends AbstractRunner<'claude-code'> {
|
|
10
|
+
private readonly client;
|
|
11
|
+
private readonly environment;
|
|
12
|
+
readonly name: "claude-code";
|
|
13
|
+
readonly discoveryScope: "WORKSPACE_REQUIRED";
|
|
14
|
+
readonly execution: ClaudeExecutionState;
|
|
15
|
+
constructor(client?: ClaudeAgentSdkAdapter, environment?: NodeJS.ProcessEnv);
|
|
16
|
+
profile(capabilities: NodeCapabilities): RunnerProfile;
|
|
17
|
+
available(capabilities: NodeCapabilities): boolean;
|
|
18
|
+
protected profileForValidation(): RunnerProfile;
|
|
19
|
+
defaultConfiguration(): RunnerDefaultConfiguration;
|
|
20
|
+
resumeExternal(external: NodeAgentSession, context: ExternalResumeContext): Promise<ExternalResumeResult>;
|
|
21
|
+
discoverSessions(context: RunnerDiscoveryContext, workspace?: NodeWorkspace): Promise<readonly NodeAgentSession[]>;
|
|
22
|
+
readContextUsage(session: NodeAgentSession): Promise<import("../native-session-history.js").NativeClaudeContextUsage | undefined>;
|
|
23
|
+
forkNative(session: NodeAgentSession, boundary: string | null, title: string): Promise<string | null>;
|
|
24
|
+
presentSession(session: NodeAgentSession, capabilities: NodeCapabilities, context: RunnerSessionPresentationContext): RunnerSessionPresentation;
|
|
25
|
+
prepareMessageInput(input: string, collaborationMode: 'default' | 'plan'): string;
|
|
26
|
+
channelToken(sessionId: string): string | undefined;
|
|
27
|
+
deliverChannel(sessionId: string, messageId: string, senderId: string, token: string, content: string, notify: (runId: string, messageId: string, delivery: ChannelDelivery) => void): ChannelDelivery;
|
|
28
|
+
managedAliasId(nativeSessionId: string): string | undefined;
|
|
29
|
+
nativeAliasesFor(managedSessionId: string): readonly string[];
|
|
30
|
+
forgetNativeAlias(nativeSessionId: string): void;
|
|
31
|
+
rememberManagedNative(session: NodeAgentSession, nativeSessionId: string): void;
|
|
32
|
+
readManagedRunnerTitle(session: NodeAgentSession, nativeSessionId: string): Promise<string | undefined>;
|
|
33
|
+
projectDiscoveredSessions(context: RunnerDiscoveryContext, workspace: NodeWorkspace, discovered: readonly ClaudeDiscoveredSession[]): readonly NodeAgentSession[];
|
|
34
|
+
stop(): void;
|
|
35
|
+
start(input: ClaudeQueryInput): ClaudeRunHandle;
|
|
36
|
+
startChannel(input: ClaudeQueryInput): ClaudeChannelRunHandle;
|
|
37
|
+
listSessions(cwd: string): ReturnType<ClaudeAgentSdkAdapter['listSessions']>;
|
|
38
|
+
sessionInfo(sessionId: string, cwd: string): ReturnType<ClaudeAgentSdkAdapter['sessionInfo']>;
|
|
39
|
+
}
|
|
40
|
+
export declare class ClaudeExecutionState {
|
|
41
|
+
readonly runs: Map<string, {
|
|
42
|
+
marSessionId: string;
|
|
43
|
+
nativeSessionId?: string;
|
|
44
|
+
cwd: string;
|
|
45
|
+
handle: ClaudeChannelRunHandle;
|
|
46
|
+
gate: ClaudeChannelGate;
|
|
47
|
+
}>;
|
|
48
|
+
readonly managedNativeIds: Set<string>;
|
|
49
|
+
readonly managedSessionByNativeId: Map<string, string>;
|
|
50
|
+
readonly approvals: Map<string, {
|
|
51
|
+
readonly runId: string;
|
|
52
|
+
readonly resolve: (decision: "APPROVED" | "REJECTED") => void;
|
|
53
|
+
readonly timer: NodeJS.Timeout;
|
|
54
|
+
}>;
|
|
55
|
+
readonly userInputs: Map<string, {
|
|
56
|
+
readonly runId: string;
|
|
57
|
+
readonly requestId: string;
|
|
58
|
+
readonly questions: ReturnType<typeof claudeUserInputQuestions>;
|
|
59
|
+
readonly input: Record<string, unknown>;
|
|
60
|
+
readonly resolve: (answers: Record<string, string>) => void;
|
|
61
|
+
readonly reject: (message?: string) => void;
|
|
62
|
+
readonly timer: NodeJS.Timeout;
|
|
63
|
+
}>;
|
|
64
|
+
}
|
|
65
|
+
type ClaudeDiscoveredSession = NativeSessionHistory | {
|
|
66
|
+
readonly externalSessionId: string;
|
|
67
|
+
readonly cwd: string;
|
|
68
|
+
readonly title?: string;
|
|
69
|
+
};
|
|
70
|
+
export {};
|