@myagentroam/node 0.1.8 → 0.9.2
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/dist/capabilities.js +6 -3
- package/dist/claude-agent-sdk.d.ts +1 -0
- package/dist/claude-agent-sdk.js +8 -1
- package/dist/codex-app-server.d.ts +6 -0
- package/dist/codex-app-server.js +6 -0
- package/dist/connector/node-connector-options.d.ts +1 -0
- package/dist/connector.d.ts +2 -0
- package/dist/connector.js +43 -14
- package/dist/database.d.ts +23 -0
- package/dist/database.js +84 -2
- package/dist/migrations/v004.d.ts +4 -0
- package/dist/migrations/v004.js +35 -0
- package/dist/native-session-history.js +72 -14
- package/dist/opencode-server.d.ts +1 -0
- package/dist/opencode-server.js +12 -0
- package/dist/runner/abstract-runner.d.ts +13 -3
- package/dist/runner/abstract-runner.js +78 -4
- package/dist/runner/claude/managed-run-controller.js +18 -7
- package/dist/runner/claude-code-runner.d.ts +1 -0
- package/dist/runner/claude-code-runner.js +17 -4
- package/dist/runner/codex/conversation-parser.d.ts +2 -2
- package/dist/runner/codex/conversation-parser.js +30 -12
- package/dist/runner/codex/managed-run-controller.js +11 -8
- package/dist/runner/codex-runner.d.ts +17 -3
- package/dist/runner/codex-runner.js +225 -12
- package/dist/runner/opencode/conversation-parser.d.ts +11 -7
- package/dist/runner/opencode/conversation-parser.js +15 -15
- package/dist/runner/opencode/managed-run-controller.d.ts +2 -1
- package/dist/runner/opencode/managed-run-controller.js +36 -12
- package/dist/runner/opencode-runner.d.ts +2 -1
- package/dist/runner/opencode-runner.js +23 -3
- package/dist/runner/runner-registry.d.ts +1 -1
- package/dist/runner/runner-registry.js +2 -2
- package/dist/runner-command-engine.js +1 -1
- package/dist/runner-profiles.js +29 -41
- package/dist/runner-usage.js +5 -5
- package/dist/runtime-command-detector.d.ts +3 -0
- package/dist/runtime-command-detector.js +78 -0
- package/dist/service/conversation-history-service.js +55 -24
- package/dist/service/conversation-segment-service.d.ts +20 -0
- package/dist/service/conversation-segment-service.js +155 -0
- package/dist/service/mcp-installation-verifier.d.ts +9 -0
- package/dist/service/mcp-installation-verifier.js +87 -0
- package/dist/service/mcp-node-operation-service.d.ts +11 -0
- package/dist/service/mcp-node-operation-service.js +90 -0
- package/dist/service/mcp-package-installer.d.ts +8 -0
- package/dist/service/mcp-package-installer.js +136 -0
- package/dist/service/native-session-watch-service.d.ts +1 -0
- package/dist/service/native-session-watch-service.js +21 -4
- package/dist/service/node-request-service.js +2 -1
- package/dist/service/run-attachment-service.d.ts +3 -2
- package/dist/service/run-attachment-service.js +21 -11
- package/dist/service/run-event-service.d.ts +1 -0
- package/dist/service/run-event-service.js +8 -2
- package/dist/service/runner-service.d.ts +2 -0
- package/dist/service/runner-service.js +5 -2
- package/dist/service/session-catalog-service.js +4 -4
- package/dist/service/session-lifecycle-service.js +7 -4
- package/dist/service/session-presentation-service.d.ts +1 -0
- package/dist/service/session-presentation-service.js +8 -1
- package/dist/service/session-query-service.d.ts +4 -0
- package/dist/service/session-query-service.js +6 -1
- package/dist/service/skill-directory-service.js +3 -26
- package/dist/service/skill-install-service.js +4 -74
- package/dist/service/skill-node-operation-service.js +2 -1
- package/dist/service/workbench-manifest-service.d.ts +2 -0
- package/dist/service/workspace-git-exclude-service.d.ts +4 -0
- package/dist/service/workspace-git-exclude-service.js +119 -0
- package/dist/service/workspace-queue-workbench-service.d.ts +2 -0
- package/dist/service/workspace-queue-workbench-service.js +5 -3
- package/dist/service/workspace-service.js +2 -0
- package/dist/supervisor.js +2 -1
- package/dist/terminal.js +1 -1
- package/dist/util/node-operation-parsers.js +2 -2
- package/dist/util/runner-native-session-parsers.d.ts +2 -3
- package/dist/util/runner-native-session-parsers.js +5 -14
- package/dist/util/safe-error.d.ts +2 -0
- package/dist/util/safe-error.js +5 -0
- package/package.json +2 -2
|
@@ -286,7 +286,7 @@ async function readTranscript(path, runner, byteLimit) {
|
|
|
286
286
|
items.push({
|
|
287
287
|
kind: 'tool_call',
|
|
288
288
|
toolUseId: result.toolUseId,
|
|
289
|
-
toolName: '
|
|
289
|
+
toolName: 'Tool',
|
|
290
290
|
input: null,
|
|
291
291
|
inputSummary: null,
|
|
292
292
|
inputTruncated: false,
|
|
@@ -301,7 +301,7 @@ async function readTranscript(path, runner, byteLimit) {
|
|
|
301
301
|
if (cwd === undefined)
|
|
302
302
|
return undefined;
|
|
303
303
|
const title = items.find((item) => item.kind === 'message' && item.role === 'USER')?.text;
|
|
304
|
-
//
|
|
304
|
+
// Tagged Plan Mode instruction labels do not form a session title.
|
|
305
305
|
const strippedTitle = title === undefined ? undefined : stripClaudePlanTag(title);
|
|
306
306
|
return {
|
|
307
307
|
runner,
|
|
@@ -591,7 +591,7 @@ function codexGeneratedImageEvent(record) {
|
|
|
591
591
|
input: null,
|
|
592
592
|
inputSummary: null,
|
|
593
593
|
inputTruncated: false,
|
|
594
|
-
outputSummary: payload['status'] === 'completed' ? '
|
|
594
|
+
outputSummary: payload['status'] === 'completed' ? 'Image generation completed' : 'Image generation failed',
|
|
595
595
|
outputTruncated: false,
|
|
596
596
|
imageAttachments: [{ name: basename(localPath), mime: imageMime(localPath), localPath }],
|
|
597
597
|
failed: payload['status'] !== 'completed',
|
|
@@ -620,7 +620,7 @@ function codexVisibleToolText(value) {
|
|
|
620
620
|
const start = value.indexOf(marker, cursor);
|
|
621
621
|
if (start < 0)
|
|
622
622
|
return visible + value.slice(cursor);
|
|
623
|
-
visible += `${value.slice(cursor, start)}[
|
|
623
|
+
visible += `${value.slice(cursor, start)}[Image content filtered]`;
|
|
624
624
|
const doubleQuote = value.indexOf('"', start);
|
|
625
625
|
const singleQuote = value.indexOf("'", start);
|
|
626
626
|
const end = doubleQuote < 0
|
|
@@ -636,14 +636,20 @@ function codexVisibleToolText(value) {
|
|
|
636
636
|
}
|
|
637
637
|
/** Extract only image placeholder metadata from native Codex output. */
|
|
638
638
|
function codexImagePlaceholders(value) {
|
|
639
|
-
const
|
|
639
|
+
const attachments = [];
|
|
640
640
|
let visited = 0;
|
|
641
641
|
const visit = (current, depth) => {
|
|
642
|
-
if (
|
|
642
|
+
if (attachments.length >= 5 || visited >= 128 || depth > 5 || current == null)
|
|
643
643
|
return;
|
|
644
644
|
visited += 1;
|
|
645
645
|
if (typeof current === 'string') {
|
|
646
|
+
const paths = [];
|
|
646
647
|
collectGeneratedImagePaths(current, paths);
|
|
648
|
+
for (const localPath of paths) {
|
|
649
|
+
if (attachments.some((attachment) => attachment.localPath === localPath))
|
|
650
|
+
continue;
|
|
651
|
+
attachments.push({ name: basename(localPath), mime: imageMime(localPath), localPath });
|
|
652
|
+
}
|
|
647
653
|
return;
|
|
648
654
|
}
|
|
649
655
|
if (Array.isArray(current)) {
|
|
@@ -653,15 +659,28 @@ function codexImagePlaceholders(value) {
|
|
|
653
659
|
}
|
|
654
660
|
if (!isRecord(current))
|
|
655
661
|
return;
|
|
662
|
+
const type = typeof current['type'] === 'string' ? current['type'] : '';
|
|
663
|
+
if (type === 'input_text' && typeof current['text'] === 'string') {
|
|
664
|
+
const mime = /["']image_url["']\s*:\s*["']data:(image\/[^;,"']+)[;,]/.exec(current['text'])?.[1];
|
|
665
|
+
if (mime !== undefined)
|
|
666
|
+
attachments.push({ name: `image-${attachments.length + 1}.${imageExtension(mime)}`, mime });
|
|
667
|
+
}
|
|
668
|
+
const imageUrl = isRecord(current['image_url'])
|
|
669
|
+
? firstString(current['image_url'], ['url'])
|
|
670
|
+
: typeof current['image_url'] === 'string'
|
|
671
|
+
? current['image_url']
|
|
672
|
+
: undefined;
|
|
673
|
+
const data = typeof current['data'] === 'string' ? current['data'] : imageUrl;
|
|
674
|
+
if ((type === 'image' || type === 'input_image' || type === 'output_image') &&
|
|
675
|
+
data?.startsWith('data:image/')) {
|
|
676
|
+
const mime = /^data:([^;,]+)[;,]/.exec(data)?.[1] ?? 'image/png';
|
|
677
|
+
attachments.push({ name: `image-${attachments.length + 1}.${imageExtension(mime)}`, mime });
|
|
678
|
+
}
|
|
656
679
|
for (const child of Object.values(current))
|
|
657
680
|
visit(child, depth + 1);
|
|
658
681
|
};
|
|
659
682
|
visit(value, 0);
|
|
660
|
-
return
|
|
661
|
-
name: basename(localPath),
|
|
662
|
-
mime: imageMime(localPath),
|
|
663
|
-
localPath
|
|
664
|
-
}));
|
|
683
|
+
return attachments;
|
|
665
684
|
}
|
|
666
685
|
function collectGeneratedImagePaths(value, paths) {
|
|
667
686
|
const marker = '/.codex/generated_images/';
|
|
@@ -690,15 +709,54 @@ function imageMime(path) {
|
|
|
690
709
|
return 'image/webp';
|
|
691
710
|
return 'image/png';
|
|
692
711
|
}
|
|
712
|
+
function imageExtension(mime) {
|
|
713
|
+
if (mime === 'image/jpeg')
|
|
714
|
+
return 'jpg';
|
|
715
|
+
if (mime === 'image/webp')
|
|
716
|
+
return 'webp';
|
|
717
|
+
if (mime === 'image/gif')
|
|
718
|
+
return 'gif';
|
|
719
|
+
return 'png';
|
|
720
|
+
}
|
|
693
721
|
function withToolResult(call, result) {
|
|
722
|
+
const viewedImagePath = codexViewedImagePath(call);
|
|
723
|
+
const imageAttachments = result.imageAttachments?.map((attachment) => attachment.localPath !== undefined || viewedImagePath === undefined
|
|
724
|
+
? attachment
|
|
725
|
+
: {
|
|
726
|
+
name: basename(viewedImagePath),
|
|
727
|
+
mime: imageMime(viewedImagePath),
|
|
728
|
+
localPath: viewedImagePath
|
|
729
|
+
});
|
|
694
730
|
return {
|
|
695
731
|
...call,
|
|
696
732
|
outputSummary: result.output,
|
|
697
733
|
outputTruncated: result.truncated,
|
|
698
|
-
...(
|
|
734
|
+
...(imageAttachments === undefined ? {} : { imageAttachments }),
|
|
699
735
|
failed: result.failed
|
|
700
736
|
};
|
|
701
737
|
}
|
|
738
|
+
function codexViewedImagePath(call) {
|
|
739
|
+
const toolName = call.toolName.toLowerCase();
|
|
740
|
+
if (toolName === 'view_image' || toolName === 'imageview') {
|
|
741
|
+
return isRecord(call.input) && typeof call.input['path'] === 'string'
|
|
742
|
+
? call.input['path']
|
|
743
|
+
: undefined;
|
|
744
|
+
}
|
|
745
|
+
if (toolName !== 'exec' || typeof call.input !== 'string')
|
|
746
|
+
return undefined;
|
|
747
|
+
if (!/tools\.view_image\s*\(/.test(call.input))
|
|
748
|
+
return undefined;
|
|
749
|
+
const encodedPath = /["']path["']\s*:\s*("(?:\\.|[^"\\])*")/.exec(call.input)?.[1];
|
|
750
|
+
if (encodedPath === undefined)
|
|
751
|
+
return undefined;
|
|
752
|
+
try {
|
|
753
|
+
const path = JSON.parse(encodedPath);
|
|
754
|
+
return typeof path === 'string' ? path : undefined;
|
|
755
|
+
}
|
|
756
|
+
catch {
|
|
757
|
+
return undefined;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
702
760
|
function isInjectedContextPrompt(text) {
|
|
703
761
|
const normalized = text.replace(/\s+/g, ' ').trim();
|
|
704
762
|
if (normalized.startsWith('<codex_internal_context'))
|
|
@@ -776,7 +834,7 @@ function compactValue(value, limit) {
|
|
|
776
834
|
text = JSON.stringify(value);
|
|
777
835
|
}
|
|
778
836
|
catch {
|
|
779
|
-
return { text: '
|
|
837
|
+
return { text: '(Unable to display)', truncated: false };
|
|
780
838
|
}
|
|
781
839
|
}
|
|
782
840
|
return {
|
|
@@ -812,7 +870,7 @@ function nestedString(value, keys) {
|
|
|
812
870
|
function compact(value) {
|
|
813
871
|
return value.replace(/\s+/g, ' ').trim().slice(0, 16_000);
|
|
814
872
|
}
|
|
815
|
-
/**
|
|
873
|
+
/** Conversation content can be Markdown, so its internal whitespace must not be collapsed like a title. */
|
|
816
874
|
function messageText(value) {
|
|
817
875
|
return value.trim().slice(0, 16_000);
|
|
818
876
|
}
|
|
@@ -16,6 +16,7 @@ export declare class OpenCodeServerClient {
|
|
|
16
16
|
start(): Promise<void>;
|
|
17
17
|
private startServer;
|
|
18
18
|
providers(directory: string): Promise<OpenCodeProviderCatalog>;
|
|
19
|
+
configureMcp(directory: string, mcp: unknown): Promise<void>;
|
|
19
20
|
sessions(directory: string, limit?: number): Promise<readonly unknown[]>;
|
|
20
21
|
sessionStatus(directory: string): Promise<Readonly<Record<string, unknown>>>;
|
|
21
22
|
createSession(directory: string, title?: string): Promise<unknown>;
|
package/dist/opencode-server.js
CHANGED
|
@@ -77,6 +77,18 @@ export class OpenCodeServerClient {
|
|
|
77
77
|
const client = await this.requireClient();
|
|
78
78
|
return unwrap(await client.provider.list({ directory }));
|
|
79
79
|
}
|
|
80
|
+
async configureMcp(directory, mcp) {
|
|
81
|
+
const client = await this.requireClient();
|
|
82
|
+
if (typeof mcp !== 'object' || mcp === null || Array.isArray(mcp))
|
|
83
|
+
return;
|
|
84
|
+
for (const [name, config] of Object.entries(mcp)) {
|
|
85
|
+
await client.mcp.add({
|
|
86
|
+
directory,
|
|
87
|
+
name,
|
|
88
|
+
config: config
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
80
92
|
async sessions(directory, limit = 100) {
|
|
81
93
|
const client = await this.requireClient();
|
|
82
94
|
return unwrap(await client.session.list({ directory, limit }));
|
|
@@ -21,14 +21,15 @@ export declare abstract class AbstractRunner<TName extends RegisteredRunnerName
|
|
|
21
21
|
abstract profile(capabilities: NodeCapabilities): RunnerProfile | undefined;
|
|
22
22
|
refreshProfile(capabilities: NodeCapabilities, workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): Promise<RunnerProfile | undefined>;
|
|
23
23
|
abstract available(capabilities: NodeCapabilities): boolean;
|
|
24
|
-
abstract defaultConfiguration(workspace?: NodeWorkspace): RunnerDefaultConfiguration;
|
|
25
|
-
supportsConfiguration(configuration: RunnerDefaultConfiguration, workspace?: NodeWorkspace): boolean;
|
|
24
|
+
abstract defaultConfiguration(workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): RunnerDefaultConfiguration;
|
|
25
|
+
supportsConfiguration(configuration: RunnerDefaultConfiguration, workspace?: NodeWorkspace, environment?: Readonly<Record<string, string>>): boolean;
|
|
26
26
|
protected abstract profileForValidation(): RunnerProfile;
|
|
27
27
|
abstract resumeExternal(session: NodeAgentSession, context: ExternalResumeContext, force?: boolean): Promise<ExternalResumeResult>;
|
|
28
28
|
abstract discoverSessions(context: RunnerDiscoveryContext, workspace?: NodeWorkspace): Promise<readonly NodeAgentSession[]>;
|
|
29
29
|
abstract readContextUsage(session: NodeAgentSession): Promise<RunnerContextUsage | undefined>;
|
|
30
30
|
presentSession(session: NodeAgentSession, capabilities: NodeCapabilities, context: RunnerSessionPresentationContext): RunnerSessionPresentation;
|
|
31
31
|
prepareMessageInput(input: string, collaborationMode: 'default' | 'plan'): string;
|
|
32
|
+
mcpConfiguration(rawInstallations: unknown, secretEnvironment: Readonly<Record<string, string>>): unknown;
|
|
32
33
|
channelToken(sessionId: string): string | undefined;
|
|
33
34
|
deliverChannel(sessionId: string, messageId: string, senderId: string, token: string, content: string, notify: (runId: string, messageId: string, delivery: ChannelDelivery) => void): ChannelDelivery;
|
|
34
35
|
executeGlobalCommand(commandId: string, input: string): unknown;
|
|
@@ -52,7 +53,7 @@ export declare abstract class AbstractRunner<TName extends RegisteredRunnerName
|
|
|
52
53
|
}, managedTurn: (nativeTurnId: string) => NodeConversationTurn | undefined): Promise<RunnerOfficialConversationPage | undefined>;
|
|
53
54
|
readExternalActivity(session: NodeAgentSession): Promise<SessionActivityState | undefined>;
|
|
54
55
|
acceptsTruncatedNativeHistory(): boolean;
|
|
55
|
-
|
|
56
|
+
nativeImageRoots(session: NodeAgentSession): readonly string[];
|
|
56
57
|
managedRunForNativeTurn(nativeTurnId: string): string | undefined;
|
|
57
58
|
abstract stop(): void | Promise<void>;
|
|
58
59
|
bindControl(control: RunnerControl): void;
|
|
@@ -66,6 +67,15 @@ export declare abstract class AbstractRunner<TName extends RegisteredRunnerName
|
|
|
66
67
|
readonly secretEnvironment: Readonly<Record<string, string>>;
|
|
67
68
|
}): Promise<unknown>;
|
|
68
69
|
}
|
|
70
|
+
export interface ResolvedMcpServer {
|
|
71
|
+
readonly name: string;
|
|
72
|
+
readonly transport: 'STDIO' | 'HTTP';
|
|
73
|
+
readonly command?: string;
|
|
74
|
+
readonly args?: readonly string[];
|
|
75
|
+
readonly environment?: Readonly<Record<string, string>>;
|
|
76
|
+
readonly url?: string;
|
|
77
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
78
|
+
}
|
|
69
79
|
export type RunnerCancellationIntent = 'CANCEL' | 'INTERRUPT';
|
|
70
80
|
export interface RunnerControl {
|
|
71
81
|
start(envelope: NodeEnvelope): void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mcpRuntimeDescriptorSchema } from '@myagentroam/protocol';
|
|
1
2
|
/**
|
|
2
3
|
* Stable lifecycle boundary for every Node Runner implementation.
|
|
3
4
|
*
|
|
@@ -15,8 +16,8 @@ export class AbstractRunner {
|
|
|
15
16
|
void [workspace, environment];
|
|
16
17
|
return this.profile(capabilities);
|
|
17
18
|
}
|
|
18
|
-
supportsConfiguration(configuration, workspace) {
|
|
19
|
-
void workspace;
|
|
19
|
+
supportsConfiguration(configuration, workspace, environment) {
|
|
20
|
+
void [workspace, environment];
|
|
20
21
|
const profile = this.profileForValidation();
|
|
21
22
|
const model = profile.models.find((candidate) => candidate.id === configuration.model);
|
|
22
23
|
return (configuration.runner === this.name &&
|
|
@@ -32,6 +33,9 @@ export class AbstractRunner {
|
|
|
32
33
|
void collaborationMode;
|
|
33
34
|
return input;
|
|
34
35
|
}
|
|
36
|
+
mcpConfiguration(rawInstallations, secretEnvironment) {
|
|
37
|
+
return resolvedMcpServers(rawInstallations, secretEnvironment);
|
|
38
|
+
}
|
|
35
39
|
channelToken(sessionId) {
|
|
36
40
|
void sessionId;
|
|
37
41
|
return undefined;
|
|
@@ -102,9 +106,9 @@ export class AbstractRunner {
|
|
|
102
106
|
acceptsTruncatedNativeHistory() {
|
|
103
107
|
return true;
|
|
104
108
|
}
|
|
105
|
-
|
|
109
|
+
nativeImageRoots(session) {
|
|
106
110
|
void session;
|
|
107
|
-
return
|
|
111
|
+
return [];
|
|
108
112
|
}
|
|
109
113
|
managedRunForNativeTurn(nativeTurnId) {
|
|
110
114
|
void nativeTurnId;
|
|
@@ -143,6 +147,76 @@ export class AbstractRunner {
|
|
|
143
147
|
return this.commandControl.execute(session, command, input, context);
|
|
144
148
|
}
|
|
145
149
|
}
|
|
150
|
+
function resolvedMcpServers(raw, secrets) {
|
|
151
|
+
if (!Array.isArray(raw))
|
|
152
|
+
return [];
|
|
153
|
+
const result = [];
|
|
154
|
+
for (const value of raw) {
|
|
155
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
156
|
+
continue;
|
|
157
|
+
const record = value;
|
|
158
|
+
if (typeof record['serverName'] !== 'string' || record['enabled'] !== true)
|
|
159
|
+
continue;
|
|
160
|
+
const runtime = mcpRuntimeDescriptorSchema.safeParse(record['runtime']);
|
|
161
|
+
if (!runtime.success)
|
|
162
|
+
continue;
|
|
163
|
+
const configuration = isRecord(record['configuration']) ? record['configuration'] : {};
|
|
164
|
+
const resolveReferences = (candidate) => {
|
|
165
|
+
if (!isRecord(candidate))
|
|
166
|
+
return {};
|
|
167
|
+
const result = {};
|
|
168
|
+
for (const [name, reference] of Object.entries(candidate)) {
|
|
169
|
+
if (!isRecord(reference) || typeof reference['secretName'] !== 'string')
|
|
170
|
+
return undefined;
|
|
171
|
+
const secret = secrets[reference['secretName']];
|
|
172
|
+
if (secret === undefined)
|
|
173
|
+
return undefined;
|
|
174
|
+
result[name] = secret;
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
};
|
|
178
|
+
if (runtime.data.transport === 'STDIO') {
|
|
179
|
+
const runtimeEnvironment = resolveReferences(runtime.data.environment);
|
|
180
|
+
const configuredEnvironment = resolveReferences(configuration['environment']);
|
|
181
|
+
if (runtimeEnvironment === undefined || configuredEnvironment === undefined)
|
|
182
|
+
continue;
|
|
183
|
+
result.push({
|
|
184
|
+
name: record['serverName'],
|
|
185
|
+
transport: 'STDIO',
|
|
186
|
+
command: runtime.data.command,
|
|
187
|
+
args: [
|
|
188
|
+
...runtime.data.args,
|
|
189
|
+
...(Array.isArray(configuration['arguments'])
|
|
190
|
+
? configuration['arguments'].filter((item) => typeof item === 'string')
|
|
191
|
+
: [])
|
|
192
|
+
],
|
|
193
|
+
environment: {
|
|
194
|
+
...runtimeEnvironment,
|
|
195
|
+
...configuredEnvironment
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const runtimeHeaders = resolveReferences(runtime.data.headers);
|
|
201
|
+
const configuredHeaders = resolveReferences(configuration['headers']);
|
|
202
|
+
if (runtimeHeaders === undefined || configuredHeaders === undefined)
|
|
203
|
+
continue;
|
|
204
|
+
result.push({
|
|
205
|
+
name: record['serverName'],
|
|
206
|
+
transport: 'HTTP',
|
|
207
|
+
url: runtime.data.url,
|
|
208
|
+
headers: {
|
|
209
|
+
...runtimeHeaders,
|
|
210
|
+
...configuredHeaders
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
function isRecord(value) {
|
|
218
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
219
|
+
}
|
|
146
220
|
export class RunnerCapabilityError extends Error {
|
|
147
221
|
runner;
|
|
148
222
|
capability;
|
|
@@ -4,6 +4,7 @@ import { boundedConversationItemId, claudePlanText, compactRunnerText, compactRu
|
|
|
4
4
|
import { isTerminalRunStatus } from '../../util/node-operation-parsers.js';
|
|
5
5
|
import { claudeContentBlocks } from '../../util/node-operation-parsers.js';
|
|
6
6
|
import { parseSecretEnvironment } from '../../util/secret-environment.js';
|
|
7
|
+
import { safeErrorCode } from '../../util/safe-error.js';
|
|
7
8
|
export class ClaudeManagedRunController {
|
|
8
9
|
runner;
|
|
9
10
|
host;
|
|
@@ -24,10 +25,10 @@ export class ClaudeManagedRunController {
|
|
|
24
25
|
commandId: 'plan',
|
|
25
26
|
active: true,
|
|
26
27
|
label: 'Plan Mode',
|
|
27
|
-
detail: '
|
|
28
|
+
detail: 'Subsequent messages run in read-only tagged Plan Mode and produce a plan card.',
|
|
28
29
|
closable: true,
|
|
29
30
|
closeInput: '/plan',
|
|
30
|
-
continuation: { label: 'Implement', prompt: '
|
|
31
|
+
continuation: { label: 'Implement', prompt: 'Implement the plan above.' }
|
|
31
32
|
});
|
|
32
33
|
return { command: command.id, states: this.host.commandStates(session.id) };
|
|
33
34
|
}
|
|
@@ -83,7 +84,7 @@ export class ClaudeManagedRunController {
|
|
|
83
84
|
secretEnvironment = parseSecretEnvironment(payload.secretEnvironment);
|
|
84
85
|
}
|
|
85
86
|
catch (error) {
|
|
86
|
-
this.host.emit(runId, 'run.rejected', { code: error
|
|
87
|
+
this.host.emit(runId, 'run.rejected', { code: safeErrorCode(error, 'MESSAGE_ATTACHMENTS_INVALID') }, 'FAILED');
|
|
87
88
|
return;
|
|
88
89
|
}
|
|
89
90
|
this.host.adopt({
|
|
@@ -138,6 +139,7 @@ export class ClaudeManagedRunController {
|
|
|
138
139
|
...(typeof payload.effort === 'string' ? { effort: payload.effort } : {}),
|
|
139
140
|
...(typeof payload.access === 'string' ? { access: payload.access } : {}),
|
|
140
141
|
environment: secretEnvironment,
|
|
142
|
+
mcpServers: this.runner.mcpConfiguration(payload.mcpInstallations, secretEnvironment),
|
|
141
143
|
...(attachments.length === 0
|
|
142
144
|
? {}
|
|
143
145
|
: {
|
|
@@ -254,14 +256,23 @@ export class ClaudeManagedRunController {
|
|
|
254
256
|
const kind = toolKinds.get(record.tool_use_id);
|
|
255
257
|
if (kind === 'tool_call') {
|
|
256
258
|
const elapsed = typeof record.elapsed_time_seconds === 'number'
|
|
257
|
-
?
|
|
259
|
+
? ` (${Math.max(0, Math.round(record.elapsed_time_seconds))} seconds)`
|
|
258
260
|
: '';
|
|
259
261
|
this.host.emitItem(runId, {
|
|
260
262
|
itemId: boundedConversationItemId('claude-tool', record.tool_use_id),
|
|
261
263
|
kind,
|
|
262
264
|
status: 'IN_PROGRESS',
|
|
263
265
|
payload: {
|
|
264
|
-
progressLabel: compactRunnerText(`${typeof record.tool_name === 'string' ? record.tool_name : '
|
|
266
|
+
progressLabel: compactRunnerText(`${typeof record.tool_name === 'string' ? record.tool_name : 'Tool'} is running${elapsed}`, 500),
|
|
267
|
+
progressLabelCode: typeof record.elapsed_time_seconds === 'number'
|
|
268
|
+
? 'TOOL_RUNNING_WITH_DURATION'
|
|
269
|
+
: 'TOOL_RUNNING',
|
|
270
|
+
progressLabelParameters: {
|
|
271
|
+
tool: typeof record.tool_name === 'string' ? record.tool_name : 'Tool',
|
|
272
|
+
seconds: typeof record.elapsed_time_seconds === 'number'
|
|
273
|
+
? Math.max(0, Math.round(record.elapsed_time_seconds))
|
|
274
|
+
: 0
|
|
275
|
+
}
|
|
265
276
|
},
|
|
266
277
|
merge: true
|
|
267
278
|
});
|
|
@@ -314,12 +325,12 @@ export class ClaudeManagedRunController {
|
|
|
314
325
|
this.host.send('session.channel.ready', { sessionId, token: gate.token });
|
|
315
326
|
this.host.emit(runId, 'run.running', {}, 'RUNNING');
|
|
316
327
|
void handle.completed.then(() => finish('SUCCEEDED', 'run.completed', { subtype: 'stream_closed' }), (error) => finish('FAILED', 'run.failed', {
|
|
317
|
-
code: error
|
|
328
|
+
code: safeErrorCode(error, 'CLAUDE_RUN_FAILED')
|
|
318
329
|
}));
|
|
319
330
|
}
|
|
320
331
|
catch (error) {
|
|
321
332
|
finish('FAILED', 'run.failed', {
|
|
322
|
-
code: error
|
|
333
|
+
code: safeErrorCode(error, 'CLAUDE_RUN_START_FAILED')
|
|
323
334
|
});
|
|
324
335
|
}
|
|
325
336
|
}
|
|
@@ -17,6 +17,7 @@ export declare class ClaudeCodeRunner extends AbstractRunner<'claude-code'> {
|
|
|
17
17
|
available(capabilities: NodeCapabilities): boolean;
|
|
18
18
|
protected profileForValidation(): RunnerProfile;
|
|
19
19
|
defaultConfiguration(): RunnerDefaultConfiguration;
|
|
20
|
+
mcpConfiguration(raw: unknown, secrets: Readonly<Record<string, string>>): unknown;
|
|
20
21
|
resumeExternal(external: NodeAgentSession, context: ExternalResumeContext): Promise<ExternalResumeResult>;
|
|
21
22
|
discoverSessions(context: RunnerDiscoveryContext, workspace?: NodeWorkspace): Promise<readonly NodeAgentSession[]>;
|
|
22
23
|
readContextUsage(session: NodeAgentSession): Promise<import("../native-session-history.js").NativeClaudeContextUsage | undefined>;
|
|
@@ -3,7 +3,7 @@ import { AbstractRunner } from './abstract-runner.js';
|
|
|
3
3
|
import { claudeDefaultModel, declaredRunnerProfiles } from '../runner-profiles.js';
|
|
4
4
|
import { withClaudePlanTag } from '../runner-command-engine.js';
|
|
5
5
|
import { discoverNativeSessions, readNativeSession, readClaudeNativeContextUsage } from '../native-session-history.js';
|
|
6
|
-
import { claudeDiscoveredSession, deduplicateDirectSessions,
|
|
6
|
+
import { claudeDiscoveredSession, deduplicateDirectSessions, isNativeHistory, isWithinWorkspace, latestNativeActivity } from './codex/conversation-parser.js';
|
|
7
7
|
export class ClaudeCodeRunner extends AbstractRunner {
|
|
8
8
|
client;
|
|
9
9
|
environment;
|
|
@@ -45,6 +45,20 @@ export class ClaudeCodeRunner extends AbstractRunner {
|
|
|
45
45
|
access: 'default'
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
+
mcpConfiguration(raw, secrets) {
|
|
49
|
+
const servers = super.mcpConfiguration(raw, secrets);
|
|
50
|
+
return Object.fromEntries(servers.map((server) => [
|
|
51
|
+
server.name,
|
|
52
|
+
server.transport === 'STDIO'
|
|
53
|
+
? {
|
|
54
|
+
type: 'stdio',
|
|
55
|
+
command: server.command,
|
|
56
|
+
args: [...(server.args ?? [])],
|
|
57
|
+
env: { ...(server.environment ?? {}) }
|
|
58
|
+
}
|
|
59
|
+
: { type: 'http', url: server.url, headers: { ...(server.headers ?? {}) } }
|
|
60
|
+
]));
|
|
61
|
+
}
|
|
48
62
|
async resumeExternal(external, context) {
|
|
49
63
|
if (external.runner !== this.name ||
|
|
50
64
|
external.nativeControl !== 'EXTERNAL' ||
|
|
@@ -88,11 +102,10 @@ export class ClaudeCodeRunner extends AbstractRunner {
|
|
|
88
102
|
context.rememberManagedNative(external.externalSessionId, configured.id);
|
|
89
103
|
return { session: configured, activity: 'IDLE' };
|
|
90
104
|
}
|
|
91
|
-
catch
|
|
105
|
+
catch {
|
|
92
106
|
return {
|
|
93
107
|
failureCode: 'RUNNER_RESUME_FAILED',
|
|
94
|
-
messageFailureCode: 'SESSION_RESUME_REJECTED'
|
|
95
|
-
failureDetail: externalResumeFailureDetail(error)
|
|
108
|
+
messageFailureCode: 'SESSION_RESUME_REJECTED'
|
|
96
109
|
};
|
|
97
110
|
}
|
|
98
111
|
}
|
|
@@ -32,8 +32,8 @@ export declare function mergeCodexCustomToolItems(invocation: NodeConversationIt
|
|
|
32
32
|
export declare function coalesceImageGenerationItems(items: readonly NodeConversationItem[]): readonly NodeConversationItem[];
|
|
33
33
|
export declare function codexPlanText(value: Record<string, unknown>): string | undefined;
|
|
34
34
|
/**
|
|
35
|
-
* Claude
|
|
36
|
-
*
|
|
35
|
+
* A final Claude tagged Plan Mode plan is fully wrapped in `<proposed_plan>` tags;
|
|
36
|
+
* text with missing or unclosed tags remains a regular assistant message.
|
|
37
37
|
*/
|
|
38
38
|
export declare function codexOfficialItem(session: NodeAgentSession, nativeTurnId: string, value: unknown, turnSequence: number, itemSequence: number, startedAt: number | null, completedAt: number | null): NodeConversationItem | undefined;
|
|
39
39
|
export declare function codexLiveConversationItem(value: Record<string, unknown>, completed: boolean, imageAttachments?: readonly Record<string, unknown>[]): PublicConversationItem | undefined;
|
|
@@ -201,6 +201,11 @@ export function codexOfficialItems(session, nativeTurnId, values, turnSequence,
|
|
|
201
201
|
const item = codexOfficialItem(session, nativeTurnId, value, turnSequence, itemSequence, startedAt, completedAt);
|
|
202
202
|
if (item === undefined)
|
|
203
203
|
continue;
|
|
204
|
+
// Codex persists one compaction as both `compacted` state and a
|
|
205
|
+
// `context_compacted` event. App Server normalizes both records to
|
|
206
|
+
// consecutive `contextCompaction` items without preserving their source.
|
|
207
|
+
if (item.kind === 'context_compaction' && items.at(-1)?.kind === 'context_compaction')
|
|
208
|
+
continue;
|
|
204
209
|
const key = codexPairedToolCallId(value);
|
|
205
210
|
if (key === undefined) {
|
|
206
211
|
items.push(item);
|
|
@@ -295,8 +300,8 @@ export function codexPlanText(value) {
|
|
|
295
300
|
return plan.length > 0 ? plan : undefined;
|
|
296
301
|
}
|
|
297
302
|
/**
|
|
298
|
-
* Claude
|
|
299
|
-
*
|
|
303
|
+
* A final Claude tagged Plan Mode plan is fully wrapped in `<proposed_plan>` tags;
|
|
304
|
+
* text with missing or unclosed tags remains a regular assistant message.
|
|
300
305
|
*/
|
|
301
306
|
export function codexOfficialItem(session, nativeTurnId, value, turnSequence, itemSequence, startedAt, completedAt) {
|
|
302
307
|
if (!isPlainRecord(value) || typeof value.type !== 'string')
|
|
@@ -361,7 +366,7 @@ export function codexOfficialItem(session, nativeTurnId, value, turnSequence, it
|
|
|
361
366
|
payload: {
|
|
362
367
|
command: typeof value.command === 'string'
|
|
363
368
|
? compactRunnerText(value.command, 10_000)
|
|
364
|
-
: '
|
|
369
|
+
: '(No command provided)',
|
|
365
370
|
cwd: typeof value.cwd === 'string' ? compactRunnerText(value.cwd, 4_096) : null,
|
|
366
371
|
outputPreview: compactRunnerText(output, 10_000),
|
|
367
372
|
outputRef: null,
|
|
@@ -415,7 +420,7 @@ export function codexOfficialItem(session, nativeTurnId, value, turnSequence, it
|
|
|
415
420
|
namespace: 'codex',
|
|
416
421
|
toolName: 'imagegen',
|
|
417
422
|
inputSummary: typeof value.revisedPrompt === 'string' ? value.revisedPrompt : null,
|
|
418
|
-
outputSummary: value.status === 'completed' ? '
|
|
423
|
+
outputSummary: value.status === 'completed' ? 'Image generation completed' : 'Image generation failed',
|
|
419
424
|
progressLabel: null,
|
|
420
425
|
errorCode: value.status === 'failed' ? 'IMAGE_GENERATION_FAILED' : null,
|
|
421
426
|
truncated: false
|
|
@@ -456,7 +461,11 @@ export function codexOfficialItem(session, nativeTurnId, value, turnSequence, it
|
|
|
456
461
|
return {
|
|
457
462
|
...base,
|
|
458
463
|
kind: 'context_compaction',
|
|
459
|
-
payload: {
|
|
464
|
+
payload: {
|
|
465
|
+
phase: 'COMPLETED',
|
|
466
|
+
summary: 'Context compacted',
|
|
467
|
+
summaryCode: 'CONTEXT_COMPACTED'
|
|
468
|
+
}
|
|
460
469
|
};
|
|
461
470
|
return codexUnknownOfficialItem(base, value);
|
|
462
471
|
}
|
|
@@ -501,7 +510,7 @@ export function codexLiveConversationItem(value, completed, imageAttachments = [
|
|
|
501
510
|
payload: {
|
|
502
511
|
command: typeof value.command === 'string'
|
|
503
512
|
? compactRunnerText(value.command, 10_000)
|
|
504
|
-
: '
|
|
513
|
+
: '(No command provided)',
|
|
505
514
|
cwd: typeof value.cwd === 'string' ? compactRunnerText(value.cwd, 4_096) : null,
|
|
506
515
|
outputPreview: compactRunnerText(output, 10_000),
|
|
507
516
|
outputRef: null,
|
|
@@ -550,7 +559,8 @@ export function codexLiveConversationItem(value, completed, imageAttachments = [
|
|
|
550
559
|
title: 'Web Search',
|
|
551
560
|
inputSummary: typeof value.query === 'string' ? compactRunnerText(value.query, 10_000) : null,
|
|
552
561
|
outputSummary: null,
|
|
553
|
-
progressLabel: completed ? null : '
|
|
562
|
+
progressLabel: completed ? null : 'Searching',
|
|
563
|
+
progressLabelCode: completed ? null : 'SEARCHING',
|
|
554
564
|
errorCode: null,
|
|
555
565
|
truncated: false
|
|
556
566
|
}
|
|
@@ -575,8 +585,10 @@ export function codexLiveConversationItem(value, completed, imageAttachments = [
|
|
|
575
585
|
inputSummary: typeof value.revisedPrompt === 'string'
|
|
576
586
|
? compactRunnerText(value.revisedPrompt, 10_000)
|
|
577
587
|
: null,
|
|
578
|
-
outputSummary: completed ? '
|
|
579
|
-
|
|
588
|
+
outputSummary: completed ? 'Image generation completed' : null,
|
|
589
|
+
outputSummaryCode: completed ? 'IMAGE_GENERATION_COMPLETED' : null,
|
|
590
|
+
progressLabel: completed ? null : 'Generating image',
|
|
591
|
+
progressLabelCode: completed ? null : 'GENERATING_IMAGE',
|
|
580
592
|
errorCode: value.status === 'failed' ? 'IMAGE_GENERATION_FAILED' : null,
|
|
581
593
|
truncated: false,
|
|
582
594
|
...(imageAttachments.length === 0 ? {} : { attachments: imageAttachments })
|
|
@@ -704,7 +716,9 @@ export function codexImageAttachments(value) {
|
|
|
704
716
|
totalBytes + Buffer.byteLength(dataBase64, 'base64') <= MAX_COMPOSER_ATTACHMENTS_BYTES) {
|
|
705
717
|
totalBytes += Buffer.byteLength(dataBase64, 'base64');
|
|
706
718
|
found.push({
|
|
707
|
-
name: typeof current.name === 'string'
|
|
719
|
+
name: typeof current.name === 'string'
|
|
720
|
+
? current.name
|
|
721
|
+
: `generated-image-${found.length + 1}.png`,
|
|
708
722
|
mime,
|
|
709
723
|
dataBase64
|
|
710
724
|
});
|
|
@@ -802,10 +816,14 @@ export function codexToolOutput(value) {
|
|
|
802
816
|
return [block.content];
|
|
803
817
|
return [];
|
|
804
818
|
});
|
|
805
|
-
return text.length > 0
|
|
819
|
+
return text.length > 0
|
|
820
|
+
? text.join('')
|
|
821
|
+
: value.some(isCodexImageBlock)
|
|
822
|
+
? 'Image generated'
|
|
823
|
+
: value;
|
|
806
824
|
}
|
|
807
825
|
export function codexVisibleToolText(value) {
|
|
808
|
-
return value.replace(/(["']image_url["']\s*:\s*["'])data:image\/[^"']+(["'])/g, '$1[
|
|
826
|
+
return value.replace(/(["']image_url["']\s*:\s*["'])data:image\/[^"']+(["'])/g, '$1[Image content extracted]$2');
|
|
809
827
|
}
|
|
810
828
|
export function isCodexImageBlock(value) {
|
|
811
829
|
if (!isPlainRecord(value))
|