@getpaseo/server 0.1.100 → 0.1.101
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/server/executable-resolution/windows.js +3 -0
- package/dist/server/server/agent/agent-manager.d.ts +10 -0
- package/dist/server/server/agent/agent-manager.js +65 -27
- package/dist/server/server/agent/agent-sdk-types.d.ts +8 -0
- package/dist/server/server/agent/mcp-server.d.ts +2 -45
- package/dist/server/server/agent/mcp-server.js +45 -1985
- package/dist/server/server/agent/prompt-attachments.js +6 -2
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +4 -0
- package/dist/server/server/agent/provider-snapshot-manager.js +58 -13
- package/dist/server/server/agent/providers/acp-agent.d.ts +20 -1
- package/dist/server/server/agent/providers/acp-agent.js +170 -26
- package/dist/server/server/agent/providers/claude/agent.js +60 -10
- package/dist/server/server/agent/providers/codex-app-server-agent.js +6 -57
- package/dist/server/server/agent/providers/diagnostic-utils.d.ts +1 -0
- package/dist/server/server/agent/providers/diagnostic-utils.js +1 -1
- package/dist/server/server/agent/providers/generic-acp-agent.d.ts +3 -0
- package/dist/server/server/agent/providers/generic-acp-agent.js +41 -23
- package/dist/server/server/agent/providers/mock-load-test-agent.js +4 -2
- package/dist/server/server/agent/providers/pi/agent.d.ts +2 -1
- package/dist/server/server/agent/providers/pi/agent.js +3 -0
- package/dist/server/server/agent/providers/provider-image-output.d.ts +5 -0
- package/dist/server/server/agent/providers/provider-image-output.js +55 -0
- package/dist/server/server/agent/tools/paseo-tools.d.ts +48 -0
- package/dist/server/server/agent/tools/paseo-tools.js +2121 -0
- package/dist/server/server/agent/tools/types.d.ts +36 -0
- package/dist/server/server/agent/tools/types.js +2 -0
- package/dist/server/server/bootstrap.js +71 -62
- package/dist/server/server/persisted-config.d.ts +5 -0
- package/dist/server/server/persisted-config.js +10 -2
- package/dist/server/server/session/agent-updates/agent-updates-service.d.ts +59 -0
- package/dist/server/server/session/agent-updates/agent-updates-service.js +220 -0
- package/dist/server/server/session/checkout/checkout-session.d.ts +13 -15
- package/dist/server/server/session/checkout/checkout-session.js +18 -16
- package/dist/server/server/session/checkout/git-metadata-generator.d.ts +53 -0
- package/dist/server/server/session/checkout/git-metadata-generator.js +159 -0
- package/dist/server/server/session/daemon/daemon-session.d.ts +14 -0
- package/dist/server/server/session/daemon/daemon-session.js +38 -0
- package/dist/server/server/session/daemon/diagnostics.d.ts +41 -0
- package/dist/server/server/session/daemon/diagnostics.js +421 -0
- package/dist/server/server/session/git-mutation/git-mutation-service.d.ts +34 -0
- package/dist/server/server/session/git-mutation/git-mutation-service.js +71 -0
- package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.d.ts +36 -0
- package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.js +134 -0
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.d.ts +34 -0
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.js +190 -0
- package/dist/server/server/session/workspace-scripts/workspace-scripts-service.d.ts +41 -0
- package/dist/server/server/session/workspace-scripts/workspace-scripts-service.js +100 -0
- package/dist/server/server/session.d.ts +7 -51
- package/dist/server/server/session.js +113 -938
- package/dist/server/server/speech/providers/openai/config.d.ts +1 -2
- package/dist/server/server/speech/providers/openai/config.js +13 -9
- package/dist/server/server/speech/providers/openai/runtime.js +2 -16
- package/dist/server/server/speech/providers/openai/stt.d.ts +1 -0
- package/dist/server/server/speech/providers/openai/stt.js +4 -2
- package/dist/server/server/speech/providers/openai/tts.d.ts +1 -0
- package/dist/server/server/speech/providers/openai/tts.js +1 -0
- package/dist/server/server/websocket/runtime-metrics.d.ts +20 -0
- package/dist/server/server/websocket-server.d.ts +1 -2
- package/dist/server/server/websocket-server.js +26 -21
- package/dist/server/server/worktree-bootstrap.d.ts +1 -1
- package/dist/server/server/worktree-branch-name-generator.js +3 -1
- package/dist/server/utils/checkout-git.js +51 -26
- package/dist/src/executable-resolution/windows.js +3 -0
- package/dist/src/server/persisted-config.js +10 -2
- package/package.json +5 -5
- package/dist/server/server/speech/providers/openai/realtime-transcription-session.d.ts +0 -42
- package/dist/server/server/speech/providers/openai/realtime-transcription-session.js +0 -168
|
@@ -67,14 +67,18 @@ export function buildAgentBranchNameSeed(firstAgentContext) {
|
|
|
67
67
|
const parts = [];
|
|
68
68
|
const prompt = firstAgentContext.prompt?.trim();
|
|
69
69
|
if (prompt) {
|
|
70
|
-
parts.push(prompt);
|
|
70
|
+
parts.push(["<user-prompt>", prompt, "</user-prompt>"].join("\n"));
|
|
71
71
|
}
|
|
72
|
+
const renderedAttachments = [];
|
|
72
73
|
for (const attachment of firstAgentContext.attachments ?? []) {
|
|
73
74
|
const rendered = renderPromptAttachmentAsText(attachment).trim();
|
|
74
75
|
if (rendered) {
|
|
75
|
-
|
|
76
|
+
renderedAttachments.push(rendered);
|
|
76
77
|
}
|
|
77
78
|
}
|
|
79
|
+
if (renderedAttachments.length > 0) {
|
|
80
|
+
parts.push(["<attachments>", renderedAttachments.join("\n\n"), "</attachments>"].join("\n"));
|
|
81
|
+
}
|
|
78
82
|
return parts.length > 0 ? parts.join("\n\n") : undefined;
|
|
79
83
|
}
|
|
80
84
|
//# sourceMappingURL=prompt-attachments.js.map
|
|
@@ -15,6 +15,7 @@ export interface ProviderSnapshotManagerOptions {
|
|
|
15
15
|
isDev?: boolean;
|
|
16
16
|
extraClients?: Partial<Record<AgentProvider, AgentClient>>;
|
|
17
17
|
refreshTimeoutMs?: number;
|
|
18
|
+
diagnosticTimeoutMs?: number;
|
|
18
19
|
}
|
|
19
20
|
interface ProviderSnapshotRefreshOptions {
|
|
20
21
|
cwd: string;
|
|
@@ -64,6 +65,7 @@ export declare class ProviderSnapshotManager {
|
|
|
64
65
|
private readonly events;
|
|
65
66
|
private destroyed;
|
|
66
67
|
private readonly refreshTimeoutMs;
|
|
68
|
+
private readonly diagnosticTimeoutMs;
|
|
67
69
|
private readonly logger;
|
|
68
70
|
private readonly workspaceGitService?;
|
|
69
71
|
private readonly managedProcesses?;
|
|
@@ -101,6 +103,8 @@ export declare class ProviderSnapshotManager {
|
|
|
101
103
|
private resolveParent;
|
|
102
104
|
private getReadyProvider;
|
|
103
105
|
private requireProvider;
|
|
106
|
+
private refreshDiagnosticSnapshotEntry;
|
|
107
|
+
private getBaseProviderDiagnostic;
|
|
104
108
|
private createLoadingEntries;
|
|
105
109
|
private reconcileSnapshotForRegistry;
|
|
106
110
|
private warmUp;
|
|
@@ -5,8 +5,9 @@ import { expandTilde } from "../../utils/path.js";
|
|
|
5
5
|
import { withTimeout } from "../../utils/promise-timeout.js";
|
|
6
6
|
import { buildProviderRegistry, shutdownAgentClients, } from "./provider-registry.js";
|
|
7
7
|
import { applyMutableProviderConfigToOverrides } from "../daemon-config-store.js";
|
|
8
|
-
import { formatProviderDiagnostic } from "./providers/diagnostic-utils.js";
|
|
9
|
-
const DEFAULT_REFRESH_TIMEOUT_MS =
|
|
8
|
+
import { formatProviderDiagnostic, formatProviderDiagnosticError, } from "./providers/diagnostic-utils.js";
|
|
9
|
+
const DEFAULT_REFRESH_TIMEOUT_MS = 60000;
|
|
10
|
+
const DEFAULT_DIAGNOSTIC_TIMEOUT_MS = 120000;
|
|
10
11
|
const REFRESH_TIMEOUT_ENV_VAR = "PASEO_PROVIDER_REFRESH_TIMEOUT_MS";
|
|
11
12
|
// Provider refresh probes can be slow on cold starts (e.g. Copilot's first
|
|
12
13
|
// `copilot --acp` invocation, OpenCode workspace probes with many MCP servers).
|
|
@@ -25,6 +26,12 @@ function resolveRefreshTimeoutMs(option) {
|
|
|
25
26
|
}
|
|
26
27
|
return DEFAULT_REFRESH_TIMEOUT_MS;
|
|
27
28
|
}
|
|
29
|
+
function resolveDiagnosticTimeoutMs(option, refreshTimeoutMs) {
|
|
30
|
+
if (typeof option === "number" && Number.isFinite(option) && option > 0) {
|
|
31
|
+
return option;
|
|
32
|
+
}
|
|
33
|
+
return Math.max(refreshTimeoutMs, DEFAULT_DIAGNOSTIC_TIMEOUT_MS);
|
|
34
|
+
}
|
|
28
35
|
export class ProviderSnapshotManager {
|
|
29
36
|
constructor(options) {
|
|
30
37
|
this.snapshots = new Map();
|
|
@@ -40,6 +47,7 @@ export class ProviderSnapshotManager {
|
|
|
40
47
|
this.providerOverrides = options.providerOverrides;
|
|
41
48
|
this.baseProviderOverrides = options.providerOverrides;
|
|
42
49
|
this.refreshTimeoutMs = resolveRefreshTimeoutMs(options.refreshTimeoutMs);
|
|
50
|
+
this.diagnosticTimeoutMs = resolveDiagnosticTimeoutMs(options.diagnosticTimeoutMs, this.refreshTimeoutMs);
|
|
43
51
|
this.providerRegistry = this.buildRegistry();
|
|
44
52
|
this.providerClients = { ...this.extraClients };
|
|
45
53
|
}
|
|
@@ -180,18 +188,23 @@ export class ProviderSnapshotManager {
|
|
|
180
188
|
});
|
|
181
189
|
}
|
|
182
190
|
async getProviderDiagnostic(provider) {
|
|
183
|
-
const definition = this.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
191
|
+
const definition = this.providerRegistry[provider];
|
|
192
|
+
if (!definition) {
|
|
193
|
+
return {
|
|
194
|
+
provider,
|
|
195
|
+
diagnostic: formatProviderDiagnostic(provider, [
|
|
196
|
+
{ label: "Error", value: `Provider ${provider} is not configured` },
|
|
197
|
+
]),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const baseDiagnosticPromise = this.getBaseProviderDiagnostic(provider, definition);
|
|
201
|
+
const snapshotEntryPromise = this.refreshDiagnosticSnapshotEntry(provider, definition);
|
|
202
|
+
const [baseDiagnostic, entry] = await Promise.all([
|
|
203
|
+
baseDiagnosticPromise,
|
|
204
|
+
snapshotEntryPromise,
|
|
205
|
+
]);
|
|
188
206
|
const modelCount = entry.status === "ready" ? String(entry.models?.length ?? 0) : "—";
|
|
189
207
|
const status = formatProviderStatus(entry);
|
|
190
|
-
const baseDiagnostic = client.getDiagnostic
|
|
191
|
-
? (await client.getDiagnostic()).diagnostic
|
|
192
|
-
: formatProviderDiagnostic(definition.label ?? provider, [
|
|
193
|
-
{ label: "Diagnostic", value: "No diagnostic available" },
|
|
194
|
-
]);
|
|
195
208
|
const diagnostic = `${baseDiagnostic}\n Models: ${modelCount}\n Status: ${status}`;
|
|
196
209
|
return { provider, diagnostic };
|
|
197
210
|
}
|
|
@@ -283,6 +296,38 @@ export class ProviderSnapshotManager {
|
|
|
283
296
|
}
|
|
284
297
|
return definition;
|
|
285
298
|
}
|
|
299
|
+
async refreshDiagnosticSnapshotEntry(provider, definition) {
|
|
300
|
+
try {
|
|
301
|
+
const cwd = resolveSnapshotCwd();
|
|
302
|
+
await this.refreshSnapshotForCwd({ cwd, providers: [provider] });
|
|
303
|
+
return await this.getProvider({ cwd, provider, wait: false });
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
return {
|
|
307
|
+
provider,
|
|
308
|
+
status: "error",
|
|
309
|
+
enabled: definition.enabled,
|
|
310
|
+
label: definition.label,
|
|
311
|
+
description: definition.description,
|
|
312
|
+
defaultModeId: definition.defaultModeId,
|
|
313
|
+
error: toErrorMessage(error),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async getBaseProviderDiagnostic(provider, definition) {
|
|
318
|
+
try {
|
|
319
|
+
const client = this.ensureClient(provider, definition);
|
|
320
|
+
if (client.getDiagnostic) {
|
|
321
|
+
return (await withTimeout(client.getDiagnostic(), this.diagnosticTimeoutMs, `Timed out collecting ${definition.label ?? provider} diagnostic after ${this.diagnosticTimeoutMs}ms`)).diagnostic;
|
|
322
|
+
}
|
|
323
|
+
return formatProviderDiagnostic(definition.label ?? provider, [
|
|
324
|
+
{ label: "Diagnostic", value: "No diagnostic available" },
|
|
325
|
+
]);
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
return formatProviderDiagnosticError(definition.label ?? provider, error);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
286
331
|
createLoadingEntries() {
|
|
287
332
|
const entries = new Map();
|
|
288
333
|
for (const provider of this.getProviderIds()) {
|
|
@@ -454,7 +499,7 @@ export class ProviderSnapshotManager {
|
|
|
454
499
|
setEntry({ ...base, status: "unavailable", enabled: true });
|
|
455
500
|
return;
|
|
456
501
|
}
|
|
457
|
-
const catalog = await withTimeout(definition.fetchCatalog({ cwd, force }, client), this.refreshTimeoutMs, `Timed out refreshing ${definition.label} after ${this.refreshTimeoutMs}ms`);
|
|
502
|
+
const catalog = await withTimeout(definition.fetchCatalog({ cwd, force, timeoutMs: this.refreshTimeoutMs }, client), this.refreshTimeoutMs, `Timed out refreshing ${definition.label} after ${this.refreshTimeoutMs}ms`);
|
|
458
503
|
setEntry({
|
|
459
504
|
...base,
|
|
460
505
|
status: "ready",
|
|
@@ -5,6 +5,7 @@ import { ClientSideConnection, type Client as ACPClient, type CreateTerminalRequ
|
|
|
5
5
|
import type { Logger } from "pino";
|
|
6
6
|
import { type AgentCapabilityFlags, type AgentClient, type AgentFeature, type AgentLaunchContext, type AgentMode, type AgentModelDefinition, type AgentPermissionRequest, type AgentPermissionResponse, type AgentPersistenceHandle, type AgentPromptInput, type AgentRunOptions, type AgentRunResult, type AgentRuntimeInfo, type AgentSession, type AgentSessionConfig, type AgentSlashCommand, type AgentStreamEvent, type AgentUsage, type FetchCatalogOptions, type ImportableProviderSession, type ImportProviderSessionContext, type ImportProviderSessionInput, type ListImportableSessionsOptions, type ProviderCatalog } from "../agent-sdk-types.js";
|
|
7
7
|
import { type ProviderRuntimeSettings } from "../provider-launch-config.js";
|
|
8
|
+
import { type DiagnosticEntry } from "./diagnostic-utils.js";
|
|
8
9
|
export declare function summarizeACPRequestError(error: unknown): {
|
|
9
10
|
message: string;
|
|
10
11
|
code?: string;
|
|
@@ -62,6 +63,17 @@ export interface SpawnedACPProcess {
|
|
|
62
63
|
child: ChildProcessWithoutNullStreams;
|
|
63
64
|
connection: ClientSideConnection;
|
|
64
65
|
initialize: InitializeResponse;
|
|
66
|
+
stderrChunks?: string[];
|
|
67
|
+
}
|
|
68
|
+
type UninitializedACPProcess = Omit<SpawnedACPProcess, "initialize"> & {
|
|
69
|
+
initialize?: InitializeResponse;
|
|
70
|
+
};
|
|
71
|
+
interface ACPProcessTransport {
|
|
72
|
+
child: ChildProcessWithoutNullStreams;
|
|
73
|
+
connection: ClientSideConnection;
|
|
74
|
+
stderrChunks: string[];
|
|
75
|
+
spawnReady: Promise<void>;
|
|
76
|
+
spawnError: Promise<never>;
|
|
65
77
|
}
|
|
66
78
|
export interface ACPToolSnapshot {
|
|
67
79
|
toolCallId: string;
|
|
@@ -176,10 +188,17 @@ export declare class ACPAgentClient implements AgentClient {
|
|
|
176
188
|
isAvailable(): Promise<boolean>;
|
|
177
189
|
protected spawnProcess(launchEnv?: Record<string, string>, options?: {
|
|
178
190
|
initializeTimeoutMs?: number;
|
|
191
|
+
onSpawned?: (probe: UninitializedACPProcess) => void;
|
|
179
192
|
}): Promise<SpawnedACPProcess>;
|
|
193
|
+
protected spawnTransport(launchEnv?: Record<string, string>): Promise<ACPProcessTransport>;
|
|
194
|
+
protected initializeTransport(transport: ACPProcessTransport, initializeTimeoutMs?: number): Promise<InitializeResponse>;
|
|
180
195
|
protected buildProbeClient(): ACPClient;
|
|
181
|
-
protected closeProbe(probe:
|
|
196
|
+
protected closeProbe(probe: UninitializedACPProcess): Promise<void>;
|
|
182
197
|
protected runACPRequest<T>(request: () => Promise<T>): Promise<T>;
|
|
198
|
+
protected buildACPProbeDiagnosticRows(options?: {
|
|
199
|
+
cwd?: string;
|
|
200
|
+
phaseTimeoutMs?: number;
|
|
201
|
+
}): Promise<DiagnosticEntry[]>;
|
|
183
202
|
protected resolveLaunchCommand(): Promise<{
|
|
184
203
|
command: string;
|
|
185
204
|
args: string[];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { Readable, Writable } from "node:stream";
|
|
5
6
|
import { terminateWithTreeKill } from "../../../utils/tree-kill.js";
|
|
@@ -10,6 +11,8 @@ import { checkProviderLaunchAvailable, createProviderEnvSpec, resolveProviderLau
|
|
|
10
11
|
import { renderPromptAttachmentAsText } from "../prompt-attachments.js";
|
|
11
12
|
import { appendOrReplaceGrowingAssistantMessage, runProviderTurn } from "./provider-runner.js";
|
|
12
13
|
import { platformShell, spawnProcess } from "../../../utils/spawn.js";
|
|
14
|
+
import { toDiagnosticErrorMessage, truncateForDiagnostic, } from "./diagnostic-utils.js";
|
|
15
|
+
import { withTimeout } from "../../../utils/promise-timeout.js";
|
|
13
16
|
function assertChildWithPipes(child) {
|
|
14
17
|
if (!child.stdin || !child.stdout || !child.stderr) {
|
|
15
18
|
throw new Error("Child process did not expose stdio pipes");
|
|
@@ -70,6 +73,19 @@ function resolveTerminalCommand(command, args) {
|
|
|
70
73
|
const shell = platformShell();
|
|
71
74
|
return { command: shell.command, args: [...shell.flag, command] };
|
|
72
75
|
}
|
|
76
|
+
function formatDurationMs(startedAt) {
|
|
77
|
+
return `${Math.max(0, Date.now() - startedAt)}ms`;
|
|
78
|
+
}
|
|
79
|
+
function pushACPStderrRow(rows, stderrChunks) {
|
|
80
|
+
const stderr = stderrChunks.join("").trim();
|
|
81
|
+
if (!stderr) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
rows.push({
|
|
85
|
+
label: "ACP stderr",
|
|
86
|
+
value: truncateForDiagnostic(stderr),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
73
89
|
export const DEFAULT_ACP_CAPABILITIES = {
|
|
74
90
|
supportsStreaming: true,
|
|
75
91
|
supportsSessionPersistence: true,
|
|
@@ -96,6 +112,8 @@ const ACP_CLIENT_CAPABILITIES = {
|
|
|
96
112
|
// sign-in URL in the browser) when probing an ACP agent for models/modes.
|
|
97
113
|
// NO_BROWSER is honored by Gemini CLI; other ACP agents ignore it.
|
|
98
114
|
const PROBE_ENV = { NO_BROWSER: "true" };
|
|
115
|
+
const ACP_CATALOG_TIMEOUT_MS = 60000;
|
|
116
|
+
const ACP_DIAGNOSTIC_PHASE_TIMEOUT_MS = 20000;
|
|
99
117
|
function summarizeMalformedACPStdoutError(error) {
|
|
100
118
|
return {
|
|
101
119
|
type: error instanceof Error ? error.name : typeof error,
|
|
@@ -369,22 +387,35 @@ export class ACPAgentClient {
|
|
|
369
387
|
}
|
|
370
388
|
async fetchCatalog(options) {
|
|
371
389
|
const { cwd } = options;
|
|
372
|
-
const
|
|
390
|
+
const timeoutMs = options.timeoutMs ?? ACP_CATALOG_TIMEOUT_MS;
|
|
391
|
+
let probe = null;
|
|
373
392
|
try {
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
393
|
+
const catalogProbe = (async () => {
|
|
394
|
+
const initializedProbe = await this.spawnProcess(PROBE_ENV, {
|
|
395
|
+
initializeTimeoutMs: timeoutMs,
|
|
396
|
+
onSpawned: (spawned) => {
|
|
397
|
+
probe = spawned;
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
probe = initializedProbe;
|
|
401
|
+
const response = await this.runACPRequest(() => initializedProbe.connection.newSession({
|
|
402
|
+
cwd,
|
|
403
|
+
mcpServers: [],
|
|
404
|
+
}));
|
|
405
|
+
const transformed = this.transformSessionResponse(response);
|
|
406
|
+
const models = deriveModelDefinitionsFromACP(this.provider, transformed.models, transformed.configOptions);
|
|
407
|
+
const modeInfo = deriveModesFromACP(this.defaultModes, transformed.modes, transformed.configOptions);
|
|
408
|
+
return {
|
|
409
|
+
models: this.modelTransformer ? this.modelTransformer(models) : models,
|
|
410
|
+
modes: modeInfo.modes,
|
|
411
|
+
};
|
|
412
|
+
})();
|
|
413
|
+
return await withTimeout(catalogProbe, timeoutMs, `ACP catalog probe timed out after ${timeoutMs}ms`);
|
|
385
414
|
}
|
|
386
415
|
finally {
|
|
387
|
-
|
|
416
|
+
if (probe) {
|
|
417
|
+
await this.closeProbe(probe);
|
|
418
|
+
}
|
|
388
419
|
}
|
|
389
420
|
}
|
|
390
421
|
async listFeatures(config) {
|
|
@@ -461,6 +492,28 @@ export class ACPAgentClient {
|
|
|
461
492
|
}
|
|
462
493
|
}
|
|
463
494
|
async spawnProcess(launchEnv, options) {
|
|
495
|
+
const transport = await this.spawnTransport(launchEnv);
|
|
496
|
+
const probe = {
|
|
497
|
+
child: transport.child,
|
|
498
|
+
connection: transport.connection,
|
|
499
|
+
stderrChunks: transport.stderrChunks,
|
|
500
|
+
};
|
|
501
|
+
options?.onSpawned?.(probe);
|
|
502
|
+
try {
|
|
503
|
+
const initialize = await this.initializeTransport(transport, options?.initializeTimeoutMs);
|
|
504
|
+
const initializedProbe = {
|
|
505
|
+
...probe,
|
|
506
|
+
initialize,
|
|
507
|
+
};
|
|
508
|
+
probe.initialize = initialize;
|
|
509
|
+
return initializedProbe;
|
|
510
|
+
}
|
|
511
|
+
catch (error) {
|
|
512
|
+
await terminateChildProcess(transport.child, 2000, this.terminateProcess);
|
|
513
|
+
throw error;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
async spawnTransport(launchEnv) {
|
|
464
517
|
const { command, args } = await this.resolveLaunchCommand();
|
|
465
518
|
const child = spawnProcess(command, args, {
|
|
466
519
|
cwd: process.cwd(),
|
|
@@ -481,38 +534,46 @@ export class ACPAgentClient {
|
|
|
481
534
|
reject(new Error(stderr ? `${String(error)}\n${stderr}` : String(error)));
|
|
482
535
|
});
|
|
483
536
|
});
|
|
537
|
+
const spawnReadyPromise = new Promise((resolve) => {
|
|
538
|
+
child.once("spawn", () => {
|
|
539
|
+
resolve();
|
|
540
|
+
});
|
|
541
|
+
});
|
|
484
542
|
const stream = createLoggedNdJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout), { logger: this.logger, provider: this.provider });
|
|
485
543
|
const connection = new ClientSideConnection(() => this.buildProbeClient(), stream);
|
|
544
|
+
return {
|
|
545
|
+
child,
|
|
546
|
+
connection,
|
|
547
|
+
stderrChunks,
|
|
548
|
+
spawnReady: spawnReadyPromise,
|
|
549
|
+
spawnError: spawnErrorPromise,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
async initializeTransport(transport, initializeTimeoutMs) {
|
|
486
553
|
let timeout = null;
|
|
487
|
-
const initializeTimeoutPromise =
|
|
554
|
+
const initializeTimeoutPromise = initializeTimeoutMs
|
|
488
555
|
? new Promise((_, reject) => {
|
|
489
556
|
timeout = setTimeout(() => {
|
|
490
|
-
reject(new Error(`ACP initialize timed out after ${
|
|
491
|
-
},
|
|
557
|
+
reject(new Error(`ACP initialize timed out after ${initializeTimeoutMs}ms`));
|
|
558
|
+
}, initializeTimeoutMs);
|
|
492
559
|
})
|
|
493
560
|
: null;
|
|
494
|
-
let initialize;
|
|
495
561
|
try {
|
|
496
|
-
|
|
497
|
-
connection.initialize({
|
|
562
|
+
return await this.runACPRequest(() => Promise.race([
|
|
563
|
+
transport.connection.initialize({
|
|
498
564
|
protocolVersion: PROTOCOL_VERSION,
|
|
499
565
|
clientCapabilities: ACP_CLIENT_CAPABILITIES,
|
|
500
566
|
clientInfo: { name: "Paseo", version: "dev" },
|
|
501
567
|
}),
|
|
502
|
-
|
|
568
|
+
transport.spawnError,
|
|
503
569
|
...(initializeTimeoutPromise ? [initializeTimeoutPromise] : []),
|
|
504
570
|
]));
|
|
505
571
|
}
|
|
506
|
-
catch (error) {
|
|
507
|
-
await terminateChildProcess(child, 2000, this.terminateProcess);
|
|
508
|
-
throw error;
|
|
509
|
-
}
|
|
510
572
|
finally {
|
|
511
573
|
if (timeout) {
|
|
512
574
|
clearTimeout(timeout);
|
|
513
575
|
}
|
|
514
576
|
}
|
|
515
|
-
return { child, connection, initialize };
|
|
516
577
|
}
|
|
517
578
|
buildProbeClient() {
|
|
518
579
|
return {
|
|
@@ -536,7 +597,7 @@ export class ACPAgentClient {
|
|
|
536
597
|
}
|
|
537
598
|
async closeProbe(probe) {
|
|
538
599
|
try {
|
|
539
|
-
if (probe.initialize
|
|
600
|
+
if (probe.initialize?.agentCapabilities?.sessionCapabilities?.close) {
|
|
540
601
|
// No active session to close here; ignore capability.
|
|
541
602
|
}
|
|
542
603
|
}
|
|
@@ -552,6 +613,89 @@ export class ACPAgentClient {
|
|
|
552
613
|
throw toACPRequestError(error);
|
|
553
614
|
}
|
|
554
615
|
}
|
|
616
|
+
async buildACPProbeDiagnosticRows(options = {}) {
|
|
617
|
+
const rows = [];
|
|
618
|
+
const phaseTimeoutMs = options.phaseTimeoutMs ?? ACP_DIAGNOSTIC_PHASE_TIMEOUT_MS;
|
|
619
|
+
const cwd = options.cwd ?? homedir();
|
|
620
|
+
let transport = null;
|
|
621
|
+
try {
|
|
622
|
+
const spawnStartedAt = Date.now();
|
|
623
|
+
try {
|
|
624
|
+
transport = await this.spawnTransport(PROBE_ENV);
|
|
625
|
+
await withTimeout(Promise.race([transport.spawnReady, transport.spawnError]), phaseTimeoutMs, `ACP spawn timed out after ${phaseTimeoutMs}ms`);
|
|
626
|
+
rows.push({
|
|
627
|
+
label: "ACP spawn",
|
|
628
|
+
value: `ok (${formatDurationMs(spawnStartedAt)})`,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
catch (error) {
|
|
632
|
+
rows.push({
|
|
633
|
+
label: "ACP spawn",
|
|
634
|
+
value: `error: ${toDiagnosticErrorMessage(error)}`,
|
|
635
|
+
});
|
|
636
|
+
return rows;
|
|
637
|
+
}
|
|
638
|
+
const activeTransport = transport;
|
|
639
|
+
const initializeStartedAt = Date.now();
|
|
640
|
+
try {
|
|
641
|
+
await this.initializeTransport(activeTransport, phaseTimeoutMs);
|
|
642
|
+
rows.push({
|
|
643
|
+
label: "ACP initialize",
|
|
644
|
+
value: `ok (${formatDurationMs(initializeStartedAt)})`,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
catch (error) {
|
|
648
|
+
rows.push({
|
|
649
|
+
label: "ACP initialize",
|
|
650
|
+
value: `error: ${toDiagnosticErrorMessage(error)}`,
|
|
651
|
+
});
|
|
652
|
+
pushACPStderrRow(rows, activeTransport.stderrChunks);
|
|
653
|
+
return rows;
|
|
654
|
+
}
|
|
655
|
+
const sessionStartedAt = Date.now();
|
|
656
|
+
try {
|
|
657
|
+
const response = await withTimeout(this.runACPRequest(() => activeTransport.connection.newSession({
|
|
658
|
+
cwd,
|
|
659
|
+
mcpServers: [],
|
|
660
|
+
})), phaseTimeoutMs, `ACP session/new timed out after ${phaseTimeoutMs}ms`);
|
|
661
|
+
const transformed = this.transformSessionResponse(response);
|
|
662
|
+
const models = deriveModelDefinitionsFromACP(this.provider, transformed.models, transformed.configOptions);
|
|
663
|
+
const modeInfo = deriveModesFromACP(this.defaultModes, transformed.modes, transformed.configOptions);
|
|
664
|
+
rows.push({
|
|
665
|
+
label: "ACP session/new",
|
|
666
|
+
value: `ok (${formatDurationMs(sessionStartedAt)}; models=${models.length}; modes=${modeInfo.modes.length})`,
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
catch (error) {
|
|
670
|
+
rows.push({
|
|
671
|
+
label: "ACP session/new",
|
|
672
|
+
value: `error: ${toDiagnosticErrorMessage(error)}`,
|
|
673
|
+
});
|
|
674
|
+
pushACPStderrRow(rows, activeTransport.stderrChunks);
|
|
675
|
+
return rows;
|
|
676
|
+
}
|
|
677
|
+
pushACPStderrRow(rows, activeTransport.stderrChunks);
|
|
678
|
+
return rows;
|
|
679
|
+
}
|
|
680
|
+
finally {
|
|
681
|
+
if (transport) {
|
|
682
|
+
const cleanupStartedAt = Date.now();
|
|
683
|
+
try {
|
|
684
|
+
await terminateChildProcess(transport.child, 2000, this.terminateProcess);
|
|
685
|
+
rows.push({
|
|
686
|
+
label: "ACP cleanup",
|
|
687
|
+
value: `ok (${formatDurationMs(cleanupStartedAt)})`,
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
catch (error) {
|
|
691
|
+
rows.push({
|
|
692
|
+
label: "ACP cleanup",
|
|
693
|
+
value: `error: ${toDiagnosticErrorMessage(error)}`,
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
555
699
|
async resolveLaunchCommand() {
|
|
556
700
|
const prefix = await resolveProviderLaunch({
|
|
557
701
|
commandConfig: this.runtimeSettings?.command,
|
|
@@ -17,6 +17,7 @@ import { realClaudeRewindSdk, revertClaudeConversation, revertClaudeFiles } from
|
|
|
17
17
|
import { normalizeProviderReplayTimestamp } from "../../provider-history-timestamps.js";
|
|
18
18
|
import { claudeProjectDirSync } from "./project-dir.js";
|
|
19
19
|
import { SETTING_APPLIES_NEXT_TURN_NOTICE } from "../../provider-notices.js";
|
|
20
|
+
import { isProviderImageMarkdown, materializeProviderImage, renderProviderImageOutputAsAssistantMarkdown, } from "../provider-image-output.js";
|
|
20
21
|
import { getAgentStreamEventTurnId, } from "../../agent-sdk-types.js";
|
|
21
22
|
import { importSessionFromPersistence } from "../../provider-session-import.js";
|
|
22
23
|
import { checkProviderLaunchAvailable, createProviderEnv, createProviderEnvSpec, resolveProviderLaunch, } from "../../provider-launch-config.js";
|
|
@@ -370,6 +371,39 @@ function coerceToolResultContentToString(content) {
|
|
|
370
371
|
}
|
|
371
372
|
return deterministicStringify(content);
|
|
372
373
|
}
|
|
374
|
+
function toBase64ImageOutput(block) {
|
|
375
|
+
const record = toObjectRecord(block);
|
|
376
|
+
if (!record || record.type !== "image") {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
const source = toObjectRecord(record.source);
|
|
380
|
+
if (!source || source.type !== "base64" || typeof source.data !== "string") {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
return {
|
|
384
|
+
data: source.data,
|
|
385
|
+
mimeType: typeof source.media_type === "string" ? source.media_type : null,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
// Claude returns images inside tool_result content as base64 Anthropic blocks. Left in place they
|
|
389
|
+
// reach coerceToolResultContentToString, which JSON.stringifies the whole array — dumping base64
|
|
390
|
+
// into the tool output. We pull those blocks out to render them as image markdown and leave a
|
|
391
|
+
// "[image]" placeholder so image-only results still produce non-empty output.
|
|
392
|
+
function splitClaudeToolResultImages(content) {
|
|
393
|
+
if (!Array.isArray(content)) {
|
|
394
|
+
return { images: [], text: content };
|
|
395
|
+
}
|
|
396
|
+
const images = [];
|
|
397
|
+
const text = content.map((block) => {
|
|
398
|
+
const image = toBase64ImageOutput(block);
|
|
399
|
+
if (image) {
|
|
400
|
+
images.push(image);
|
|
401
|
+
return { type: "text", text: "[image]" };
|
|
402
|
+
}
|
|
403
|
+
return block;
|
|
404
|
+
});
|
|
405
|
+
return { images, text };
|
|
406
|
+
}
|
|
373
407
|
function normalizeClaudeTranscriptText(value) {
|
|
374
408
|
if (typeof value !== "string") {
|
|
375
409
|
return null;
|
|
@@ -3472,15 +3506,17 @@ class ClaudeAgentSession {
|
|
|
3472
3506
|
const callId = typeof block.tool_use_id === "string" && block.tool_use_id.length > 0
|
|
3473
3507
|
? block.tool_use_id
|
|
3474
3508
|
: (entry?.id ?? null);
|
|
3475
|
-
//
|
|
3476
|
-
|
|
3509
|
+
// Pull image blocks out of the result so base64 never reaches the tool output, and render each
|
|
3510
|
+
// one as an assistant_message markdown image after the tool_call (matching how Codex emits).
|
|
3511
|
+
const { images, text } = splitClaudeToolResultImages(block.content);
|
|
3512
|
+
const output = this.buildToolOutput(text, block, entry);
|
|
3477
3513
|
if (block.is_error) {
|
|
3478
3514
|
this.pushToolCall(mapClaudeFailedToolCall({
|
|
3479
3515
|
name: toolName,
|
|
3480
3516
|
callId,
|
|
3481
3517
|
input: entry?.input ?? null,
|
|
3482
3518
|
output: output ?? null,
|
|
3483
|
-
error: block,
|
|
3519
|
+
error: { ...block, content: text },
|
|
3484
3520
|
}), items);
|
|
3485
3521
|
}
|
|
3486
3522
|
else {
|
|
@@ -3491,12 +3527,20 @@ class ClaudeAgentSession {
|
|
|
3491
3527
|
output: output ?? null,
|
|
3492
3528
|
}), items);
|
|
3493
3529
|
}
|
|
3530
|
+
for (const image of images) {
|
|
3531
|
+
const imageItem = renderProviderImageOutputAsAssistantMarkdown(image, {
|
|
3532
|
+
materialize: materializeProviderImage,
|
|
3533
|
+
});
|
|
3534
|
+
if (imageItem) {
|
|
3535
|
+
items.push(imageItem);
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3494
3538
|
if (typeof block.tool_use_id === "string") {
|
|
3495
3539
|
this.toolUseCache.delete(block.tool_use_id);
|
|
3496
3540
|
this.sidechainTracker.delete(block.tool_use_id);
|
|
3497
3541
|
}
|
|
3498
3542
|
}
|
|
3499
|
-
buildToolOutput(block, entry) {
|
|
3543
|
+
buildToolOutput(content, block, entry) {
|
|
3500
3544
|
if (block.is_error) {
|
|
3501
3545
|
return undefined;
|
|
3502
3546
|
}
|
|
@@ -3504,23 +3548,23 @@ class ClaudeAgentSession {
|
|
|
3504
3548
|
const blockToolName = typeof block.tool_name === "string" ? block.tool_name : undefined;
|
|
3505
3549
|
const server = entry?.server ?? blockServer ?? "tool";
|
|
3506
3550
|
const tool = entry?.name ?? blockToolName ?? "tool";
|
|
3507
|
-
const
|
|
3551
|
+
const coercedContent = coerceToolResultContentToString(content);
|
|
3508
3552
|
const input = entry?.input;
|
|
3509
3553
|
// Build structured result based on tool type
|
|
3510
|
-
const structured = this.buildStructuredToolResult(server, tool,
|
|
3554
|
+
const structured = this.buildStructuredToolResult(server, tool, coercedContent, input);
|
|
3511
3555
|
if (structured) {
|
|
3512
3556
|
return structured;
|
|
3513
3557
|
}
|
|
3514
3558
|
// Fallback format - try to parse JSON first
|
|
3515
3559
|
const result = {};
|
|
3516
|
-
if (
|
|
3560
|
+
if (coercedContent.length > 0) {
|
|
3517
3561
|
try {
|
|
3518
3562
|
// If content is a JSON string, parse it
|
|
3519
|
-
result.output = JSON.parse(
|
|
3563
|
+
result.output = JSON.parse(coercedContent);
|
|
3520
3564
|
}
|
|
3521
3565
|
catch {
|
|
3522
3566
|
// If not JSON, return unchanged (no extra wrapping)
|
|
3523
|
-
result.output =
|
|
3567
|
+
result.output = coercedContent;
|
|
3524
3568
|
}
|
|
3525
3569
|
}
|
|
3526
3570
|
// Preserve file changes tracked during tool execution
|
|
@@ -3945,6 +3989,9 @@ function convertClaudeHistoryEntryPreamble(entry) {
|
|
|
3945
3989
|
}
|
|
3946
3990
|
return { proceed: { content } };
|
|
3947
3991
|
}
|
|
3992
|
+
function isProviderImageMessage(item) {
|
|
3993
|
+
return item.type === "assistant_message" && isProviderImageMarkdown(item.text);
|
|
3994
|
+
}
|
|
3948
3995
|
export function convertClaudeHistoryEntry(entry, mapBlocks) {
|
|
3949
3996
|
const preamble = convertClaudeHistoryEntryPreamble(entry);
|
|
3950
3997
|
if ("shortCircuit" in preamble) {
|
|
@@ -3980,7 +4027,10 @@ export function convertClaudeHistoryEntry(entry, mapBlocks) {
|
|
|
3980
4027
|
if (hasToolBlock && normalizedBlocks) {
|
|
3981
4028
|
const mapped = mapBlocks(normalizedBlocks);
|
|
3982
4029
|
if (entry.type === "user") {
|
|
3983
|
-
|
|
4030
|
+
// tool_result handling (handleToolResult) emits image markdown as an assistant_message
|
|
4031
|
+
// alongside the tool_call. User-entry text blocks also map to assistant_message in this path
|
|
4032
|
+
// and must stay suppressed, so keep tool_calls plus only the image assistant_messages.
|
|
4033
|
+
const toolItems = mapped.filter((item) => item.type === "tool_call" || isProviderImageMessage(item));
|
|
3984
4034
|
return timeline.length ? [...timeline, ...toolItems] : toolItems;
|
|
3985
4035
|
}
|
|
3986
4036
|
return mapped;
|