Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased (master, since v0.1.38)

- **feat(absorb): 工具结果即时压缩(#14)** — 小上下文场景(如 1w 窗口)下,常规"到阈值再催促压缩"来不及;开启 `absorb: true` 后,大工具结果(默认 ≥1000 tokens)尾部追加强制 `[ACP absorb]` 提示,模型立即调用 `absorb({ ref, summary })` 蒸馏关键信息,原工具调用+结果对在后续轮次隐藏,摘要成为持久记录。absorb 调用是普通工具调用,仍可被常规压缩折叠(两机制正交)。配置:`absorb.minToolTokens` / `absorb.contextThresholdPct` / `absorb.excludeTools` / `absorb.toolName`;内核实现(acp-kernel `2026-08-23_absorb-tool` 分支),本仓库适配

- **fix(compress): 接受 JSON 字符串形式的 `content` 参数** — 非严格工具 provider(vLLM openai-completions,`supportsStrictTools:false`)会把嵌套数组参数字符串化,pi 的 typebox 校验直接拒掉(`content.0: must be object`)。实测会话 01a00a38 全部唯一一次 compress 调用即死于此,3 小时会话零压缩。schema 改为 `Type.Union([Array, String])`,字符串自动 `JSON.parse` 并校验(错误信息引导模型传数组)
- **feat(nudge): compress 失败即时重试提示** — 失败的 compress toolResult 不再白白吃掉本轮 nudge 预算:下一次 context 事件立即注入重试提示(引用被截短的错误文本、给出正确调用格式)。仅统计**当前用户轮**的失败(旧轮失败不再复发),封顶按**失败调用次数**计:每轮最多 3 次(`MAX_COMPRESS_ATTEMPTS`),成功重置;中性结果(非错误非面板文本,如 "No ranges provided.")不重置也不递增——混合失败模式无法绕过封顶。参数类错误改为 throw(pi 仅对 throw 的工具错误标 `isError:true`,return 字符串会被当成成功并重置计数)。提示在重试前每次 LLM 调用都重新注入(pi 每次重建上下文,一次性 append 会消失)
- **fix(context)**: 上下文窗口自愈 — 上游 overflow 时从错误信息学习真实窗口、重新校准 nudge/truncate 阈值,并预留模型输出 headroom(Anthropic 除外);下一轮强制 usage≥95% 触发紧急截断(#177)
Expand Down
51 changes: 51 additions & 0 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ Create `~/.pi/acp.json` (or `<project>/.pi/acp.json`) and drop in whichever keys
"maxContextLimit": "75%",
"emergencyThresholdPercent": "95%",
"nudgeGrowthTokens": 50000
},

"absorb": {
"minToolTokens": 1000,
"contextThresholdPct": 0,
"excludeTools": ["read"]
}
}
```
Expand Down Expand Up @@ -125,6 +131,16 @@ All keys below are currently **ACTIVE**.
| `compress.emergencyThresholdPercent` | number \| string | `"95%"` | 🟢 ACTIVE | Context threshold that triggers emergency truncation. |
| `compress.nudgeGrowthTokens` | number | `50000` | 🟢 ACTIVE | Token growth step for soft compression nudges. |

**Absorb keys**

| Key | Type | Default | Status | Description |
|-----|------|---------|--------|-------------|
| `absorb` | boolean \| object | `false` | 🟢 ACTIVE | `true` enables instant tool-result absorption with defaults; an object tunes it. |
| `absorb.toolName` | string | `"absorb"` | 🟢 ACTIVE | Name of the absorb tool exposed to the model. |
| `absorb.minToolTokens` | number | `1000` | 🟢 ACTIVE | Tool results below this estimated size never get the absorb prompt. |
| `absorb.contextThresholdPct` | number \| string | `0` | 🟢 ACTIVE | Only prompt when context usage is at or above this fraction (`0.3` / `"30%"`); `0` = size alone decides. |
| `absorb.excludeTools` | string[] | `[]` | 🟢 ACTIVE | Tool names whose results are never absorbable. |

**Prompts keys**

| Key | Type | Default | Status | Description |
Expand Down Expand Up @@ -184,6 +200,41 @@ All keys below are currently **ACTIVE**.

---

## Absorb

The `absorb` sub-object enables **instant tool-result compression** — designed for small-context setups (e.g. a 10K–20K window) where waiting for a regular compression nudge starves the model of working room. Tool calls are the biggest context consumer; absorption makes the model pay that cost back immediately after every large tool result.

How it works:

1. When a tool result is large enough (≥ `absorb.minToolTokens` estimated tokens) and not excluded/protected, a forced `[ACP absorb]` instruction is appended to it: the model must immediately call the `absorb` tool with the result's ref and a distilled summary.
2. Once absorbed, the original tool-call + tool-result pair is **hidden from all later turns**; the `absorb` call (carrying your summary) becomes the durable record.
3. `absorb` calls are ordinary tool calls — the regular compression system can fold them into blocks later, so the two mechanisms stay orthogonal.

Shorthand forms (like `delegate`): `absorb: true` enables with defaults; an object tunes it.

### `absorb.minToolTokens`

- **Type:** `number`
- **Default:** `1000`
- **Status:** 🟢 ACTIVE
- **Description:** Tool results estimated below this many tokens never get the absorb prompt. Keep it high enough that only genuinely bulky outputs demand a distill step.

### `absorb.contextThresholdPct`

- **Type:** `number | string`
- **Default:** `0`
- **Status:** 🟢 ACTIVE
- **Description:** Only append absorb prompts when context usage is at or above this fraction of the window (`0.3` or `"30%"`). With the default `0`, size alone decides — every qualifying result is absorbed immediately, which is what small-context setups want.

### `absorb.excludeTools`

- **Type:** `string[]`
- **Default:** `[]`
- **Status:** 🟢 ACTIVE
- **Description:** Tool names whose results are never absorbable. ACP's own tool results (`compress`, `decompress`, `search_context`, `acp_status`, …) and protected tools are always excluded automatically.

---

## Delegate

The `delegate` sub-object controls the `acp_delegate` sub-agent tool family (`acp_delegate`, `acp_delegate_wait`, `acp_delegate_cancel`) and how their token usage is reported.
Expand Down
51 changes: 51 additions & 0 deletions CONFIGURATION.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@
"maxContextLimit": "75%",
"emergencyThresholdPercent": "95%",
"nudgeGrowthTokens": 50000
},

"absorb": {
"minToolTokens": 1000,
"contextThresholdPct": 0,
"excludeTools": ["read"]
}
}
```
Expand Down Expand Up @@ -125,6 +131,16 @@
| `compress.emergencyThresholdPercent` | number \| string | `"95%"` | 🟢 ACTIVE | 触发紧急截断的上下文阈值。 |
| `compress.nudgeGrowthTokens` | number | `50000` | 🟢 ACTIVE | 软压缩 nudge 的 token 增长步长。 |

**Absorb 键**

| 键 | 类型 | 默认值 | 状态 | 说明 |
|-----|------|--------|------|------|
| `absorb` | boolean \| object | `false` | 🟢 ACTIVE | `true` 以默认参数开启工具结果即时吸收;传对象可细调。 |
| `absorb.toolName` | string | `"absorb"` | 🟢 ACTIVE | 暴露给模型的 absorb 工具名。 |
| `absorb.minToolTokens` | number | `1000` | 🟢 ACTIVE | 低于该估算规模的工具结果不触发吸收提示。 |
| `absorb.contextThresholdPct` | number \| string | `0` | 🟢 ACTIVE | 仅当上下文占用达到该比例(`0.3` / `"30%"`)时提示;`0` 表示只看大小。 |
| `absorb.excludeTools` | string[] | `[]` | 🟢 ACTIVE | 永不参与吸收的工具名列表。 |

**prompts 键**

| 键 | 类型 | 默认值 | 状态 | 说明 |
Expand Down Expand Up @@ -184,6 +200,41 @@

---

## Absorb(工具即时压缩)

`absorb` 子对象开启**工具结果即时压缩**——面向小上下文场景(例如 1w–2w 窗口):此时常规的"到阈值再催促压缩"来不及,模型没有干活的空间。工具调用是上下文的最大消耗者;即时吸收让模型在每次大工具输出之后立刻把这笔开销还回去。

工作方式:

1. 当工具结果足够大(估算 ≥ `absorb.minToolTokens`)且未被排除/保护时,内核在其后追加一条强制的 `[ACP absorb]` 指令:要求模型立即调用 `absorb` 工具,带上该结果的 ref 和蒸馏摘要。
2. 吸收完成后,原来的工具调用+工具结果对在**后续轮次中被隐藏**;携带摘要的 `absorb` 调用成为持久记录。
3. `absorb` 调用本身是普通工具调用——之后仍可被常规压缩系统折叠进 block,两个机制彼此正交。

支持简写(同 `delegate`):`absorb: true` 以默认值开启;传对象可细调。

### `absorb.minToolTokens`

- **类型:** `number`
- **默认值:** `1000`
- **状态:** 🟢 ACTIVE
- **说明:** 估算低于该 token 数的工具结果不触发吸收提示。保持足够高,只让真正的大输出走蒸馏步骤。

### `absorb.contextThresholdPct`

- **类型:** `number | string`
- **默认值:** `0`
- **状态:** 🟢 ACTIVE
- **说明:** 仅当上下文占用达到窗口的该比例时(`0.3` 或 `"30%"`)才追加吸收提示。默认 `0` 表示只看大小——每个达标结果都立即吸收,这正是小上下文场景想要的。

### `absorb.excludeTools`

- **类型:** `string[]`
- **默认值:** `[]`
- **状态:** 🟢 ACTIVE
- **说明:** 永不参与吸收的工具名。ACP 自身工具的结果(`compress`、`decompress`、`search_context`、`acp_status` 等)与受保护工具始终自动排除。

---

## Delegate

`delegate` 子对象控制 `acp_delegate` 子代理工具族(`acp_delegate`、`acp_delegate_wait`、`acp_delegate_cancel`)及其 token 用量的报告方式。
Expand Down
75 changes: 75 additions & 0 deletions src/absorb-tool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Type, type Static } from "typebox";
import type {
AgentToolResult,
ExtensionContext,
ToolDefinition,
} from "@earendil-works/pi-coding-agent";
import type { AcpRuntime } from "./runtime.js";
import { logInfo, logThrow, logWarn } from "./log.js";
import { parseAbsorbInput, applyAbsorb } from "acp-kernel";

Check failure on line 9 in src/absorb-tool.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 22)

Module '"acp-kernel"' has no exported member 'applyAbsorb'.

Check failure on line 9 in src/absorb-tool.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 22)

Module '"acp-kernel"' has no exported member 'parseAbsorbInput'.

Check failure on line 9 in src/absorb-tool.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest, 22)

Module '"acp-kernel"' has no exported member 'applyAbsorb'.

Check failure on line 9 in src/absorb-tool.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest, 22)

Module '"acp-kernel"' has no exported member 'parseAbsorbInput'.

Check failure on line 9 in src/absorb-tool.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest, 24)

Module '"acp-kernel"' has no exported member 'applyAbsorb'.

Check failure on line 9 in src/absorb-tool.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest, 24)

Module '"acp-kernel"' has no exported member 'parseAbsorbInput'.

Check failure on line 9 in src/absorb-tool.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 24)

Module '"acp-kernel"' has no exported member 'applyAbsorb'.

Check failure on line 9 in src/absorb-tool.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 24)

Module '"acp-kernel"' has no exported member 'parseAbsorbInput'.

const AbsorbParams = Type.Object({
ref: Type.String({ description: 'Ref of the tool result being absorbed, e.g. "m00042" (the mNNNNN from its acp tag).' }),
summary: Type.String({ description: "Distilled key results that REPLACE the tool output in context. Keep: outcome, key data/values, exact paths:lines, errors verbatim, decisions. You will NOT see the original again." }),
});

type AbsorbArgs = Static<typeof AbsorbParams>;

export function makeAbsorbTool(runtime: AcpRuntime, name = "absorb"): ToolDefinition<typeof AbsorbParams> {
return {
name,
label: "Absorb",
description:
'Distill a large tool result you just read into a compact summary. Once absorbed, the original tool output is removed from context and only your summary remains. Call as: absorb({ ref: "m00042", summary: "..." }).',
promptSnippet: 'absorb({ ref: "m00042", summary: "key results" }) — checkpoint a big tool result',
promptGuidelines: [
"When a tool result carries a forced [ACP absorb] prompt, call absorb with its ref IMMEDIATELY, before any other tool call.",
"The summary must be self-contained — the original output disappears from context after absorption.",
],
parameters: AbsorbParams,
async execute(toolCallId, params, _signal, _onUpdate, ctx): Promise<AgentToolResult<unknown>> {
let result: string;
try {
result = await handleAbsorb(params as AbsorbArgs, runtime, ctx, toolCallId);
} catch (e) {
logThrow("absorb", e, { sid: ctx.sessionManager.getSessionId(), ref: String((params as AbsorbArgs).ref ?? "") });
throw e;
}
return { details: undefined, content: [{ type: "text", text: result }] };
},
};
}

async function handleAbsorb(args: AbsorbArgs, runtime: AcpRuntime, ctx: ExtensionContext, toolCallId?: string): Promise<string> {
const parsed = parseAbsorbInput(args, toolCallId, (message) => logWarn("absorb", { sid: ctx.sessionManager.getSessionId(), event: "lenient-parse", message }));

Check failure on line 44 in src/absorb-tool.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 22)

Parameter 'message' implicitly has an 'any' type.

Check failure on line 44 in src/absorb-tool.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest, 22)

Parameter 'message' implicitly has an 'any' type.

Check failure on line 44 in src/absorb-tool.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest, 24)

Parameter 'message' implicitly has an 'any' type.

Check failure on line 44 in src/absorb-tool.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 24)

Parameter 'message' implicitly has an 'any' type.
if (!parsed || !parsed.ref || !parsed.summary.trim()) {
throw new Error(
"Invalid absorb arguments: provide ref (the mNNNNN from the tool result's acp tag) and summary (the distilled key results that replace it). " +
`Example: absorb({ ref: "m00042", summary: "..." })`,
);
}
const { state: initialState, coreMessages } = await runtime.stateFor(ctx);
const config = runtime.configFor(ctx);
if (!config.absorb?.enabled) return "absorb is disabled — nothing changed.";

Check failure on line 53 in src/absorb-tool.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 22)

Property 'absorb' does not exist on type 'Config'.

Check failure on line 53 in src/absorb-tool.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest, 22)

Property 'absorb' does not exist on type 'Config'.

Check failure on line 53 in src/absorb-tool.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest, 24)

Property 'absorb' does not exist on type 'Config'.

Check failure on line 53 in src/absorb-tool.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 24)

Property 'absorb' does not exist on type 'Config'.
const turn = runtime.core.processTurn({ messages: coreMessages, state: initialState, config, tokenCount: 0 });
const outcome = applyAbsorb({
ref: parsed.ref,
summary: parsed.summary,
absorbCallId: toolCallId,
messages: turn.messages,
state: turn.state,
config,
});
if (!outcome.ok) throw new Error(outcome.resultText);
await runtime.save(outcome.state, ctx);
const record = outcome.state.absorbed?.[outcome.state.absorbed.length - 1];
logInfo("absorb", {
sid: ctx.sessionManager.getSessionId(),
event: "applied",
ref: parsed.ref,
summaryLen: parsed.summary.length,
tokensReclaimed: record?.tokensReclaimed ?? null,
totalAbsorbed: outcome.state.stats?.absorbedTokens ?? null,
});
return outcome.resultText;
}
53 changes: 53 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,28 @@
import { defaultConfig, type Config, type Prompts } from "acp-kernel";
import type { ThrottleRetryConfig } from "./throttle-retry.js";

/** Instant tool-result absorption settings (small-context mode). Mirrors
* acp-kernel's absorb feature: when enabled, every eligible large tool
* result gets a forced [ACP absorb] prompt and the model must distill it
* via the absorb tool; the original tool-call + tool-result pair is then
* hidden from subsequent turns. */
export interface AbsorbSettings {
/** Enable instant absorption. Default: false. */
enabled?: boolean;
/** Registered name of the absorb tool. Default: "absorb". */
toolName?: string;
/** Minimum estimated tokens of a tool result before it demands
* absorption. Smaller results stay as-is. Default: 1000. */
minToolTokens?: number;
/** Context usage percentage (ratio 0.3 or percent string "30%") above
* which absorption prompts fire. 0 = fire on size alone regardless of
* usage. Default: 0. */
contextThresholdPct?: number | string;
/** Tool names whose results are never absorb-prompted (in addition to
* ACP's own tools and protectedTools). */
excludeTools?: string[];
}

/** Delegate sub-agent configuration. */
export interface DelegateConfig {
/** Enable acp_delegate tools (delegate/wait/cancel) and their system-prompt
Expand Down Expand Up @@ -94,6 +116,10 @@
delegate?: boolean | DelegateConfig;
/** Compression tuning. */
compress?: CompressConfig;
/** Instant tool-result absorption (small-context mode). Accepts a boolean
* shorthand (`true` → enabled with defaults) or an AbsorbSettings object.
* Default: disabled. */
absorb?: boolean | AbsorbSettings;
/** Provider token-throttle (Bedrock "Too many tokens, please wait before
* trying again.") auto-retry. Accepts a boolean shorthand (`false`
* disables) or a ThrottleRetryConfig object. Default: enabled, 10 retries,
Expand Down Expand Up @@ -164,6 +190,23 @@
return mergeCompress(compress, prov, model);
}

/** Resolve absorb settings from the adapter config, handling the boolean
* shorthand. contextThresholdPct is parsed to a ratio when provided. */
export function resolveAbsorb(adapter: AdapterConfig): { enabled: boolean; toolName?: string; minToolTokens?: number; contextThresholdPct?: number; excludeTools?: string[] } {
const a = adapter.absorb;
if (a === true) return { enabled: true };
if (typeof a === "object" && a !== null) {
return {
enabled: a.enabled !== false,
toolName: a.toolName,
minToolTokens: a.minToolTokens,
contextThresholdPct: a.contextThresholdPct !== undefined ? parsePercent(a.contextThresholdPct) : undefined,
excludeTools: a.excludeTools,
};
}
return { enabled: false };
}

export function resolveConfig(adapter: AdapterConfig, liveContextLimit: number, provider?: string, modelId?: string): Config {
const envLimit = process.env.ACP_MODEL_CONTEXT_LIMIT;
const envLimitNum = envLimit ? Number(envLimit) : NaN;
Expand Down Expand Up @@ -192,6 +235,16 @@
config.nudge.growthFloor = c.nudgeGrowthTokens;
config.nudge.growthCap = c.nudgeGrowthTokens;
}
const absorb = resolveAbsorb(adapter);
if (absorb.enabled) {
config.absorb = {

Check failure on line 240 in src/config.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 22)

Property 'absorb' does not exist on type 'Config'.

Check failure on line 240 in src/config.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest, 22)

Property 'absorb' does not exist on type 'Config'.

Check failure on line 240 in src/config.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest, 24)

Property 'absorb' does not exist on type 'Config'.

Check failure on line 240 in src/config.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 24)

Property 'absorb' does not exist on type 'Config'.
enabled: true,
toolName: absorb.toolName ?? "absorb",
minToolTokens: absorb.minToolTokens ?? 1000,
contextThresholdPct: absorb.contextThresholdPct ?? 0,
excludeTools: absorb.excludeTools ?? [],
};
}
return config;
}

Expand Down
21 changes: 16 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
SessionMessageEntry,
} from "@earendil-works/pi-coding-agent";
import type { CoreMessage, NudgeDecision, CompressionBlock, Prompts } from "acp-kernel";
import { renderNudgeText, resolvePrompts, defaultPrompts, viableRanges } from "acp-kernel";
import { type AdapterConfig, resolveDelegate } from "./config.js";
import { renderNudgeText, resolvePrompts, defaultPrompts, viableRanges, buildAbsorbSystemPrompt } from "acp-kernel";

Check failure on line 8 in src/index.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 22)

Module '"acp-kernel"' has no exported member 'buildAbsorbSystemPrompt'.

Check failure on line 8 in src/index.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest, 22)

Module '"acp-kernel"' has no exported member 'buildAbsorbSystemPrompt'.

Check failure on line 8 in src/index.ts

View workflow job for this annotation

GitHub Actions / test (windows-latest, 24)

Module '"acp-kernel"' has no exported member 'buildAbsorbSystemPrompt'.

Check failure on line 8 in src/index.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 24)

Module '"acp-kernel"' has no exported member 'buildAbsorbSystemPrompt'.
import { type AdapterConfig, resolveDelegate, resolveAbsorb } from "./config.js";
import { createRuntime, type AcpRuntime, MAX_COMPRESS_ATTEMPTS } from "./runtime.js";
import { makeCompressTool, isCompressSuccessText, isCompressNoopText } from "./compress-tool.js";
import { makeAbsorbTool } from "./absorb-tool.js";
import { makeDecompressTool } from "./decompress-tool.js";
import { makeSearchTool } from "./search-tool.js";
import { makeStatusTool } from "./status-tool.js";
Expand Down Expand Up @@ -52,12 +53,19 @@
pi.registerTool(makeDecompressTool(runtime));
pi.registerTool(makeSearchTool(runtime));
pi.registerTool(makeStatusTool(runtime));
registerAbsorbIfEnabled(pi, runtime, adapter);
for (const { name, options } of makeCommands(runtime)) {
pi.registerCommand(name, options);
}
};
}

function registerAbsorbIfEnabled(pi: ExtensionAPI, runtime: AcpRuntime, adapter: AdapterConfig): void {
const absorb = resolveAbsorb(adapter);
if (!absorb.enabled) return;
pi.registerTool(makeAbsorbTool(runtime, absorb.toolName));
}

export default createAcpExtension();

// ACP owns compression; cancel Pi's built-in auto-compaction entirely (mirrors
Expand Down Expand Up @@ -106,6 +114,7 @@
pi.registerTool(makeDelegateWaitTool(pi));
pi.registerTool(makeDelegateCancelTool(pi));
}
registerAbsorbIfEnabled(pi, runtime, runtime.adapter);
// Headless hosts exit as soon as the turn ends; awaiting the check keeps
// the process alive until a running install finishes. TUI stays
// fire-and-forget so interactive startup is never blocked by npm.
Expand Down Expand Up @@ -379,9 +388,11 @@
function wireSystemPrompt(pi: ExtensionAPI, runtime: AcpRuntime): void {
pi.on("before_agent_start", (event) => {
const delegate = runtime.adapter.delegate !== false;
const acp = buildAcpSystemPrompt(runtime.prompts);
const prompt = delegate ? `${acp}\n${ACP_DELEGATE_PROMPT}` : acp;
return { systemPrompt: formatSystemPromptForEvent(event.systemPrompt, prompt) };
const sections = [buildAcpSystemPrompt(runtime.prompts)];
const absorb = resolveAbsorb(runtime.adapter);
if (absorb.enabled) sections.push(buildAbsorbSystemPrompt(absorb.toolName));
if (delegate) sections.push(ACP_DELEGATE_PROMPT);
return { systemPrompt: formatSystemPromptForEvent(event.systemPrompt, sections.join("\n")) };
});
}

Expand Down
Loading
Loading