@tickernelz/paperclip-pro-adapter-utils 2026.1005.10 → 2026.1006.1
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 +13 -0
- package/dist/acpx-engine/ephemeral-session-environment.d.ts +4 -0
- package/dist/acpx-engine/ephemeral-session-environment.d.ts.map +1 -0
- package/dist/acpx-engine/ephemeral-session-environment.js +29 -0
- package/dist/acpx-engine/ephemeral-session-environment.js.map +1 -0
- package/dist/acpx-engine/ephemeral-session-environment.test.d.ts +2 -0
- package/dist/acpx-engine/ephemeral-session-environment.test.d.ts.map +1 -0
- package/dist/acpx-engine/ephemeral-session-environment.test.js +177 -0
- package/dist/acpx-engine/ephemeral-session-environment.test.js.map +1 -0
- package/dist/acpx-engine/execute.d.ts +2 -6
- package/dist/acpx-engine/execute.d.ts.map +1 -1
- package/dist/acpx-engine/execute.js +125 -62
- package/dist/acpx-engine/execute.js.map +1 -1
- package/dist/acpx-engine/execute.test.js +299 -3
- package/dist/acpx-engine/execute.test.js.map +1 -1
- package/dist/acpx-engine/session-codec.d.ts.map +1 -1
- package/dist/acpx-engine/session-codec.js +1 -0
- package/dist/acpx-engine/session-codec.js.map +1 -1
- package/dist/acpx-engine/settlement-characterization.test.js +105 -0
- package/dist/acpx-engine/settlement-characterization.test.js.map +1 -1
- package/dist/acpx-engine/spawn-smoke.test.js +16 -7
- package/dist/acpx-engine/spawn-smoke.test.js.map +1 -1
- package/dist/acpx-engine/startup-cancellation.d.ts +1 -0
- package/dist/acpx-engine/startup-cancellation.d.ts.map +1 -1
- package/dist/acpx-engine/startup-cancellation.js +7 -2
- package/dist/acpx-engine/startup-cancellation.js.map +1 -1
- package/dist/acpx-engine/terminal-session-failure.d.ts +18 -0
- package/dist/acpx-engine/terminal-session-failure.d.ts.map +1 -0
- package/dist/acpx-engine/terminal-session-failure.js +106 -0
- package/dist/acpx-engine/terminal-session-failure.js.map +1 -0
- package/dist/acpx-engine/terminal-session-failure.test.d.ts +2 -0
- package/dist/acpx-engine/terminal-session-failure.test.d.ts.map +1 -0
- package/dist/acpx-engine/terminal-session-failure.test.js +139 -0
- package/dist/acpx-engine/terminal-session-failure.test.js.map +1 -0
- package/dist/acpx-engine/turn-characterization.test.js +4 -3
- package/dist/acpx-engine/turn-characterization.test.js.map +1 -1
- package/dist/command-managed-runtime.d.ts +2 -0
- package/dist/command-managed-runtime.d.ts.map +1 -1
- package/dist/command-managed-runtime.js +2 -0
- package/dist/command-managed-runtime.js.map +1 -1
- package/dist/command-redaction.d.ts +2 -0
- package/dist/command-redaction.d.ts.map +1 -1
- package/dist/command-redaction.js +20 -10
- package/dist/command-redaction.js.map +1 -1
- package/dist/command-redaction.test.js +20 -1
- package/dist/command-redaction.test.js.map +1 -1
- package/dist/directory-merge-lock.test.d.ts +2 -0
- package/dist/directory-merge-lock.test.d.ts.map +1 -0
- package/dist/directory-merge-lock.test.js +166 -0
- package/dist/directory-merge-lock.test.js.map +1 -0
- package/dist/execution-phase.d.ts +10 -0
- package/dist/execution-phase.d.ts.map +1 -0
- package/dist/execution-phase.js +27 -0
- package/dist/execution-phase.js.map +1 -0
- package/dist/execution-phase.test.d.ts +2 -0
- package/dist/execution-phase.test.d.ts.map +1 -0
- package/dist/execution-phase.test.js +26 -0
- package/dist/execution-phase.test.js.map +1 -0
- package/dist/execution-target-sandbox.test.js +49 -7
- package/dist/execution-target-sandbox.test.js.map +1 -1
- package/dist/execution-target-stdin-race.test.js +245 -2
- package/dist/execution-target-stdin-race.test.js.map +1 -1
- package/dist/execution-target.d.ts +6 -0
- package/dist/execution-target.d.ts.map +1 -1
- package/dist/execution-target.js +74 -22
- package/dist/execution-target.js.map +1 -1
- package/dist/git-workspace-sync.d.ts +15 -5
- package/dist/git-workspace-sync.d.ts.map +1 -1
- package/dist/git-workspace-sync.js +267 -124
- package/dist/git-workspace-sync.js.map +1 -1
- package/dist/git-workspace-sync.test.js +296 -20
- package/dist/git-workspace-sync.test.js.map +1 -1
- package/dist/github-launcher.d.ts.map +1 -1
- package/dist/github-launcher.js +26 -11
- package/dist/github-launcher.js.map +1 -1
- package/dist/github-launcher.test.js +34 -1
- package/dist/github-launcher.test.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/paperclip-runner-permissions.d.ts +28 -1
- package/dist/paperclip-runner-permissions.d.ts.map +1 -1
- package/dist/paperclip-runner-permissions.js +9 -1
- package/dist/paperclip-runner-permissions.js.map +1 -1
- package/dist/prompt-sections.test.d.ts +2 -0
- package/dist/prompt-sections.test.d.ts.map +1 -0
- package/dist/prompt-sections.test.js +53 -0
- package/dist/prompt-sections.test.js.map +1 -0
- package/dist/provider-stopped-boundary.d.ts +7 -0
- package/dist/provider-stopped-boundary.d.ts.map +1 -0
- package/dist/provider-stopped-boundary.js +22 -0
- package/dist/provider-stopped-boundary.js.map +1 -0
- package/dist/provider-stopped-boundary.test.d.ts +2 -0
- package/dist/provider-stopped-boundary.test.d.ts.map +1 -0
- package/dist/provider-stopped-boundary.test.js +76 -0
- package/dist/provider-stopped-boundary.test.js.map +1 -0
- package/dist/remote-managed-runtime.d.ts +2 -0
- package/dist/remote-managed-runtime.d.ts.map +1 -1
- package/dist/remote-managed-runtime.js +3 -1
- package/dist/remote-managed-runtime.js.map +1 -1
- package/dist/remote-managed-runtime.test.js +25 -0
- package/dist/remote-managed-runtime.test.js.map +1 -1
- package/dist/sandbox-callback-bridge.d.ts +2 -0
- package/dist/sandbox-callback-bridge.d.ts.map +1 -1
- package/dist/sandbox-callback-bridge.js +34 -10
- package/dist/sandbox-callback-bridge.js.map +1 -1
- package/dist/sandbox-callback-bridge.test.js +106 -3
- package/dist/sandbox-callback-bridge.test.js.map +1 -1
- package/dist/sandbox-managed-runtime.d.ts +3 -0
- package/dist/sandbox-managed-runtime.d.ts.map +1 -1
- package/dist/sandbox-managed-runtime.js +749 -718
- package/dist/sandbox-managed-runtime.js.map +1 -1
- package/dist/sandbox-managed-runtime.test.js +171 -2
- package/dist/sandbox-managed-runtime.test.js.map +1 -1
- package/dist/server-utils.d.ts +43 -1
- package/dist/server-utils.d.ts.map +1 -1
- package/dist/server-utils.js +103 -31
- package/dist/server-utils.js.map +1 -1
- package/dist/server-utils.test.js +235 -45
- package/dist/server-utils.test.js.map +1 -1
- package/dist/session-compaction.d.ts.map +1 -1
- package/dist/session-compaction.js +6 -0
- package/dist/session-compaction.js.map +1 -1
- package/dist/ssh-fixture.test.js +22 -0
- package/dist/ssh-fixture.test.js.map +1 -1
- package/dist/ssh.d.ts +2 -0
- package/dist/ssh.d.ts.map +1 -1
- package/dist/ssh.js +2 -2
- package/dist/ssh.js.map +1 -1
- package/dist/test-fixtures/prompt-context.d.ts +54 -0
- package/dist/test-fixtures/prompt-context.d.ts.map +1 -0
- package/dist/test-fixtures/prompt-context.js +55 -0
- package/dist/test-fixtures/prompt-context.js.map +1 -0
- package/dist/types.d.ts +12 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/workspace-git-stream.d.ts +19 -0
- package/dist/workspace-git-stream.d.ts.map +1 -0
- package/dist/workspace-git-stream.js +99 -0
- package/dist/workspace-git-stream.js.map +1 -0
- package/dist/workspace-git-stream.test.d.ts +2 -0
- package/dist/workspace-git-stream.test.d.ts.map +1 -0
- package/dist/workspace-git-stream.test.js +71 -0
- package/dist/workspace-git-stream.test.js.map +1 -0
- package/dist/workspace-manifest.d.ts +61 -0
- package/dist/workspace-manifest.d.ts.map +1 -0
- package/dist/workspace-manifest.js +257 -0
- package/dist/workspace-manifest.js.map +1 -0
- package/dist/workspace-manifest.test.d.ts +2 -0
- package/dist/workspace-manifest.test.d.ts.map +1 -0
- package/dist/workspace-manifest.test.js +160 -0
- package/dist/workspace-manifest.test.js.map +1 -0
- package/dist/workspace-restore-diagnostics.d.ts +34 -0
- package/dist/workspace-restore-diagnostics.d.ts.map +1 -0
- package/dist/workspace-restore-diagnostics.js +238 -0
- package/dist/workspace-restore-diagnostics.js.map +1 -0
- package/dist/workspace-restore-diagnostics.test.d.ts +2 -0
- package/dist/workspace-restore-diagnostics.test.d.ts.map +1 -0
- package/dist/workspace-restore-diagnostics.test.js +258 -0
- package/dist/workspace-restore-diagnostics.test.js.map +1 -0
- package/dist/workspace-restore-merge.d.ts +47 -6
- package/dist/workspace-restore-merge.d.ts.map +1 -1
- package/dist/workspace-restore-merge.js +382 -114
- package/dist/workspace-restore-merge.js.map +1 -1
- package/dist/workspace-restore-merge.test.js +253 -4
- package/dist/workspace-restore-merge.test.js.map +1 -1
- package/dist/workspace-restore-result.d.ts +6 -0
- package/dist/workspace-restore-result.d.ts.map +1 -0
- package/dist/workspace-restore-result.js +72 -0
- package/dist/workspace-restore-result.js.map +1 -0
- package/dist/workspace-restore-result.test.d.ts +2 -0
- package/dist/workspace-restore-result.test.d.ts.map +1 -0
- package/dist/workspace-restore-result.test.js +74 -0
- package/dist/workspace-restore-result.test.js.map +1 -0
- package/dist/workspace-restore-teardown.d.ts.map +1 -1
- package/dist/workspace-restore-teardown.js +5 -3
- package/dist/workspace-restore-teardown.js.map +1 -1
- package/dist/workspace-restore-teardown.test.js +24 -0
- package/dist/workspace-restore-teardown.test.js.map +1 -1
- package/node_modules/acpx/dist/live-checkpoint-ClPCSdrW.js +4 -3
- package/package.json +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cancellableSandboxStartup } from "./startup-cancellation.js";
|
|
2
|
+
import { withAdapterExecutionPhase } from "../execution-phase.js";
|
|
2
3
|
import fs from "node:fs/promises";
|
|
3
4
|
import fsSync from "node:fs";
|
|
4
5
|
import os from "node:os";
|
|
@@ -6,16 +7,19 @@ import path from "node:path";
|
|
|
6
7
|
import { execFile } from "node:child_process";
|
|
7
8
|
import { promisify } from "node:util";
|
|
8
9
|
import { createHash, randomUUID } from "node:crypto";
|
|
10
|
+
import { parseFrontmatterMarkdown } from "@tickernelz/paperclip-pro-shared";
|
|
9
11
|
import { fileURLToPath } from "node:url";
|
|
10
12
|
import { adapterExecutionTargetSessionIdentity, describeAdapterExecutionTarget, adapterExecutionTargetDuplexObservabilityRecorder, adapterExecutionTargetEnablesSandboxDuplexBridge, formatAdapterExecutionTimeoutErrorMessage, formatAdapterExecutionTimeoutStartLogLine, prepareAdapterExecutionTargetRuntime, readAdapterExecutionTarget, resolveAdapterExecutionTargetTimeout, resolveReferencedSourceIgnore, runAdapterExecutionTargetShellCommand, startAdapterExecutionTargetPaperclipBridge, startAdapterExecutionTargetProcessSessionBridge, } from "@tickernelz/paperclip-pro-adapter-utils/execution-target";
|
|
11
13
|
import { captureLocalProcess, capturedProcessExited, killCapturedLocalProcess } from "./local-process-control.js";
|
|
12
14
|
import { DUPLEX_CHANNEL_LOST_ERROR_CODE } from "../bridge-transport-contract.js";
|
|
15
|
+
import { classifyToolDefinitionFailure, formatTerminalSessionFailure, sanitizeTerminalSessionFailure, } from "./terminal-session-failure.js";
|
|
13
16
|
import { classifyWorkspaceRestoreFailure, describeWorkspaceRestoreFailure, } from "../workspace-restore-merge.js";
|
|
14
|
-
import { paperclipAgentPromptTemplate, DEFAULT_PAPERCLIP_CONVERSATION_PROMPT_TEMPLATE, applyPaperclipWorkspaceEnv, asNumber, asString, buildInvocationEnvForLogs, buildPaperclipEnv, ensureAbsoluteDirectory, ensurePathInEnv, ensurePaperclipSkillSymlink, isForbiddenConfigEnvKey, isPaperclipExternalChatTurn, isPaperclipRuntimeEnvKey, joinPromptSections, materializePaperclipSkillCopy, parseObject, isPaperclipSkillSourceMissing, readPaperclipRuntimeSkillEntries, readPaperclipIssueWorkModeFromContext,
|
|
17
|
+
import { paperclipAgentPromptTemplate, DEFAULT_PAPERCLIP_CONVERSATION_PROMPT_TEMPLATE, applyPaperclipWorkspaceEnv, asNumber, asString, buildInvocationEnvForLogs, buildPaperclipEnv, ensureAbsoluteDirectory, ensurePathInEnv, ensurePaperclipSkillSymlink, isForbiddenConfigEnvKey, isPaperclipExternalChatTurn, isPaperclipRuntimeEnvKey, joinPromptSections, materializePaperclipSkillCopy, parseObject, isPaperclipSkillSourceMissing, readPaperclipRuntimeSkillEntries, readPaperclipIssueWorkModeFromContext, renderTemplate, resolvePaperclipInstanceRootForAdapter, hydrateFreshSessionHandoff, selectInitialCommunicationGuidance, selectPaperclipPromptSections, resolveLegacyPaperclipDesiredSkillNames, removeMaintainerOnlySkillSymlinks, rewriteWorkspaceCwdEnvVarsForExecution, shapePaperclipWorkspaceEnvForExecution, } from "@tickernelz/paperclip-pro-adapter-utils/server-utils";
|
|
15
18
|
import { PAPERCLIP_MCP_SERVER_NAME, paperclipAccessGuidance, paperclipAccessMode, paperclipMcpToolsets, } from "@tickernelz/paperclip-pro-adapter-utils/paperclip-mcp";
|
|
16
19
|
import { paperclipMcpHttpTarget } from "@tickernelz/paperclip-pro-adapter-utils/paperclip-mcp-mount";
|
|
17
20
|
import { shellQuote } from "@tickernelz/paperclip-pro-adapter-utils/ssh";
|
|
18
21
|
import { runToolProfile } from "@tickernelz/paperclip-pro-adapter-utils/tool-profile";
|
|
22
|
+
import { createEphemeralSessionEnvironmentStore } from "./ephemeral-session-environment.js";
|
|
19
23
|
import { createAcpRuntime, createAgentRegistry, createRuntimeStore, isAcpRuntimeError, } from "acpx/runtime";
|
|
20
24
|
import { ACPX_DUPLEX_LOSS_CANCEL_DEADLINE_MS, ACPX_HANDSHAKE_TIMEOUT_MS, ACPX_HANDSHAKE_TRANSPORT_POLL_MS, DEFAULT_ACP_ENGINE_AGENT, DEFAULT_ACP_ENGINE_MODE, DEFAULT_ACP_ENGINE_NON_INTERACTIVE_PERMISSIONS, DEFAULT_ACP_ENGINE_PERMISSION_MODE, DEFAULT_ACP_ENGINE_TIMEOUT_SEC, DEFAULT_ACP_ENGINE_WARM_HANDLE_IDLE_MS, } from "./constants.js";
|
|
21
25
|
import { createRunResourceLedger } from "./run-resource-ledger.js";
|
|
@@ -337,6 +341,9 @@ function isCodexCredentialStoreHome(homePath) {
|
|
|
337
341
|
const segments = resolved.slice(companiesRoot.length + 1).split(path.sep);
|
|
338
342
|
return segments.length >= 2 && segments[1] === "codex-auth-cache";
|
|
339
343
|
}
|
|
344
|
+
function resolveManagedCodexApiKeyHomeDir(companyId, agentId) {
|
|
345
|
+
return path.join(defaultStateDir(companyId, agentId), "codex-home");
|
|
346
|
+
}
|
|
340
347
|
// Mirrors `resolveManagedGrokHomeDir` in
|
|
341
348
|
// `packages/adapters/grok-local/src/server/grok-home.ts` — this package
|
|
342
349
|
// cannot import that adapter package (it would invert the dependency
|
|
@@ -627,11 +634,25 @@ async function prepareClaudeSkillRuntime(input) {
|
|
|
627
634
|
}
|
|
628
635
|
}
|
|
629
636
|
const selectedNames = materializedNames.sort();
|
|
637
|
+
const skillDescriptions = await Promise.all(selectedNames.map(async (name) => {
|
|
638
|
+
const file = path.join(skillsHome, name, "SKILL.md");
|
|
639
|
+
let description = "";
|
|
640
|
+
try {
|
|
641
|
+
const parsed = parseFrontmatterMarkdown(await fs.readFile(file, "utf8"));
|
|
642
|
+
description = asString(parsed.frontmatter.description, "").replace(/\s+/g, " ").trim().slice(0, 512);
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
// A readable skill without valid routing metadata remains available.
|
|
646
|
+
// Do not substitute its body for a missing description.
|
|
647
|
+
}
|
|
648
|
+
return `- ${name}${description ? `: ${description}` : ""} (file: ${file})`;
|
|
649
|
+
}));
|
|
630
650
|
const promptInstructions = selectedNames.length > 0
|
|
631
651
|
? [
|
|
632
652
|
"Paperclip has materialized selected runtime skills for this ACPX Claude session.",
|
|
633
653
|
`Skill root: ${skillsHome}`,
|
|
634
654
|
`Selected skills: ${selectedNames.join(", ")}`,
|
|
655
|
+
...skillDescriptions,
|
|
635
656
|
"When a task calls for one of these skills, read its SKILL.md from that root and follow it.",
|
|
636
657
|
].join("\n")
|
|
637
658
|
: "";
|
|
@@ -725,8 +746,10 @@ async function prepareCodexSkillRuntime(input) {
|
|
|
725
746
|
: process.env.OPENAI_API_KEY?.trim() || process.env.CODEX_API_KEY?.trim();
|
|
726
747
|
const apiKey = input.env.OPENAI_API_KEY?.trim() || input.env.CODEX_API_KEY?.trim()
|
|
727
748
|
|| hostApiKey;
|
|
749
|
+
// Keep API-key auth separate from the company home used by subscription
|
|
750
|
+
// agents, so one agent cannot switch another agent's login on its next turn.
|
|
728
751
|
const managedCodexHome = apiKey
|
|
729
|
-
?
|
|
752
|
+
? resolveManagedCodexApiKeyHomeDir(input.companyId, input.agentId)
|
|
730
753
|
: resolveManagedCodexHomeDir(input.companyId);
|
|
731
754
|
const effectiveCodexHome = configuredCodexHome ??
|
|
732
755
|
await prepareManagedCodexHome({
|
|
@@ -945,6 +968,7 @@ function buildSessionParams(input) {
|
|
|
945
968
|
mode: prepared.mode,
|
|
946
969
|
stateDir: prepared.stateDir,
|
|
947
970
|
configFingerprint: prepared.fingerprint,
|
|
971
|
+
mcpFingerprint: shortHash(prepared.mcpIdentity),
|
|
948
972
|
...(prepared.requestedModel ? { model: prepared.requestedModel } : {}),
|
|
949
973
|
...(prepared.requestedThinkingEffort ? { thinkingEffort: prepared.requestedThinkingEffort } : {}),
|
|
950
974
|
...(prepared.fastMode ? { fastMode: true } : {}),
|
|
@@ -1379,7 +1403,11 @@ async function buildRuntime(input) {
|
|
|
1379
1403
|
paperclipClaudeSettings = await writePaperclipClaudeSettings({
|
|
1380
1404
|
cwd,
|
|
1381
1405
|
stateDir,
|
|
1382
|
-
|
|
1406
|
+
// A run's writable AGENT_HOME moves, but the existing permission root
|
|
1407
|
+
// must not invalidate its resumable conversation. Run copies are covered
|
|
1408
|
+
// by the company root already granted in these settings; the process env
|
|
1409
|
+
// still receives this run's actual AGENT_HOME.
|
|
1410
|
+
agentHome: asString(workspaceContext.agentHomeForPermissions, agentHome),
|
|
1383
1411
|
companyId: agent.companyId,
|
|
1384
1412
|
});
|
|
1385
1413
|
skillCommandNotes.push(`Wrote Paperclip-managed Claude settings to ${paperclipClaudeSettings.filePath} (defaultMode=${paperclipClaudeSettings.defaultMode}${paperclipClaudeSettings.overrodeDontAsk ? "; overrode user dontAsk" : ""}, +${paperclipClaudeSettings.additionalDirectories.length} read root(s), +${paperclipClaudeSettings.allow.length} allow rule(s)).`);
|
|
@@ -1510,7 +1538,9 @@ async function buildRuntime(input) {
|
|
|
1510
1538
|
defaultMode: paperclipClaudeSettings.defaultMode,
|
|
1511
1539
|
}
|
|
1512
1540
|
: null,
|
|
1513
|
-
|
|
1541
|
+
// Qualified built-in ACP harnesses reconnect to the supplied MCP servers
|
|
1542
|
+
// on session/load. Keep their conversation identity independent of tools.
|
|
1543
|
+
mcpServers: ["claude", "codex", "grok", "gemini", "kimi"].includes(acpxAgent) ? [] : mcpIdentity,
|
|
1514
1544
|
...(toolProfile === "read_only" ? { toolProfile } : {}),
|
|
1515
1545
|
secretManifestHash: shortHash(secretManifest),
|
|
1516
1546
|
// Fold the resolved adapter env (all applied user-configured values —
|
|
@@ -2204,16 +2234,13 @@ async function buildPrompt(ctx, resumedSession, env, paperclipAccess, paperclipT
|
|
|
2204
2234
|
const renderedBootstrapPrompt = !resumedSession && bootstrapPromptTemplate.trim().length > 0
|
|
2205
2235
|
? renderTemplate(bootstrapPromptTemplate, templateData).trim()
|
|
2206
2236
|
: "";
|
|
2207
|
-
|
|
2208
|
-
const
|
|
2209
|
-
const wakePrompt = renderPaperclipWakePrompt(context.paperclipWake, {
|
|
2237
|
+
await hydrateFreshSessionHandoff(ctx, { resumedSession });
|
|
2238
|
+
const { taskContextNote, wakePrompt } = selectPaperclipPromptSections(context, {
|
|
2210
2239
|
resumedSession,
|
|
2211
|
-
|
|
2212
|
-
// The task-context markdown is the authoritative brief on this lane; keep
|
|
2213
|
-
// the wake prompt's description copy out so the prompt carries it once.
|
|
2214
|
-
suppressIssueDescription: taskContextNote.length > 0,
|
|
2240
|
+
includeCommunicationGuidance: false,
|
|
2215
2241
|
paperclipAccess,
|
|
2216
2242
|
});
|
|
2243
|
+
const externalChatTurn = isPaperclipExternalChatTurn(context.paperclipWake);
|
|
2217
2244
|
const shouldUseResumeDeltaPrompt = resumedSession && wakePrompt.length > 0;
|
|
2218
2245
|
const promptInstructionsPrefix = shouldUseResumeDeltaPrompt ? "" : instructionsPrefix;
|
|
2219
2246
|
const renderedPrompt = shouldUseResumeDeltaPrompt || (externalChatTurn && !hasCustomPromptTemplate)
|
|
@@ -2225,6 +2252,7 @@ async function buildPrompt(ctx, resumedSession, env, paperclipAccess, paperclipT
|
|
|
2225
2252
|
const prompt = joinPromptSections([
|
|
2226
2253
|
promptInstructionsPrefix,
|
|
2227
2254
|
renderedBootstrapPrompt,
|
|
2255
|
+
selectInitialCommunicationGuidance(context, { resumedSession }),
|
|
2228
2256
|
wakePrompt,
|
|
2229
2257
|
sessionHandoffNote,
|
|
2230
2258
|
taskContextNote,
|
|
@@ -2717,12 +2745,13 @@ async function closeWarmHandle(input) {
|
|
|
2717
2745
|
input.handles.delete(input.key);
|
|
2718
2746
|
}
|
|
2719
2747
|
clearWarmHandleTimer(input.entry);
|
|
2720
|
-
await input.entry.runtime.close({
|
|
2748
|
+
const stopped = await input.entry.runtime.close({
|
|
2721
2749
|
handle: input.entry.handle,
|
|
2722
2750
|
reason: input.reason,
|
|
2723
2751
|
discardPersistentState: input.discardPersistentState ?? false,
|
|
2724
|
-
}).
|
|
2752
|
+
}).then(() => true, () => false);
|
|
2725
2753
|
flushChildStderr(input.entry.childStderrState);
|
|
2754
|
+
return stopped;
|
|
2726
2755
|
}
|
|
2727
2756
|
function warmHandleMatches(entry, runtime, handle) {
|
|
2728
2757
|
return entry !== undefined && entry.runtime === runtime && entry.handle === handle;
|
|
@@ -3130,9 +3159,9 @@ export function createAcpxEngineExecutor(deps = {}) {
|
|
|
3130
3159
|
: teardownErr instanceof Error
|
|
3131
3160
|
? teardownErr.message
|
|
3132
3161
|
: String(teardownErr);
|
|
3133
|
-
await ctx
|
|
3162
|
+
await withAdapterExecutionPhase(ctx, "phase_reporting", () => ctx
|
|
3134
3163
|
.onLog("stderr", `[paperclip] ACPX teardown step "${step}" failed: ${reason}\n`)
|
|
3135
|
-
.catch(() => { });
|
|
3164
|
+
.catch(() => { }));
|
|
3136
3165
|
};
|
|
3137
3166
|
// Emit one per-phase timing run-log event. It is not an OpenTelemetry
|
|
3138
3167
|
// export and it is not a Telemetry event: it carries the phase name
|
|
@@ -3145,19 +3174,20 @@ export function createAcpxEngineExecutor(deps = {}) {
|
|
|
3145
3174
|
const timedPhase = async (phase, run) => {
|
|
3146
3175
|
const start = now();
|
|
3147
3176
|
try {
|
|
3148
|
-
await run
|
|
3177
|
+
await withAdapterExecutionPhase(ctx, phase, run);
|
|
3149
3178
|
}
|
|
3150
3179
|
catch (error) {
|
|
3151
|
-
await emitPhase(phase, start, "failed");
|
|
3180
|
+
await withAdapterExecutionPhase(ctx, "phase_reporting", () => emitPhase(phase, start, "failed"));
|
|
3152
3181
|
throw error;
|
|
3153
3182
|
}
|
|
3154
|
-
await emitPhase(phase, start, "ok");
|
|
3183
|
+
await withAdapterExecutionPhase(ctx, "phase_reporting", () => emitPhase(phase, start, "ok"));
|
|
3155
3184
|
};
|
|
3156
3185
|
// The turn the run started. It is hoisted to the run scope so the settlement
|
|
3157
3186
|
// `endSession` step can cancel a running turn before it closes the runtime
|
|
3158
3187
|
// (the cancel-before-close order). The turn wrapper assigns it in `turnStart`.
|
|
3159
3188
|
let activeTurn = null;
|
|
3160
3189
|
let terminalFailureClassification = null;
|
|
3190
|
+
let terminalSessionFailure = null;
|
|
3161
3191
|
// How the settlement `endSession` step must release the runtime for the path
|
|
3162
3192
|
// this run took. Each exit path that acquired the runtime records it before it
|
|
3163
3193
|
// returns; a build or create-runtime failure never registers the runtime, so
|
|
@@ -3268,7 +3298,14 @@ export function createAcpxEngineExecutor(deps = {}) {
|
|
|
3268
3298
|
// same session still sees it. The borrow clears the entry's idle timer, so
|
|
3269
3299
|
// the reused runtime cannot expire under its own timer while this run uses
|
|
3270
3300
|
// it (today's `clearWarmHandleTimer(cached)` after the reuse decision).
|
|
3271
|
-
|
|
3301
|
+
let cached = canResume ? hostStore.borrow(prepared.sessionKey) : undefined;
|
|
3302
|
+
if (cached && (ctx.context.refreshTools === true
|
|
3303
|
+
|| asString(previousParams.mcpFingerprint, "") !== shortHash(prepared.mcpIdentity)
|
|
3304
|
+
// MCP bearer credentials are run-scoped, even for an unchanged catalog.
|
|
3305
|
+
|| prepared.mcpServers.length > 0)) {
|
|
3306
|
+
await hostStore.discard(prepared.sessionKey);
|
|
3307
|
+
cached = undefined;
|
|
3308
|
+
}
|
|
3272
3309
|
childStderrState = cached?.childStderrState ?? { logPath: null, pendingLiveLine: "" };
|
|
3273
3310
|
processIdentitySink = cached?.processIdentitySink ?? {
|
|
3274
3311
|
current: ctx.onSpawn,
|
|
@@ -3281,27 +3318,9 @@ export function createAcpxEngineExecutor(deps = {}) {
|
|
|
3281
3318
|
flushChildStderr(childStderrState);
|
|
3282
3319
|
childStderrState.logPath = prepared.childStderrLogPath;
|
|
3283
3320
|
const persistedRuntimeStore = createRuntimeStore({ stateDir: prepared.stateDir });
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
if (!record)
|
|
3288
|
-
return undefined;
|
|
3289
|
-
// ACPX resumes from the stored session options rather than the
|
|
3290
|
-
// options passed to ensureSession. Keep conversation state, but
|
|
3291
|
-
// launch the provider with this run's credentials and scratch paths.
|
|
3292
|
-
return {
|
|
3293
|
-
...record,
|
|
3294
|
-
acpx: {
|
|
3295
|
-
...record.acpx,
|
|
3296
|
-
session_options: {
|
|
3297
|
-
...record.acpx?.session_options,
|
|
3298
|
-
env: { ...prepared.env },
|
|
3299
|
-
},
|
|
3300
|
-
},
|
|
3301
|
-
};
|
|
3302
|
-
},
|
|
3303
|
-
save: (record) => persistedRuntimeStore.save(record),
|
|
3304
|
-
};
|
|
3321
|
+
// Resume with this run's launch environment; keep it out of the saved
|
|
3322
|
+
// conversation record without mutating the live runtime's options.
|
|
3323
|
+
const runtimeStore = createEphemeralSessionEnvironmentStore(persistedRuntimeStore, prepared.env);
|
|
3305
3324
|
const runtimeOptions = {
|
|
3306
3325
|
cwd: prepared.cwd,
|
|
3307
3326
|
// Host-only spawn cwd for the relay proxy on the remote process-session
|
|
@@ -3741,6 +3760,8 @@ export function createAcpxEngineExecutor(deps = {}) {
|
|
|
3741
3760
|
};
|
|
3742
3761
|
let eventBreakdown = null;
|
|
3743
3762
|
let eventCostUsd = null;
|
|
3763
|
+
let lastRuntimeEventAt = null;
|
|
3764
|
+
let observedRuntimeEvents = 0;
|
|
3744
3765
|
// The turn-local state the sequence steps share. `promptBuild` sets the
|
|
3745
3766
|
// prompt, `preTurnUsage` sets the pre-turn status, `turnStart` sets the
|
|
3746
3767
|
// active turn, and `turnFinalize` reads all three. `activeTurn` is the run-
|
|
@@ -3831,14 +3852,13 @@ export function createAcpxEngineExecutor(deps = {}) {
|
|
|
3831
3852
|
timeoutMs: startTimeoutMs,
|
|
3832
3853
|
signal,
|
|
3833
3854
|
// The callback belongs to this turn, including when a runtime is reused.
|
|
3834
|
-
//
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
: {}),
|
|
3855
|
+
// Diagnostics are retained even when an adapter has no recovery
|
|
3856
|
+
// classifier. Redact before bounding so partial secrets cannot leak.
|
|
3857
|
+
onTerminalSessionFailure: (failure) => {
|
|
3858
|
+
terminalFailureClassification = classifyToolDefinitionFailure(failure)
|
|
3859
|
+
?? deps.classifyTerminalSessionFailure?.(failure, new Date(now())) ?? null;
|
|
3860
|
+
terminalSessionFailure = sanitizeTerminalSessionFailure(failure, prepared.env, ctx.authToken, parseObject(ctx.config.env));
|
|
3861
|
+
},
|
|
3842
3862
|
});
|
|
3843
3863
|
activeTurn = turn;
|
|
3844
3864
|
// A latched sandbox duplex-channel loss otherwise has no way to reach
|
|
@@ -3895,7 +3915,7 @@ export function createAcpxEngineExecutor(deps = {}) {
|
|
|
3895
3915
|
armStopDeadline();
|
|
3896
3916
|
return {
|
|
3897
3917
|
cancel: async (reason) => {
|
|
3898
|
-
await turn.cancel({ reason });
|
|
3918
|
+
await withAdapterExecutionPhase(ctx, "cancel_turn", () => turn.cancel({ reason }));
|
|
3899
3919
|
},
|
|
3900
3920
|
};
|
|
3901
3921
|
};
|
|
@@ -3914,6 +3934,8 @@ export function createAcpxEngineExecutor(deps = {}) {
|
|
|
3914
3934
|
const toolTitles = new Map();
|
|
3915
3935
|
const drainEvents = (async () => {
|
|
3916
3936
|
for await (const event of turn.events) {
|
|
3937
|
+
lastRuntimeEventAt = now();
|
|
3938
|
+
observedRuntimeEvents += 1;
|
|
3917
3939
|
// ACPX currently flattens client-side filesystem/terminal receipts
|
|
3918
3940
|
// into status text. They cannot establish complete action outcomes.
|
|
3919
3941
|
if (event.type === "status" && /^(fs|terminal)\//.test(event.text))
|
|
@@ -3972,6 +3994,16 @@ export function createAcpxEngineExecutor(deps = {}) {
|
|
|
3972
3994
|
return await Promise.race([turn.result, lossDeadline.then(() => LOSS_DEADLINE_TERMINAL)]);
|
|
3973
3995
|
};
|
|
3974
3996
|
const stepTurnFinalize = async (input) => {
|
|
3997
|
+
// Capture runtime activity before usage reads, error logs, or cleanup
|
|
3998
|
+
// make an otherwise silent turn look active at its execution deadline.
|
|
3999
|
+
const lastEventAgeMs = lastRuntimeEventAt === null ? undefined : now() - lastRuntimeEventAt;
|
|
4000
|
+
const activityDiagnostics = {
|
|
4001
|
+
acpObservedEventCount: observedRuntimeEvents,
|
|
4002
|
+
...(lastEventAgeMs !== undefined && Number.isSafeInteger(lastEventAgeMs) && lastEventAgeMs >= 0
|
|
4003
|
+
? { acpLastEventAgeMs: lastEventAgeMs } : {}),
|
|
4004
|
+
acpPendingToolCount: [...interruptionTools.values()].filter((tool) => tool.status !== "completed" && tool.status !== "failed" && tool.status !== "cancelled").length,
|
|
4005
|
+
acpToolInventoryComplete: !incompleteToolInventory,
|
|
4006
|
+
};
|
|
3975
4007
|
if (input.kind === "terminal") {
|
|
3976
4008
|
const terminal = input.terminal;
|
|
3977
4009
|
const timedOut = input.timedOut;
|
|
@@ -4055,11 +4087,14 @@ export function createAcpxEngineExecutor(deps = {}) {
|
|
|
4055
4087
|
cancelTurnReason: null,
|
|
4056
4088
|
skipRemoteClose: channelLost,
|
|
4057
4089
|
};
|
|
4090
|
+
const failureDiagnostic = !timedOut && !channelLost && terminal.status === "failed"
|
|
4091
|
+
? terminalSessionFailure
|
|
4092
|
+
: null;
|
|
4058
4093
|
const errorMessage = timedOut
|
|
4059
4094
|
? formatAdapterExecutionTimeoutErrorMessage(prepared.timeoutResolution)
|
|
4060
4095
|
: channelLost
|
|
4061
4096
|
? channelLostMessage
|
|
4062
|
-
: resultErrorMessage(terminal);
|
|
4097
|
+
: formatTerminalSessionFailure(resultErrorMessage(terminal), failureDiagnostic);
|
|
4063
4098
|
const terminalStopReason = terminal.status === "failed" ? terminal.error.message : terminal.stopReason;
|
|
4064
4099
|
const classifiedFailure = !timedOut && !channelLost && terminal.status === "failed"
|
|
4065
4100
|
? terminalFailureClassification
|
|
@@ -4098,6 +4133,8 @@ export function createAcpxEngineExecutor(deps = {}) {
|
|
|
4098
4133
|
costUsd: turnUsage.costUsd,
|
|
4099
4134
|
resultJson: {
|
|
4100
4135
|
status: channelLost ? "failed" : terminal.status,
|
|
4136
|
+
...activityDiagnostics,
|
|
4137
|
+
...(failureDiagnostic ? { terminalSessionFailure: failureDiagnostic } : {}),
|
|
4101
4138
|
...(classifiedFailure?.errorFamily ? { errorFamily: classifiedFailure.errorFamily } : {}),
|
|
4102
4139
|
...(classifiedFailure?.retryNotBefore
|
|
4103
4140
|
? {
|
|
@@ -4228,7 +4265,7 @@ export function createAcpxEngineExecutor(deps = {}) {
|
|
|
4228
4265
|
...referencedProjectStagingFailuresField,
|
|
4229
4266
|
model: prepared.requestedModel || null,
|
|
4230
4267
|
clearSession: clearSession || timedOut,
|
|
4231
|
-
resultJson: { phase },
|
|
4268
|
+
resultJson: { phase, ...activityDiagnostics },
|
|
4232
4269
|
summary: message,
|
|
4233
4270
|
};
|
|
4234
4271
|
// Return a typed failed completion so the coordinator settles for a cause
|
|
@@ -4334,7 +4371,7 @@ export function createAcpxEngineExecutor(deps = {}) {
|
|
|
4334
4371
|
: baseSettlement;
|
|
4335
4372
|
// Cancel a running turn before the close (the turn-error path).
|
|
4336
4373
|
if (settlement.cancelTurnReason && activeTurn) {
|
|
4337
|
-
await activeTurn.cancel({ reason: settlement.cancelTurnReason }).catch(() => { });
|
|
4374
|
+
await withAdapterExecutionPhase(ctx, "cancel_turn", () => activeTurn.cancel({ reason: settlement.cancelTurnReason }).catch(() => { }));
|
|
4338
4375
|
}
|
|
4339
4376
|
const existing = warmHandles.get(prepared.sessionKey);
|
|
4340
4377
|
// Re-read the duplex control-channel disposition here, at the
|
|
@@ -4371,26 +4408,26 @@ export function createAcpxEngineExecutor(deps = {}) {
|
|
|
4371
4408
|
existing) {
|
|
4372
4409
|
// A matching warm entry closes through the warm store, which also
|
|
4373
4410
|
// clears its idle timer and flushes its child stderr.
|
|
4374
|
-
await closeWarmHandle({
|
|
4411
|
+
runtimeStopConfirmed = await withAdapterExecutionPhase(ctx, "close_session", () => closeWarmHandle({
|
|
4375
4412
|
handles: warmHandles,
|
|
4376
4413
|
key: prepared.sessionKey,
|
|
4377
4414
|
entry: existing,
|
|
4378
4415
|
reason: settlement.reason,
|
|
4379
4416
|
discardPersistentState: settlement.discardPersistentState,
|
|
4380
|
-
});
|
|
4417
|
+
}));
|
|
4381
4418
|
return;
|
|
4382
4419
|
}
|
|
4383
4420
|
const onCloseError = settlement.recordCloseError || ctx.signal?.aborted
|
|
4384
4421
|
? (closeErr) => recordTeardownError("runtime-close", closeErr)
|
|
4385
4422
|
: () => { };
|
|
4386
|
-
await runtime
|
|
4423
|
+
await withAdapterExecutionPhase(ctx, "close_session", () => runtime
|
|
4387
4424
|
.close({
|
|
4388
4425
|
handle: settlement.handle,
|
|
4389
4426
|
reason: settlement.reason,
|
|
4390
4427
|
discardPersistentState: settlement.discardPersistentState,
|
|
4391
4428
|
})
|
|
4392
4429
|
.then(() => { runtimeStopConfirmed = true; })
|
|
4393
|
-
.catch(onCloseError);
|
|
4430
|
+
.catch(onCloseError));
|
|
4394
4431
|
if (settlement.dropWarmEntry && warmHandleMatches(existing, runtime, settlement.handle) && existing) {
|
|
4395
4432
|
clearWarmHandleTimer(existing);
|
|
4396
4433
|
warmHandles.delete(prepared.sessionKey);
|
|
@@ -4418,12 +4455,29 @@ export function createAcpxEngineExecutor(deps = {}) {
|
|
|
4418
4455
|
// runs, so the host mints a `traceparent` for the provider spans, and the
|
|
4419
4456
|
// per-task restore spans parent to `sandbox.syncBack`.
|
|
4420
4457
|
syncBack: () => timedPhase("sync_back", async () => {
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4458
|
+
// A transport error or terminal narrative cannot authorize collecting
|
|
4459
|
+
// mutable provider files. Require the owned close or local OS handle.
|
|
4460
|
+
const stopped = runtimeStopConfirmed || (!prepared.processSessionBridge && capturedProcessExited(processIdentitySink?.localProcess));
|
|
4461
|
+
try {
|
|
4462
|
+
if (stopped)
|
|
4463
|
+
await withAdapterExecutionPhase(ctx, "instruction_collection", () => ctx.onProviderStopped?.());
|
|
4464
|
+
}
|
|
4465
|
+
catch {
|
|
4466
|
+
// Match ACP's fail-soft teardown policy without describing this as
|
|
4467
|
+
// a workspace restore failure or exposing paths from a raw error.
|
|
4468
|
+
await recordTeardownError("instruction-collection", new Error("Instruction collection failed after provider stop. No instruction save is claimed."));
|
|
4469
|
+
}
|
|
4470
|
+
finally {
|
|
4471
|
+
await withAdapterExecutionPhase(ctx, "workspace_restore", () => runRuntimeSpan("sandbox.syncBack", async () => {
|
|
4472
|
+
const restoreOutcome = await syncBackManagedHome(prepared);
|
|
4473
|
+
if (!restoreOutcome.ok) {
|
|
4474
|
+
workspaceRestoreFailureField = {
|
|
4475
|
+
workspaceRestoreFailure: restoreOutcome.code,
|
|
4476
|
+
...(restoreOutcome.diagnostic ? { workspaceRestoreDiagnostic: restoreOutcome.diagnostic } : {}),
|
|
4477
|
+
};
|
|
4478
|
+
}
|
|
4479
|
+
}));
|
|
4480
|
+
}
|
|
4427
4481
|
}),
|
|
4428
4482
|
// The staging lease releases as the run's final act, AFTER the coordinator
|
|
4429
4483
|
// reproduces the result, in the run root `finally` below. This step stays a
|
|
@@ -4492,6 +4546,15 @@ export function createAcpxEngineExecutor(deps = {}) {
|
|
|
4492
4546
|
} },
|
|
4493
4547
|
};
|
|
4494
4548
|
}
|
|
4549
|
+
// Preserve the policy resolved for this execution target. The server
|
|
4550
|
+
// cannot reconstruct sandbox defaults from the stored agent config.
|
|
4551
|
+
capturedResult = {
|
|
4552
|
+
...capturedResult,
|
|
4553
|
+
resultJson: {
|
|
4554
|
+
...capturedResult.resultJson,
|
|
4555
|
+
adapterExecutionTimeout: { ...prepared.timeoutResolution },
|
|
4556
|
+
},
|
|
4557
|
+
};
|
|
4495
4558
|
// The sync-back settlement step runs before this reproduces the result
|
|
4496
4559
|
// (settlement precedes reproduction), so a failed workspace restore is
|
|
4497
4560
|
// already recorded by the time we get here. Merge it into `resultJson`
|