feat(runner): forward ACP _meta from ACPX as a bounded copy (#19259)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The native runner runs Claude through ACPX and maps ACP updates into
canonical run events.
> - Claude sends useful data in the ACP `_meta` field: the real tool
name, the parent tool of a subagent call, the terminal exit code, and
the rate-limit state.
> - ACPX 0.13.1 drops all `_meta` before the runner sees it.
> - Three planned features need this data: richer tool rows, a
rate-limit gauge, and a subagent tree.
> - This pull request forwards `_meta` from ACPX and bounds it once in
the runner.
> - The benefit is that each feature can read the data from one safe,
shared place.

## Linked Issues or Issue Description

**Problem or motivation**
ACPX keeps only the title, status, kind, locations, raw input, raw
output and content of an ACP tool update. It drops `_meta`. Claude's
tool name, `parentToolUseId`, `terminal_exit` and `_claude/rateLimit`
never reach the runner.

**Proposed solution**
Forward `_meta` from ACPX on tool and status events. Copy it into a
bounded, JSON-safe object in the runner before any mapper reads it.

**Alternatives considered**
Read the full ACP notification through `onSessionNotification` and join
it to runtime events by tool call ID. That needs a side table and
depends on callback order. Forwarding on the event is simpler and keeps
the data with the event.

## What Changed

- `patches/acpx@0.13.1.patch`: ACPX now sets `_meta` on `tool_call`
events and on status events (`usage_update`, `current_mode_update`,
`config_option_update`, `session_info_update`). The type declarations
document the field as unvalidated producer data.
- `packages/paperclip-runner/src/provider-events.ts`: new
`boundedAcpMeta()`. `createAcpxToolEventNormalizer` applies it to every
runtime event. Limits: depth 6, 64 entries per object or array, 4 KB per
string, about 16 KB in total. It drops `__proto__`, `constructor` and
`prototype` keys, functions and non-finite numbers.
- The hardened ACPX sidecar path keeps its explicit field allowlist. It
does not forward `_meta` yet. A feature that needs a field there must
add it to that allowlist.
- Tests for the passthrough and for each bound.

## Verification

- `cd packages/paperclip-runner && npx vitest run
src/provider-events.test.ts`
- The canonical mappers do not read `_meta` yet, so run events do not
change. Follow-up pull requests use it.

## Risks

- Low risk. The new field is additive and the mappers ignore it today.
- The lockfile patch hash for `acpx@0.13.1` changes. A fresh `pnpm
install` applies the new patch.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use and
code execution.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DottaandClaude Opus 5.5 authored and GitHub committed 2026-11-05 11:31:06 -06:00
1 parent d8c47159d4
commit 29afc633f6
3 files changed
+302 -72

No files matched your search

@@ -6,6 +6,7 @@ import {
canonicalProviderEventsFromAcpxRuntimeEvent,
canonicalProviderEventsFromCodex,
canonicalProviderEventsFromOpenCodePart,
boundedAcpMeta,
createAcpxToolEventNormalizer,
createGrokMessageNormalizer,
providerFamilyCapabilities,
@@ -555,6 +556,84 @@ describe("provider-neutral events", () => {
});
});
it("forwards ACP _meta as a bounded JSON-safe copy on every runtime event", () => {
const normalize = createAcpxToolEventNormalizer();
const tool = normalize({
type: "tool_call",
tag: "tool_call",
toolCallId: "tool-meta",
title: "Terminal",
status: "pending",
_meta: {
claudeCode: { toolName: "Bash", parentToolUseId: "toolu_parent" },
terminal_exit: { exit_code: 2, signal: null },
["__proto__"]: { polluted: true },
},
} as never);
expect(tool._meta).toEqual({
claudeCode: { toolName: "Bash", parentToolUseId: "toolu_parent" },
terminal_exit: { exit_code: 2, signal: null },
});
const status = normalize({
type: "status",
tag: "usage_update",
text: "usage updated",
_meta: { "_claude/rateLimit": { status: "allowed", utilization: 0.78 } },
} as never);
expect(status._meta).toEqual({
"_claude/rateLimit": { status: "allowed", utilization: 0.78 },
});
expect(
normalize({ type: "status", text: "x", _meta: "not an object" } as never),
).not.toHaveProperty("_meta");
});
it("bounds ACP _meta depth, breadth, string size and total size", () => {
let deep: Record<string, unknown> = { leaf: true };
for (let index = 0; index < 10; index += 1) deep = { next: deep };
const wide = Object.fromEntries(
Array.from({ length: 100 }, (_, index) => [`k${index}`, index]),
);
const bounded = boundedAcpMeta({
deep,
wide,
long: "x".repeat(10_000),
big: Array.from({ length: 64 }, () => "y".repeat(3_000)),
notFinite: Number.POSITIVE_INFINITY,
fn: () => undefined,
})!;
expect(Buffer.byteLength(JSON.stringify(bounded))).toBeLessThanOrEqual(16_000);
expect(Object.keys(bounded.wide as object)).toHaveLength(64);
expect((bounded.long as string).length).toBe(4_000);
expect(bounded).not.toHaveProperty("notFinite");
expect(bounded).not.toHaveProperty("fn");
expect(JSON.stringify(bounded.deep)).not.toContain("leaf");
expect(boundedAcpMeta([1, 2])).toBeUndefined();
expect(boundedAcpMeta({})).toBeUndefined();
});
it("charges every retained container, separator and escaped byte against the ACP _meta budget", () => {
let tree: unknown = [];
for (let level = 0; level < 3; level += 1) tree = Array.from({ length: 64 }, () => tree);
const nested = boundedAcpMeta({ a: tree })!;
expect(Buffer.byteLength(JSON.stringify(nested))).toBeLessThanOrEqual(16_000);
const escaped = boundedAcpMeta({ nulls: "\u0000".repeat(4_000), ok: "kept?" })!;
expect(Buffer.byteLength(JSON.stringify(escaped))).toBeLessThanOrEqual(16_000);
expect(escaped).not.toHaveProperty("nulls");
expect(escaped.ok).toBe("kept?");
});
it("inspects a bounded number of keys on a very wide ACP _meta object", () => {
let reads = 0;
const wide = new Proxy(
Object.fromEntries(Array.from({ length: 10_000 }, (_, index) => [`__proto__x${index}`.replace("__proto__x", "k"), index])),
{ get(target, key, receiver) { reads += 1; return Reflect.get(target, key, receiver); } },
);
const bounded = boundedAcpMeta({ wide })!;
expect(Object.keys(bounded.wide as object)).toHaveLength(64);
expect(reads).toBeLessThanOrEqual(64);
});
it("preserves ACPX identity across title-less progress and completion updates", () => {
const normalize = createAcpxToolEventNormalizer();
const started = normalize({
@@ -264,6 +264,95 @@ function boundedAcpxLifecycleLocations(value: unknown): unknown[] | undefined {
});
}
const MAX_ACP_META_DEPTH = 6;
const MAX_ACP_META_ENTRIES = 64;
const MAX_ACP_META_STRING_BYTES = 4_000;
const MAX_ACP_META_TOTAL_BYTES = 16_000;
const UNSAFE_ACP_META_KEYS = new Set(["__proto__", "constructor", "prototype"]);
/**
* Bounded, JSON-safe copy of an ACP update `_meta` object. The patched ACPX
* runtime forwards `_meta` unvalidated on tool and status events (for example
* Claude's `_meta.claudeCode.toolName`, `parentToolUseId`, `terminal_exit`
* and `_claude/rateLimit`). The values are producer-controlled hints: they
* can name a tool or carry a provider counter, but they never grant authority.
*
* Limits: depth 6, 64 keys or items per container, 4 KB per string and about
* 16 KB in total. Entries beyond a limit are dropped, never partially parsed.
* Returns undefined when nothing usable remains.
*/
export function boundedAcpMeta(
value: unknown,
): Record<string, unknown> | undefined {
// The budget is the serialized JSON size of the retained copy: every
// scalar at its escaped length, every bracket, separator and key. A value
// that does not fit is dropped whole, so the copy never exceeds the bound.
let budget = MAX_ACP_META_TOTAL_BYTES;
const charge = (bytes: number): boolean => {
if (bytes > budget) return false;
budget -= bytes;
return true;
};
const visit = (input: unknown, depth: number): unknown => {
if (input === null || typeof input === "boolean") {
return charge(String(input).length) ? input : undefined;
}
if (typeof input === "number") {
if (!Number.isFinite(input)) return undefined;
return charge(JSON.stringify(input).length) ? input : undefined;
}
if (typeof input === "string") {
const bounded = boundedAcpxLifecycleText(input, MAX_ACP_META_STRING_BYTES) ?? "";
return charge(Buffer.byteLength(JSON.stringify(bounded))) ? bounded : undefined;
}
if (depth >= MAX_ACP_META_DEPTH || typeof input !== "object") return undefined;
if (!charge(2)) return undefined;
if (Array.isArray(input)) {
const items: unknown[] = [];
const limit = Math.min(input.length, MAX_ACP_META_ENTRIES);
for (let index = 0; index < limit && budget > 0; index += 1) {
if (items.length > 0 && !charge(1)) break;
const next = visit(input[index], depth + 1);
if (next !== undefined) items.push(next);
else if (items.length > 0) budget += 1;
}
return items;
}
const output: Record<string, unknown> = {};
let retained = 0;
let inspected = 0;
// for...in stops early; Object.entries would materialize every key of a
// wide producer object before any limit applied.
for (const key in input as Record<string, unknown>) {
if (!Object.prototype.hasOwnProperty.call(input, key)) continue;
if (retained >= MAX_ACP_META_ENTRIES || inspected >= MAX_ACP_META_ENTRIES * 4 || budget <= 0) break;
inspected += 1;
if (UNSAFE_ACP_META_KEYS.has(key) || Buffer.byteLength(key) > 160) continue;
const keyBytes = Buffer.byteLength(JSON.stringify(key)) + 1 + (retained > 0 ? 1 : 0);
if (!charge(keyBytes)) break;
const next = visit((input as Record<string, unknown>)[key], depth + 1);
if (next === undefined) {
budget += keyBytes;
continue;
}
output[key] = next;
retained += 1;
}
return output;
};
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
const bounded = visit(value, 0) as Record<string, unknown> | undefined;
return bounded && Object.keys(bounded).length > 0 ? bounded : undefined;
}
/** Bound any forwarded `_meta` on a runtime event, whatever its type. */
function withBoundedAcpMeta<T extends AcpRuntimeEventShape>(event: T): T {
if (!Object.prototype.hasOwnProperty.call(event, "_meta")) return event;
const { _meta: raw, ...rest } = event as T & { _meta?: unknown };
const meta = boundedAcpMeta(raw);
return (meta ? { ...rest, _meta: meta } : rest) as T;
}
/**
* ACP tool updates are deltas: a later update may omit the title, kind, and
* locations that were present on the opening event. ACPX represents a missing
@@ -291,7 +380,8 @@ export function createAcpxToolEventNormalizer<
T extends AcpRuntimeEventShape,
>(): (event: T) => T {
const lifecycle = new Map<string, AcpxToolLifecycleIdentity>();
return (event) => {
return (input) => {
const event = withBoundedAcpMeta(input);
if (event.type !== "tool_call" || !event.toolCallId) return event;
const toolCallId = boundedAcpxLifecycleId(event.toolCallId);
const boundedTitle = boundedAcpxLifecycleText(
+132 -71
View File
@@ -1,5 +1,5 @@
diff --git a/dist/client-CxNllqui.d.ts b/dist/client-CxNllqui.d.ts
index 5e2113ac0..45aff03c3 100644
index 5e2113ac0a1a92c99322cf01e5c106760a0b0dbf..45aff03c37427071de49b45eb0361a9a808786e0 100644
--- a/dist/client-CxNllqui.d.ts
+++ b/dist/client-CxNllqui.d.ts
@@ -120,6 +120,8 @@ declare class AcpClient {
@@ -20,9 +20,10 @@ index 5e2113ac0..45aff03c3 100644
private setSessionModelThroughConfig;
private setSessionModelThroughLegacyMethod;
diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
index d454fd7c5bf742b469be75eb8c3988b694a0ffaa..b29bee391efcdc1ca9c37e9608cc34f527fe428e 100644
--- a/dist/live-checkpoint-BSIrfgVo.js
+++ b/dist/live-checkpoint-BSIrfgVo.js
@@ -6,7 +6,7 @@
@@ -6,7 +6,7 @@ import os from "node:os";
import { randomUUID } from "node:crypto";
import { execFile, spawn } from "node:child_process";
import { Readable, Writable } from "node:stream";
@@ -31,7 +32,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
import readline from "node:readline/promises";
//#region src/errors.ts
var AcpxOperationalError = class extends Error {
@@ -1068,12 +1068,14 @@
@@ -1068,12 +1068,14 @@ function serializeSessionRecordForDisk(record) {
last_agent_disconnect_reason: canonical.lastAgentDisconnectReason,
protocol_version: canonical.protocolVersion,
agent_capabilities: canonical.agentCapabilities,
@@ -46,7 +47,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
acpx: canonical.acpx,
imported_from: canonical.importedFrom ? {
record_id: canonical.importedFrom.recordId,
@@ -1239,7 +1241,8 @@
@@ -1239,7 +1241,8 @@ function parseRequestTokenUsage(raw) {
for (const [key, value] of Object.entries(record)) {
const parsed = parseTokenUsage(value);
if (parsed == null) return null;
@@ -56,7 +57,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
}
return usage;
}
@@ -1325,6 +1328,93 @@
@@ -1325,6 +1328,93 @@ function isAgentMessage$1(raw) {
function isConversationMessage(raw) {
return raw === "Resume" || isUserMessage$1(raw) || isAgentMessage$1(raw);
}
@@ -150,7 +151,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
function parseConversationRecord(record) {
if (!hasValidConversationCore(record)) return;
const title = parseConversationTitle(record.title);
@@ -1339,7 +1429,8 @@
@@ -1339,7 +1429,8 @@ function parseConversationRecord(record) {
updated_at: record.updated_at,
cumulative_token_usage: cumulativeTokenUsage ?? {},
cumulative_cost: cumulativeCost,
@@ -160,7 +161,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
};
}
const INVALID_VALUE = Symbol("invalid");
@@ -1541,12 +1632,14 @@
@@ -1541,12 +1632,14 @@ function parseSessionRecord(raw) {
lastAgentDisconnectReason: optionals.lastAgentDisconnectReason,
protocolVersion: typeof record.protocol_version === "number" ? record.protocol_version : void 0,
agentCapabilities: asRecord$4(record.agent_capabilities),
@@ -175,7 +176,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
acpx: parseAcpxState(record.acpx),
importedFrom: metadata.importedFrom
};
@@ -1661,8 +1754,9 @@
@@ -1661,8 +1754,9 @@ const ZED_TAG_KEYS = /* @__PURE__ */ new Set([
"RedactedThinking",
"ToolUse"
]);
@@ -186,7 +187,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
"agent_capabilities",
"messages.Agent.content.ToolUse.input",
"acpx.desired_config_options",
@@ -3104,10 +3198,10 @@
@@ -3104,10 +3198,10 @@ function authEnvKey(methodId) {
authEnvKeyCache.set(methodId, key);
return key;
}
@@ -199,7 +200,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
if (typeof value === "string" && value.trim().length > 0) return value;
}
function protectedEnvKey(key) {
@@ -3135,8 +3229,21 @@
@@ -3135,8 +3229,21 @@ function promotePrefixedAuthEnvironment(env) {
}
return protectedKeys;
}
@@ -223,7 +224,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
const protectedAuthEnvKeys = promotePrefixedAuthEnvironment(env);
if (authCredentials) for (const [methodId, credential] of Object.entries(authCredentials)) {
addAuthCredentialEnvKeys(protectedAuthEnvKeys, methodId, credential);
@@ -3178,10 +3285,10 @@
@@ -3178,10 +3285,10 @@ function resolveConfiguredAuthCredential(methodId, authCredentials) {
const configCredentials = authCredentials ?? {};
return configCredentials[methodId] ?? configCredentials[toEnvToken(methodId)];
}
@@ -236,7 +237,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
stdio: [
"pipe",
"pipe",
@@ -3286,28 +3393,12 @@
@@ -3286,28 +3393,12 @@ function formatAvailableModelIds(models) {
const ids = models?.availableModels.map((model) => model.modelId.trim()).filter((modelId) => modelId.length > 0) ?? [];
return ids.length > 0 ? ids.join(", ") : "none advertised";
}
@@ -266,7 +267,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
}
//#endregion
//#region src/acp/session-control-errors.ts
@@ -3961,10 +4052,13 @@
@@ -3961,10 +4052,13 @@ function resolveClientCapabilities(params) {
...params.elicitationModes.includes("url") ? { url: {} } : {}
} } : {}
};
@@ -282,7 +283,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
};
}
function hasResponseField(response, field) {
@@ -4067,6 +4161,27 @@
@@ -4067,6 +4161,27 @@ function createNdJsonMessageStream(agentCommand, output, input) {
} })
};
}
@@ -310,7 +311,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
var AcpClient = class {
options;
connection;
@@ -4105,7 +4220,9 @@
@@ -4105,7 +4220,9 @@ var AcpClient = class {
cwd: asAbsoluteCwd(options.cwd),
authPolicy: options.authPolicy ?? "skip",
permissionPolicy: snapshotPermissionPolicy(options.permissionPolicy),
@@ -321,7 +322,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
};
this.eventHandlers = {
onAcpMessage: this.options.onAcpMessage,
@@ -4216,9 +4333,21 @@
@@ -4216,9 +4333,21 @@ var AcpClient = class {
this.lastAgentExit = void 0;
this.lastKnownPid = child.pid ?? void 0;
this.attachAgentLifecycleObservers(child);
@@ -343,7 +344,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
if (!this.options.verbose) return;
process.stderr.write(chunk);
});
@@ -4226,6 +4355,7 @@
@@ -4226,6 +4355,7 @@ var AcpClient = class {
const output = Readable.toWeb(child.stdout);
const stream = this.createTappedStream(createNdJsonMessageStream(this.options.agentCommand, input, output));
const connection = this.createConnection(stream, launch);
@@ -351,7 +352,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
connection.signal.addEventListener("abort", () => {
this.recordAgentExit("connection_close", child.exitCode ?? null, child.signalCode ?? null);
}, { once: true });
@@ -4253,7 +4383,12 @@
@@ -4253,7 +4383,12 @@ var AcpClient = class {
geminiAcp: isGeminiAcpCommand(spawnCommand, args),
copilotAcp: isCopilotAcpCommand(spawnCommand, args),
claudeAcp: isClaudeAcpCommand(spawnCommand, args),
@@ -365,7 +366,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
};
}
logAgentLaunch(plan) {
@@ -4279,10 +4414,17 @@
@@ -4279,10 +4414,17 @@ var AcpClient = class {
}
async spawnAgentProcess(plan) {
const spawnCommand = buildAgentSpawnCommand(plan.spawnCommand, plan.args, process.platform, plan.spawnOptions.env);
@@ -385,7 +386,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
try {
await waitForSpawn$1(spawnedChild);
} catch (error) {
@@ -4293,10 +4435,10 @@
@@ -4293,10 +4435,10 @@ var AcpClient = class {
createConnection(stream, launch) {
const app = client({ name: "acpx" }).onNotification(methods.client.session.update, async ({ params }) => {
await this.handleSessionUpdate(params);
@@ -399,7 +400,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
}).onRequest(methods.client.fs.readTextFile, async ({ params }) => {
return await this.handleReadTextFile(params);
}).onRequest(methods.client.fs.writeTextFile, async ({ params }) => {
@@ -4312,6 +4454,11 @@
@@ -4312,6 +4454,11 @@ var AcpClient = class {
}).onRequest(methods.client.terminal.release, async ({ params }) => {
return await this.handleReleaseTerminal(params);
});
@@ -411,7 +412,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
if (launch.devinAcp) app.onRequest("_cognition.ai/request_diagnostics", (params) => {
return params && typeof params === "object" && !Array.isArray(params) ? params : {};
}, async () => ({}));
@@ -4333,12 +4480,12 @@
@@ -4333,12 +4480,12 @@ var AcpClient = class {
async initializeProtocolConnection(connection, launch) {
const initializePromise = connection.initialize({
protocolVersion: PROTOCOL_VERSION,
@@ -426,7 +427,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
clientInfo: resolveClientInfo(launch.devinAcp)
});
const initialized = launch.geminiAcp ? await withTimeout(initializePromise, resolveGeminiAcpStartupTimeoutMs()) : await initializePromise;
@@ -4358,6 +4505,49 @@
@@ -4358,6 +4505,49 @@ var AcpClient = class {
throw normalizedError;
}
createTappedStream(base) {
@@ -476,7 +477,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
const onAcpMessage = () => this.eventHandlers.onAcpMessage;
const onAcpOutputMessage = () => this.eventHandlers.onAcpOutputMessage;
const elicitationRequestIds = /* @__PURE__ */ new Set();
@@ -4367,8 +4557,12 @@
@@ -4367,8 +4557,12 @@ var AcpClient = class {
const shouldSuppressInboundReplaySessionUpdate = (message) => {
return this.suppressReplaySessionUpdateMessages && isSessionUpdateNotification(message);
};
@@ -490,7 +491,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
if (requestId !== void 0) {
elicitationRequestIds.add(requestId);
return;
@@ -4379,6 +4573,7 @@
@@ -4379,6 +4573,7 @@ var AcpClient = class {
}
};
return {
@@ -498,7 +499,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
readable: new ReadableStream({ async start(controller) {
const reader = base.readable.getReader();
try {
@@ -4386,16 +4581,27 @@
@@ -4386,16 +4581,27 @@ var AcpClient = class {
const { value, done } = await reader.read();
if (done) break;
if (!value) continue;
@@ -528,7 +529,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
const promptOwner = promptRequestOwner(message);
if (promptOwner) bindPromptOwner(promptOwner);
const id = responseId(message);
@@ -4403,10 +4609,20 @@
@@ -4403,10 +4609,20 @@ var AcpClient = class {
onAcpOutputMessage()?.("outbound", message);
onAcpMessage()?.("outbound", message);
}
@@ -549,7 +550,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
writer.releaseLock();
}
} })
@@ -4422,7 +4638,10 @@
@@ -4422,7 +4638,10 @@ var AcpClient = class {
const createPromise = this.runConnectionRequest(() => connection.newSession({
cwd: sessionCwd,
mcpServers: this.options.mcpServers ?? [],
@@ -561,7 +562,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
}));
result = claudeAcp ? await withTimeout(createPromise, resolveClaudeAcpSessionCreateTimeoutMs()) : await createPromise;
} catch (error) {
@@ -4454,6 +4673,7 @@
@@ -4454,6 +4673,7 @@ var AcpClient = class {
}
async loadSessionWithOptions(sessionId, cwd = this.options.cwd, options = {}) {
const connection = this.getConnection();
@@ -569,7 +570,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
const sessionCwd = await resolveAgentSessionCwd(cwd, this.options.agentCommand);
const previousSuppression = this.applySessionUpdateSuppression(Boolean(options.suppressReplayUpdates));
let response;
@@ -4461,7 +4681,9 @@
@@ -4461,7 +4681,9 @@ var AcpClient = class {
response = await this.runConnectionRequest(() => connection.loadSession({
sessionId,
cwd: sessionCwd,
@@ -580,7 +581,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
}));
await this.waitForSessionUpdateDrain(options.replayIdleMs ?? REPLAY_IDLE_MS, options.replayDrainTimeoutMs ?? REPLAY_DRAIN_TIMEOUT_MS);
} finally {
@@ -4474,11 +4696,14 @@
@@ -4474,11 +4696,14 @@ var AcpClient = class {
}
async resumeSession(sessionId, cwd = this.options.cwd) {
const connection = this.getConnection();
@@ -596,7 +597,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
}));
this.loadedSessionId = sessionId;
const result = toReconnectedSessionResult(response);
@@ -4590,15 +4815,17 @@
@@ -4590,15 +4815,17 @@ var AcpClient = class {
throw maybeWrapSessionControlError("session/set_config_option", error, `for "${configId}"="${value}"`);
}
}
@@ -620,7 +621,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
}
async setSessionModelThroughConfig(sessionId, modelId, configId) {
const connection = this.getConnection();
@@ -4608,7 +4835,9 @@
@@ -4608,7 +4835,9 @@ var AcpClient = class {
configId,
value: modelId
}));
@@ -631,7 +632,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
return response;
} catch (error) {
return this.throwSessionModelError("session/set_config_option", modelId, error);
@@ -4864,7 +5093,7 @@
@@ -4864,7 +5093,7 @@ var AcpClient = class {
}
selectAuthMethod(methods) {
for (const method of methods) {
@@ -640,7 +641,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
if (envCredential) return {
methodId: method.id,
credential: envCredential,
@@ -4890,7 +5119,7 @@
@@ -4890,7 +5119,7 @@ var AcpClient = class {
}
readAgentSpecificEnvCredential(methodId) {
if (!this.isGrokBuildAcpCommand() || methodId !== "xai.api_key") return;
@@ -649,7 +650,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
}
selectAgentManagedAuthMethod(methodId) {
@@ -4915,9 +5144,9 @@
@@ -4915,9 +5144,9 @@ var AcpClient = class {
await connection.authenticate({ methodId: selected.methodId });
this.log(`authenticated with method ${selected.methodId} (${selected.source})`);
}
@@ -661,7 +662,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
if (hostResponse) return hostResponse;
const { response, recorded } = await this.resolvePermissionRequestFromMode(params);
if (!recorded) {
@@ -4926,14 +5155,81 @@
@@ -4926,14 +5155,81 @@ var AcpClient = class {
}
return response;
}
@@ -745,7 +746,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
})).then((response) => ({
kind: "response",
response
@@ -4970,7 +5266,7 @@
@@ -4970,7 +5266,7 @@ var AcpClient = class {
isElicitationSessionCancelling(sessionId) {
return this.closing || this.cancellingSessionIds.has(sessionId);
}
@@ -754,7 +755,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
if (!this.options.onPermissionRequest) return;
const signal = this.cancellationSignalForSession(params.sessionId);
try {
@@ -4978,7 +5274,7 @@
@@ -4978,7 +5274,7 @@ var AcpClient = class {
sessionId: params.sessionId,
raw: params,
inferredKind: inferToolKind(params)
@@ -763,7 +764,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
return this.hostPermissionDecisionResponse(params, signal, decision);
} catch (error) {
return this.hostPermissionErrorResponse(params, signal, error);
@@ -5028,6 +5324,12 @@
@@ -5028,6 +5324,12 @@ var AcpClient = class {
attachAgentLifecycleObservers(child) {
child.once("exit", (exitCode, signal) => {
this.recordAgentExit("process_exit", exitCode, signal);
@@ -776,7 +777,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
});
child.once("close", (exitCode, signal) => {
this.recordAgentExit("process_close", exitCode, signal);
@@ -5100,6 +5402,9 @@
@@ -5100,6 +5402,9 @@ var AcpClient = class {
return await this.filesystem.readTextFile(params);
} catch (error) {
this.recordPermissionError(params.sessionId, error);
@@ -786,7 +787,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
throw error;
}
}
@@ -5239,6 +5544,7 @@
@@ -5239,6 +5544,7 @@ function applyConversation(record, conversation) {
record.cumulative_token_usage = conversation.cumulative_token_usage;
record.cumulative_cost = conversation.cumulative_cost;
record.request_token_usage = conversation.request_token_usage;
@@ -794,7 +795,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
}
//#endregion
//#region src/runtime/engine/session-options.ts
@@ -5701,7 +6007,8 @@
@@ -5701,7 +6007,8 @@ function cloneSessionConversation(conversation) {
updated_at: conversation.updated_at,
cumulative_token_usage: deepClone(conversation.cumulative_token_usage ?? {}),
cumulative_cost: cloneUsageCost(conversation.cumulative_cost),
@@ -804,7 +805,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
};
}
function cloneUsageCost(cost) {
@@ -5771,10 +6078,20 @@
@@ -5771,10 +6078,20 @@ function recordSessionUpdate(conversation, state, notification, timestamp = isoN
trimConversationForRuntime(conversation);
return acpx;
}
@@ -827,7 +828,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
updateConversationTimestamp(conversation, timestamp);
trimConversationForRuntime(conversation);
return true;
@@ -6136,6 +6453,7 @@
@@ -6136,6 +6453,7 @@ async function connectAndLoadSession(options) {
pendingAgentSessionId = loadState.pendingAgentSessionId;
sessionModels = loadState.sessionModels;
const preferenceReplay = await replayFreshSessionPreferences({
@@ -835,7 +836,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
client,
record,
createdFreshSession,
@@ -6193,14 +6511,16 @@
@@ -6193,14 +6511,16 @@ function logReconnectAttempt(record, storedProcessAlive, shouldReconnect, verbos
if (shouldReconnect) process.stderr.write(`[acpx] saved session pid ${record.pid} is dead; respawning agent and attempting session reconnect\n`);
}
async function replayFreshSessionPreferences(params) {
@@ -854,7 +855,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
client: params.client,
sessionId: params.sessionId,
desiredModeId: params.desiredModeId,
@@ -6219,7 +6539,7 @@
@@ -6219,7 +6539,7 @@ async function replayFreshSessionPreferences(params) {
verbose: params.verbose,
suppressWarnings: params.suppressWarnings
});
@@ -863,7 +864,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
client: params.client,
record: params.record,
sessionId: params.sessionId,
@@ -6435,6 +6755,27 @@
@@ -6435,6 +6755,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;
@@ -891,7 +892,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
async function runPromptTurn(params) {
try {
const promptPromise = params.client.prompt(params.sessionId, params.prompt, params.onPromptRequestStarted, params.onElicitation);
@@ -6444,7 +6785,23 @@
@@ -6444,7 +6785,23 @@ async function runPromptTurn(params) {
idleMs: SESSION_REPLY_IDLE_MS,
timeoutMs: SESSION_REPLY_DRAIN_TIMEOUT_MS
}).catch(() => {});
@@ -916,7 +917,7 @@ diff --git a/dist/live-checkpoint-BSIrfgVo.js b/dist/live-checkpoint-BSIrfgVo.js
stopReason: response.stopReason,
source: "rpc"
diff --git a/dist/runtime.d.ts b/dist/runtime.d.ts
index e8102acb0..377c5d838 100644
index e8102acb03c4c38830ad5ec22f356125eb0423b7..04c7e3ae0453b352447efb53af17f6e25ee98307 100644
--- a/dist/runtime.d.ts
+++ b/dist/runtime.d.ts
@@ -1,7 +1,8 @@
@@ -949,10 +950,15 @@ index e8102acb0..377c5d838 100644
type AcpRuntimeEvent = {
type: "text_delta";
text: string;
@@ -186,6 +193,10 @@ type AcpRuntimeEvent = {
@@ -186,6 +193,15 @@ type AcpRuntimeEvent = {
* non-null `input` schema.
*/
availableCommands?: AcpRuntimeAvailableCommand[];
+ /**
+ * Paperclip patch: the ACP update's `_meta`, forwarded by reference and
+ * unvalidated. Producer-controlled; consumers must bound and allowlist it.
+ */
+ _meta?: Record<string, unknown>;
+} | {
+ type: "plan";
+ tag: "plan";
@@ -960,7 +966,19 @@ index e8102acb0..377c5d838 100644
} | {
type: "tool_call";
text: string;
@@ -312,7 +323,51 @@ type AcpRuntimeOptions = {
@@ -198,6 +214,11 @@ type AcpRuntimeEvent = {
rawInput?: unknown;
rawOutput?: unknown;
content?: ToolCallContent[];
+ /**
+ * Paperclip patch: the ACP update's `_meta`, forwarded by reference and
+ * unvalidated. Producer-controlled; consumers must bound and allowlist it.
+ */
+ _meta?: Record<string, unknown>;
} |
/**
* Compatibility terminal event emitted by runTurn(...). startTurn(...).events
@@ -312,7 +333,51 @@ type AcpRuntimeOptions = {
elicitationModes?: readonly AcpElicitationMode[];
onPermissionRequest?: (req: AcpPermissionRequest, ctx: {
signal: AbortSignal;
@@ -1012,7 +1030,7 @@ index e8102acb0..377c5d838 100644
};
type AcpFileSessionStoreOptions = {
stateDir: string;
@@ -369,6 +424,7 @@ declare class AcpRuntimeManager {
@@ -369,6 +434,7 @@ declare class AcpRuntimeManager {
private createAndSaveRuntimeRecord;
private retainInitializedSessionOwner;
startTurn(input: {
@@ -1020,7 +1038,7 @@ index e8102acb0..377c5d838 100644
handle: AcpRuntimeHandle;
text: string;
attachments?: AcpRuntimeTurnAttachment[];
@@ -414,6 +470,7 @@ declare class AcpRuntimeManager {
@@ -414,6 +480,7 @@ declare class AcpRuntimeManager {
private cleanupRuntimeTurn;
private finalizeRuntimeTurnRecord;
runTurn(input: {
@@ -1029,7 +1047,7 @@ index e8102acb0..377c5d838 100644
text: string;
attachments?: AcpRuntimeTurnAttachment[];
diff --git a/dist/runtime.js b/dist/runtime.js
index a1f4a70a0..a17fce3b3 100644
index a1f4a70a003792c6eacf68b6b038f37bfec1db53..f8603a26e626253b454812865b99b0d7aa4495f6 100644
--- a/dist/runtime.js
+++ b/dist/runtime.js
@@ -153,7 +153,7 @@ function resolveTextChunk(params) {
@@ -1050,7 +1068,15 @@ index a1f4a70a0..a17fce3b3 100644
return {
type: "text_delta",
text,
@@ -371,7 +371,7 @@ const PROMPT_EVENT_PARSERS = {
@@ -321,6 +321,7 @@ function assignForwardedToolPayload(event, payload) {
if (Object.prototype.hasOwnProperty.call(payload, "rawInput")) event.rawInput = payload.rawInput;
if (Object.prototype.hasOwnProperty.call(payload, "rawOutput")) event.rawOutput = payload.rawOutput;
if (shouldForwardArray(payload.content)) event.content = payload.content;
+ if (isRecord(payload._meta)) event._meta = payload._meta;
}
function parsePromptEventLine(line) {
const trimmed = line.trim();
@@ -371,7 +372,7 @@ const PROMPT_EVENT_PARSERS = {
current_mode_update: (payload) => statusUpdateEvent("current_mode_update", payload),
config_option_update: (payload) => statusUpdateEvent("config_option_update", payload),
session_info_update: (payload) => statusUpdateEvent("session_info_update", payload),
@@ -1059,7 +1085,32 @@ index a1f4a70a0..a17fce3b3 100644
client_operation: clientOperationEvent,
update: updateStatusEvent,
done: () => null,
@@ -424,6 +424,34 @@ function availableCommandsUpdateEvent(payload) {
@@ -388,11 +389,12 @@ function usageUpdateEvent(payload) {
used,
size,
cost: normalizeUsageCost(payload.cost),
- breakdown: normalizeUsageBreakdown(meta?.usage)
+ breakdown: normalizeUsageBreakdown(meta?.usage),
+ meta
});
}
function buildUsageUpdateEvent(parts) {
- const { used, size, cost, breakdown } = parts;
+ const { used, size, cost, breakdown, meta } = parts;
return {
type: "status",
text: used != null && size != null ? `usage updated: ${used}/${size}` : "usage updated",
@@ -400,7 +402,8 @@ function buildUsageUpdateEvent(parts) {
...used != null ? { used } : {},
...size != null ? { size } : {},
...cost ? { cost } : {},
- ...breakdown ? { breakdown } : {}
+ ...breakdown ? { breakdown } : {},
+ ...meta ? { _meta: meta } : {}
};
}
function availableCommandsUpdateEvent(payload) {
@@ -424,6 +427,34 @@ function availableCommandsUpdateEvent(payload) {
availableCommands
};
}
@@ -1094,7 +1145,17 @@ index a1f4a70a0..a17fce3b3 100644
function normalizeUsageCost(value) {
if (!isRecord(value)) return;
const amount = asOptionalFiniteNumber(value.amount);
@@ -812,7 +840,22 @@ var AcpRuntimeManager = class {
@@ -474,7 +505,8 @@ function statusUpdateEvent(tag, payload) {
return {
type: "status",
text,
- tag
+ tag,
+ ...isRecord(payload._meta) ? { _meta: payload._meta } : {}
};
}
function clientOperationEvent(payload, tag) {
@@ -812,7 +844,22 @@ var AcpRuntimeManager = class {
this.deps = deps;
}
createClient(options) {
@@ -1118,7 +1179,7 @@ index a1f4a70a0..a17fce3b3 100644
}
createSessionOwner(input) {
const owner = {
@@ -821,12 +864,16 @@ var AcpRuntimeManager = class {
@@ -821,12 +868,16 @@ var AcpRuntimeManager = class {
pendingSessionUpdates: []
};
input.client.setEventHandlers({
@@ -1135,7 +1196,7 @@ index a1f4a70a0..a17fce3b3 100644
const active = owner.activeTurn;
if (active) {
const { task, turn } = active;
@@ -863,6 +910,9 @@ var AcpRuntimeManager = class {
@@ -863,6 +914,9 @@ var AcpRuntimeManager = class {
projection.checkpoint.request();
}
routeOwnedClientOperation(owner, operation) {
@@ -1145,7 +1206,7 @@ index a1f4a70a0..a17fce3b3 100644
const active = owner.activeTurn;
if (!active) return;
const { task, turn } = active;
@@ -1008,6 +1058,8 @@ var AcpRuntimeManager = class {
@@ -1008,6 +1062,8 @@ var AcpRuntimeManager = class {
record.closedAt = void 0;
record.protocolVersion = owner.client.initializeResult?.protocolVersion;
record.agentCapabilities = owner.client.initializeResult?.agentCapabilities;
@@ -1154,7 +1215,7 @@ index a1f4a70a0..a17fce3b3 100644
applyLifecycleSnapshotToRecord(record, owner.client.getAgentLifecycleSnapshot());
}
async finishBufferedOwnerControl(owner, record) {
@@ -1086,6 +1138,11 @@ var AcpRuntimeManager = class {
@@ -1086,6 +1142,11 @@ var AcpRuntimeManager = class {
record.closed = false;
record.closedAt = void 0;
this.closingActiveRecords.delete(record.acpxRecordId);
@@ -1166,7 +1227,7 @@ index a1f4a70a0..a17fce3b3 100644
await this.options.sessionStore.save(record);
return record;
}
@@ -1149,6 +1206,8 @@ var AcpRuntimeManager = class {
@@ -1149,6 +1210,8 @@ var AcpRuntimeManager = class {
this.closingActiveRecords.delete(record.acpxRecordId);
record.protocolVersion = client.initializeResult?.protocolVersion;
record.agentCapabilities = client.initializeResult?.agentCapabilities;
@@ -1175,7 +1236,7 @@ index a1f4a70a0..a17fce3b3 100644
applyConfigOptionsToRecord(record, session.sessionResult);
const modelApplication = await applyRequestedModelIfAdvertised({
client,
@@ -1301,9 +1360,11 @@ var AcpRuntimeManager = class {
@@ -1301,9 +1364,11 @@ var AcpRuntimeManager = class {
client: turn.client,
sessionId,
prompt: task.promptInput,
@@ -1187,7 +1248,7 @@ index a1f4a70a0..a17fce3b3 100644
onPromptRequestStarted: () => task.promptStarted.resolve(),
onElicitation: task.input.onElicitation
});
@@ -1469,6 +1530,10 @@ var AcpRuntimeManager = class {
@@ -1469,6 +1534,10 @@ var AcpRuntimeManager = class {
setSessionConfigOption: async (configId, value) => {
return (await task.state.activeController.setResolvedSessionConfigOption(configId, value)).response;
},
@@ -1198,7 +1259,7 @@ index a1f4a70a0..a17fce3b3 100644
setResolvedSessionConfigOption: async (configId, value) => await this.setRuntimeResolvedSessionConfigOption(task, turn, configId, value)
};
}
@@ -1575,6 +1640,8 @@ var AcpRuntimeManager = class {
@@ -1575,6 +1644,8 @@ var AcpRuntimeManager = class {
reconcileAgentSessionId(turn.record, turn.record.agentSessionId);
turn.record.protocolVersion = turn.client.initializeResult?.protocolVersion;
turn.record.agentCapabilities = turn.client.initializeResult?.agentCapabilities;
@@ -1207,7 +1268,7 @@ index a1f4a70a0..a17fce3b3 100644
turn.record.acpx = turn.acpxState;
applyConversation(turn.record, turn.conversation);
applyLifecycleSnapshotToRecord(turn.record, turn.client.getAgentLifecycleSnapshot());
@@ -1707,6 +1774,17 @@ var AcpRuntimeManager = class {
@@ -1707,6 +1778,17 @@ var AcpRuntimeManager = class {
});
await this.options.sessionStore.save(result.record);
}
@@ -1225,7 +1286,7 @@ index a1f4a70a0..a17fce3b3 100644
async cancel(handle) {
await this.activeControllers.get(handle.acpxRecordId ?? handle.sessionKey)?.requestCancelActivePrompt();
}
@@ -2055,6 +2133,7 @@ var AcpxRuntime = class {
@@ -2055,6 +2137,7 @@ var AcpxRuntime = class {
const turnPromise = this.getManager().then((manager) => manager.startTurn({
handle,
text: input.text,
@@ -1233,7 +1294,7 @@ index a1f4a70a0..a17fce3b3 100644
attachments: input.attachments,
mode: input.mode,
sessionMode: state.mode,
@@ -2087,6 +2166,7 @@ var AcpxRuntime = class {
@@ -2087,6 +2170,7 @@ var AcpxRuntime = class {
yield* (await this.getManager()).runTurn({
handle,
text: input.text,
@@ -1241,7 +1302,7 @@ index a1f4a70a0..a17fce3b3 100644
attachments: input.attachments,
mode: input.mode,
sessionMode: state.mode,
@@ -2119,6 +2199,14 @@ var AcpxRuntime = class {
@@ -2119,6 +2203,14 @@ var AcpxRuntime = class {
const { handle, state } = this.resolveManagerHandle(input.handle);
await (await this.getManager()).setConfigOption(handle, input.key, input.value, state.mode);
}
@@ -1257,7 +1318,7 @@ index a1f4a70a0..a17fce3b3 100644
const { handle } = this.resolveManagerHandle(input.handle);
await (await this.getManager()).cancel(handle);
diff --git a/dist/session-options-DwRDODlr.d.ts b/dist/session-options-DwRDODlr.d.ts
index c3da16452..40ae05653 100644
index c3da1645235bbea22de3f8484149051cd7dca56b..40ae0565394aaa96e0315a2cf3c943c500c77560 100644
--- a/dist/session-options-DwRDODlr.d.ts
+++ b/dist/session-options-DwRDODlr.d.ts
@@ -1,4 +1,5 @@