mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-10 20:50:08 +02:00
Keep selected model and skill paths current across disposable connection homes. Preserve private Grok history and assigned OpenCode and Hermes workspaces. Fix artifact helper paths and cover the boundaries with regression checks. Co-Authored-By: Paperclip <noreply@paperclip.ing>
250 lines
11 KiB
Diff
250 lines
11 KiB
Diff
diff --git a/dist/live-checkpoint-ClPCSdrW.js b/dist/live-checkpoint-ClPCSdrW.js
|
|
index 243c9d13bcba520923b63adfddad75cf2d94362d..b93d4b29edfce81c8eb3c769699712f490553506 100644
|
|
--- a/dist/live-checkpoint-ClPCSdrW.js
|
|
+++ b/dist/live-checkpoint-ClPCSdrW.js
|
|
@@ -1532,7 +1532,7 @@ const ZED_TAG_KEYS = /* @__PURE__ */ new Set([
|
|
"RedactedThinking",
|
|
"ToolUse"
|
|
]);
|
|
-const MAP_OBJECT_PATHS = /* @__PURE__ */ new Set(["request_token_usage", "messages.Agent.tool_results"]);
|
|
+const MAP_OBJECT_PATHS = /* @__PURE__ */ new Set(["request_token_usage", "messages.Agent.tool_results", "acpx.session_options.env"]);
|
|
const OPAQUE_VALUE_PATHS = /* @__PURE__ */ new Set([
|
|
"agent_capabilities",
|
|
"messages.Agent.content.ToolUse.input",
|
|
@@ -2557,7 +2557,7 @@ function readCommandLineChar(state) {
|
|
escaping: false,
|
|
hasPart: true
|
|
};
|
|
- if (state.ch === "\\" && state.quote !== "'") return {
|
|
+ if (process.platform !== "win32" && state.ch === "\\" && state.quote !== "'") return {
|
|
current: state.current,
|
|
quote: state.quote,
|
|
escaping: true,
|
|
@@ -2912,8 +2912,8 @@ function promotePrefixedAuthEnvironment(env) {
|
|
}
|
|
return protectedKeys;
|
|
}
|
|
-function buildAgentEnvironment(authCredentials, sessionEnv) {
|
|
- const env = { ...process.env };
|
|
+function buildAgentEnvironment(authCredentials, sessionEnv, inheritProcessEnv = true) {
|
|
+ const env = inheritProcessEnv ? { ...process.env } : {};
|
|
const protectedAuthEnvKeys = promotePrefixedAuthEnvironment(env);
|
|
if (authCredentials) for (const [methodId, credential] of Object.entries(authCredentials)) {
|
|
addAuthCredentialEnvKeys(protectedAuthEnvKeys, methodId, credential);
|
|
@@ -2955,10 +2955,10 @@ function resolveConfiguredAuthCredential(methodId, authCredentials) {
|
|
const configCredentials = authCredentials ?? {};
|
|
return configCredentials[methodId] ?? configCredentials[toEnvToken(methodId)];
|
|
}
|
|
-function buildAgentSpawnOptions(cwd, authCredentials, sessionEnv) {
|
|
+function buildAgentSpawnOptions(cwd, authCredentials, sessionEnv, inheritProcessEnv) {
|
|
return {
|
|
cwd,
|
|
- env: buildAgentEnvironment(authCredentials, sessionEnv),
|
|
+ env: buildAgentEnvironment(authCredentials, sessionEnv, inheritProcessEnv),
|
|
stdio: [
|
|
"pipe",
|
|
"pipe",
|
|
@@ -3724,10 +3724,13 @@ function resolveClientCapabilities(params) {
|
|
},
|
|
terminal: params.terminal
|
|
};
|
|
- if (!params.devinAcp) return baseCapabilities;
|
|
+ const typedSessionFailureMeta = {
|
|
+ jetbrains: { air: { version: 1, capabilities: ["sessionFailure"] } }
|
|
+ };
|
|
+ if (!params.devinAcp) return { ...baseCapabilities, _meta: typedSessionFailureMeta };
|
|
return {
|
|
...baseCapabilities,
|
|
- _meta: DEVIN_COMPATIBILITY_CLIENT_CAPABILITIES_META
|
|
+ _meta: { ...typedSessionFailureMeta, ...DEVIN_COMPATIBILITY_CLIENT_CAPABILITIES_META }
|
|
};
|
|
}
|
|
function isDevinRequestDiagnosticsMethod(method) {
|
|
@@ -3957,9 +3960,26 @@ var AcpClient = class {
|
|
this.lastAgentExit = void 0;
|
|
this.lastKnownPid = child.pid ?? void 0;
|
|
this.attachAgentLifecycleObservers(child);
|
|
+ if (this.options.onAgentSpawn) {
|
|
+ try {
|
|
+ if (typeof child.pid !== "number" || child.pid <= 0) {
|
|
+ throw new Error("ACPX agent spawn did not expose a valid process id.");
|
|
+ }
|
|
+ await this.options.onAgentSpawn({ pid: child.pid, startedAt: this.agentStartedAt });
|
|
+ } catch (error) {
|
|
+ try {
|
|
+ child.kill("SIGKILL");
|
|
+ } catch {}
|
|
+ throw error;
|
|
+ }
|
|
+ }
|
|
const startupStderr = [];
|
|
child.stderr.on("data", (chunk) => {
|
|
this.captureStartupStderr(startupStderr, chunk);
|
|
+ if (this.options.onAgentStderr) {
|
|
+ this.options.onAgentStderr(chunk.toString());
|
|
+ return;
|
|
+ }
|
|
if (!this.options.verbose) return;
|
|
process.stderr.write(chunk);
|
|
});
|
|
@@ -3994,7 +4014,7 @@ var AcpClient = class {
|
|
geminiAcp: isGeminiAcpCommand(spawnCommand, args),
|
|
copilotAcp: isCopilotAcpCommand(spawnCommand, args),
|
|
claudeAcp: isClaudeAcpCommand(spawnCommand, args),
|
|
- spawnOptions: buildAgentSpawnOptions(this.options.cwd, this.options.authCredentials, this.options.sessionOptions?.env)
|
|
+ spawnOptions: buildAgentSpawnOptions(this.options.spawnCwd ?? this.options.cwd, this.options.authCredentials, this.options.sessionOptions?.env, this.options.inheritProcessEnv)
|
|
};
|
|
}
|
|
logAgentLaunch(plan) {
|
|
@@ -4729,6 +4749,9 @@ var AcpClient = class {
|
|
return await this.filesystem.readTextFile(params);
|
|
} catch (error) {
|
|
this.recordPermissionError(params.sessionId, error);
|
|
+ // Preserve missing-file semantics across JSON-RPC. Gemini checks for
|
|
+ // ENOENT before creating a new file; a generic Internal error hides it.
|
|
+ if (error && typeof error === "object" && error.code === "ENOENT") throw RequestError.resourceNotFound(params.path);
|
|
throw error;
|
|
}
|
|
}
|
|
@@ -6070,6 +6093,27 @@ async function withConnectedSession(options) {
|
|
//#region src/runtime/engine/prompt-turn.ts
|
|
const SESSION_REPLY_IDLE_MS = 1e3;
|
|
const SESSION_REPLY_DRAIN_TIMEOUT_MS = 5e3;
|
|
+const TYPED_SESSION_FAILURE_CATEGORIES = /* @__PURE__ */ new Set([
|
|
+ "connection",
|
|
+ "access",
|
|
+ "limit",
|
|
+ "service",
|
|
+ "request",
|
|
+ "unknown"
|
|
+]);
|
|
+function typedTerminalSessionFailureCategory(response) {
|
|
+ if (response === null || typeof response !== "object" || Array.isArray(response)) return null;
|
|
+ const meta = response._meta;
|
|
+ if (meta === null || typeof meta !== "object" || Array.isArray(meta)) return null;
|
|
+ const jetbrains = meta.jetbrains;
|
|
+ if (jetbrains === null || typeof jetbrains !== "object" || Array.isArray(jetbrains)) return null;
|
|
+ const air = jetbrains.air;
|
|
+ if (air === null || typeof air !== "object" || Array.isArray(air)) return null;
|
|
+ if (!Number.isInteger(air.version) || air.version < 1) return null;
|
|
+ const failure = air.sessionFailure;
|
|
+ if (failure === null || typeof failure !== "object" || Array.isArray(failure) || failure.severity !== "error") return null;
|
|
+ return typeof failure.category === "string" && TYPED_SESSION_FAILURE_CATEGORIES.has(failure.category) ? failure.category : "unknown";
|
|
+}
|
|
async function runPromptTurn(params) {
|
|
try {
|
|
const promptPromise = params.client.prompt(params.sessionId, params.prompt);
|
|
@@ -6079,6 +6123,20 @@ async function runPromptTurn(params) {
|
|
idleMs: SESSION_REPLY_IDLE_MS,
|
|
timeoutMs: SESSION_REPLY_DRAIN_TIMEOUT_MS
|
|
}).catch(() => {});
|
|
+ const terminalFailureCategory = typedTerminalSessionFailureCategory(response);
|
|
+ if (terminalFailureCategory !== null) {
|
|
+ // Pass complete provider text to the diagnostic callback. Consumers redact
|
|
+ // before bounding it; truncating here can split and expose a credential.
|
|
+ const failure = response._meta.jetbrains.air.sessionFailure;
|
|
+ try {
|
|
+ params.onTerminalSessionFailure?.({
|
|
+ category: terminalFailureCategory,
|
|
+ ...(typeof failure.title === "string" ? { title: failure.title } : {}),
|
|
+ ...(typeof failure.details === "string" ? { details: failure.details } : {})
|
|
+ });
|
|
+ } catch {}
|
|
+ throw new Error(`ACP agent reported a terminal ${terminalFailureCategory} failure.`);
|
|
+ }
|
|
recordPromptResponseUsage(params.conversation, response.usage, params.promptMessageId);
|
|
return {
|
|
stopReason: response.stopReason,
|
|
diff --git a/dist/runtime.d.ts b/dist/runtime.d.ts
|
|
index ccdbe5b032521518022223733049b8b38793473b..416bdaae1450c6e85b34afd154349770f9f7b0a9 100644
|
|
--- a/dist/runtime.d.ts
|
|
+++ b/dist/runtime.d.ts
|
|
@@ -45,6 +45,7 @@ type AcpRuntimeTurnAttachment = {
|
|
data: string;
|
|
};
|
|
type AcpRuntimeTurnInput = {
|
|
+ onTerminalSessionFailure?: (failure: { category: string; title?: string; details?: string }) => void;
|
|
handle: AcpRuntimeHandle;
|
|
text: string;
|
|
attachments?: AcpRuntimeTurnAttachment[];
|
|
@@ -266,6 +267,10 @@ type AcpRuntimeOptions = {
|
|
timeoutMs?: number;
|
|
probeAgent?: string;
|
|
verbose?: boolean;
|
|
+ onAgentStderr?: (chunk: string) => void;
|
|
+ onAgentSpawn?: (meta: { pid: number; startedAt: string }) => Promise<void>;
|
|
+ spawnCwd?: string;
|
|
+ inheritProcessEnv?: boolean;
|
|
onPermissionRequest?: (req: AcpPermissionRequest, ctx: {
|
|
signal: AbortSignal;
|
|
}) => Promise<AcpPermissionDecision | undefined>;
|
|
@@ -302,6 +307,7 @@ declare class AcpRuntimeManager {
|
|
private createAndSaveRuntimeRecord;
|
|
private keepPersistentClient;
|
|
startTurn(input: {
|
|
+ onTerminalSessionFailure?: (failure: { category: string; title?: string; details?: string }) => void;
|
|
handle: AcpRuntimeHandle;
|
|
text: string;
|
|
attachments?: AcpRuntimeTurnAttachment[];
|
|
@@ -334,6 +340,7 @@ declare class AcpRuntimeManager {
|
|
private finalizeRuntimeTurn;
|
|
private finalizeRuntimeTurnRecord;
|
|
runTurn(input: {
|
|
+ onTerminalSessionFailure?: (failure: { category: string; title?: string; details?: string }) => void;
|
|
handle: AcpRuntimeHandle;
|
|
text: string;
|
|
attachments?: AcpRuntimeTurnAttachment[];
|
|
diff --git a/dist/runtime.js b/dist/runtime.js
|
|
index 6c9cc999e50a11c399c68b3a0f1b7af4bc2317c0..e06bf4fdee534f3ef56be0387a2f5f8626e697ad 100644
|
|
--- a/dist/runtime.js
|
|
+++ b/dist/runtime.js
|
|
@@ -744,7 +744,8 @@ var AcpRuntimeManager = class {
|
|
this.deps = deps;
|
|
}
|
|
createClient(options) {
|
|
- return this.deps.clientFactory?.(options) ?? new AcpClient(options);
|
|
+ const clientOptions = { ...options, onAgentStderr: this.options.onAgentStderr, onAgentSpawn: this.options.onAgentSpawn, spawnCwd: this.options.spawnCwd, inheritProcessEnv: this.options.inheritProcessEnv };
|
|
+ return this.deps.clientFactory?.(clientOptions) ?? new AcpClient(clientOptions);
|
|
}
|
|
async readPendingPersistentClient(record, options) {
|
|
const pendingClient = this.pendingPersistentClients.get(record.acpxRecordId);
|
|
@@ -982,6 +983,7 @@ var AcpRuntimeManager = class {
|
|
client: turn.client,
|
|
sessionId,
|
|
prompt: task.promptInput,
|
|
+ onTerminalSessionFailure: task.input.onTerminalSessionFailure,
|
|
timeoutMs: task.input.timeoutMs ?? this.options.timeoutMs,
|
|
conversation: turn.conversation,
|
|
promptMessageId: turn.promptMessageId
|
|
@@ -1586,6 +1588,7 @@ var AcpxRuntime = class {
|
|
const turnPromise = this.getManager().then((manager) => manager.startTurn({
|
|
handle,
|
|
text: input.text,
|
|
+ onTerminalSessionFailure: input.onTerminalSessionFailure,
|
|
attachments: input.attachments,
|
|
mode: input.mode,
|
|
sessionMode: state.mode,
|
|
@@ -1614,6 +1617,7 @@ var AcpxRuntime = class {
|
|
yield* (await this.getManager()).runTurn({
|
|
handle,
|
|
text: input.text,
|
|
+ onTerminalSessionFailure: input.onTerminalSessionFailure,
|
|
attachments: input.attachments,
|
|
mode: input.mode,
|
|
sessionMode: state.mode,
|
|
diff --git a/dist/session-options-jkYbBxGE.d.ts b/dist/session-options-jkYbBxGE.d.ts
|
|
index 9d37f377fb6a0828e0d2bc5a48754f3aa71509a4..22dda59e0b7616a20ad45218913e5d8f884bae49 100644
|
|
--- a/dist/session-options-jkYbBxGE.d.ts
|
|
+++ b/dist/session-options-jkYbBxGE.d.ts
|
|
@@ -84,6 +84,10 @@ type AcpClientOptions = {
|
|
terminal?: boolean;
|
|
suppressSdkConsoleErrors?: boolean;
|
|
verbose?: boolean;
|
|
+ onAgentStderr?: (chunk: string) => void;
|
|
+ onAgentSpawn?: (meta: { pid: number; startedAt: string }) => Promise<void>;
|
|
+ spawnCwd?: string;
|
|
+ inheritProcessEnv?: boolean;
|
|
sessionOptions?: {
|
|
model?: string;
|
|
allowedTools?: string[];
|