billion-context-pi 0.1.35 → 0.1.36

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 CHANGED
@@ -148,7 +148,10 @@ Create `~/.pi/acp.json` (global) and/or `<project>/.pi/acp.json` (project-local,
148
148
  "modelContextLimit": 200000,
149
149
  "delegate": true,
150
150
  "toolBashDefaultTimeout": 60,
151
- "toolOutputMaxBytes": 200000
151
+ "toolOutputMaxBytes": 200000,
152
+ "maxContextLimit": "75%",
153
+ "emergencyThresholdPercent": "95%",
154
+ "nudgeGrowthTokens": 50000
152
155
  }
153
156
  ```
154
157
 
@@ -160,8 +163,11 @@ Create `~/.pi/acp.json` (global) and/or `<project>/.pi/acp.json` (project-local,
160
163
  | `delegate` | `true` | Enable the `acp_delegate` tools (delegate/wait/cancel) and their system-prompt section. Set `false` to skip registering them (e.g. you use a different sub-agent extension, or run headless where async injection adds no value). |
161
164
  | `toolBashDefaultTimeout` | `60` | Seconds injected into the `bash` tool when the model omits `timeout`. Pi has **no** default of its own, so without this a forgotten timeout can hang for thousands of seconds. On timeout the model is guided to re-run with a larger `timeout`. `0` restores Pi's unbounded behavior. |
162
165
  | `toolOutputMaxBytes` | `200000` | Hard byte cap on tool result text (~5000 lines at ~40 B/line; applied via the `tool_result` hook). Stops runaway output that Pi's own 50KB/2000-line cap can't catch (e.g. tools Pi doesn't cap). When it fires the model is told how to see the full output — for `bash` the full output is in its temp file (`BashToolDetails.fullOutputPath`); set lower (e.g. `8192`) for a tighter context budget, or `0` to disable. |
166
+ | `maxContextLimit` | `"75%"` | Context usage threshold that triggers **forced compression** nudges (bypasses growth-gate + cadence). Accepts a ratio (`0.75`) or percent string (`"75%"`). Lower = compress earlier / more aggressively. Maps to kernel `nudge.maxContextLimitPct`. |
167
+ | `emergencyThresholdPercent` | `"95%"` | Context usage threshold that triggers **emergency truncation** of large tool outputs to keep the session alive. Accepts a ratio (`0.95`) or percent string (`"95%"`). Must be ≥ `maxContextLimit`. Maps to kernel `nudge.emergencyThresholdPct` + `truncate.threshold`. |
168
+ | `nudgeGrowthTokens` | `50000` | Token growth step for soft compression nudges. A nudge fires roughly every time this many tokens become compressible. Lower = compress more often; higher = compress less often. Maps to kernel `nudge.growthFloor` + `nudge.growthCap`. |
163
169
 
164
- > **Only these six keys are read from `acp.json`.** Other tuning knobs (`preserveRecentMessages`, `protectedTools`, nudge thresholds) are code-level and not user-overridable.
170
+ > **Only these nine keys are read from `acp.json`.** Other tuning knobs (`preserveRecentMessages`, `protectedTools`) are code-level and not user-overridable. The three nudge thresholds (`maxContextLimit`, `emergencyThresholdPercent`, `nudgeGrowthTokens`) form a three-tier escalation: growth-driven soft nudges → forced nudges at `maxContextLimit` → emergency truncation at `emergencyThresholdPercent`.
165
171
 
166
172
  ### Environment variables
167
173
 
package/README.zh-CN.md CHANGED
@@ -147,7 +147,10 @@ billion-context-pi 开箱即用,无需任何配置。可以在 JSON 配置文件
147
147
  "modelContextLimit": 200000,
148
148
  "delegate": true,
149
149
  "toolBashDefaultTimeout": 60,
150
- "toolOutputMaxBytes": 200000
150
+ "toolOutputMaxBytes": 200000,
151
+ "maxContextLimit": "75%",
152
+ "emergencyThresholdPercent": "95%",
153
+ "nudgeGrowthTokens": 50000
151
154
  }
152
155
  ```
153
156
 
@@ -159,8 +162,11 @@ billion-context-pi 开箱即用,无需任何配置。可以在 JSON 配置文件
159
162
  | `delegate` | `true` | 启用 `acp_delegate` 工具(delegate/wait/cancel)及其系统提示词段落。设为 `false` 则不注册这些工具(例如你用了别的子代理扩展,或跑 headless 场景异步注入没有意义)。 |
160
163
  | `toolBashDefaultTimeout` | `60` | 当模型未指定 `timeout` 时注入 `bash` 工具的超时秒数。Pi **本身没有默认超时**,不加这个,一次遗漏的超时可能挂起几千秒。超时后会提示模型用更大的 `timeout` 重跑。设为 `0` 恢复 Pi 的无界行为。 |
161
164
  | `toolOutputMaxBytes` | `200000` | 工具结果文本硬上限(字节,约 5000 行 @ ~40 字节/行,通过 `tool_result` hook 应用)。用于兜住 Pi 自身 50KB/2000 行截断管不到的输出(例如 Pi 未加限制的工具)。触发截断时会告诉模型如何查看完整输出——对 `bash`,完整输出在其临时文件(`BashToolDetails.fullOutputPath`)中;设更小(如 `8192`)可更省上下文,设 `0` 关闭。 |
165
+ | `maxContextLimit` | `"75%"` | 上下文使用率达到此值时触发**强制压缩** nudge(绕过增长门控 + 频率限制)。支持比例(`0.75`)或百分比字符串(`"75%"`)。调小 → 更早/更激进压缩。映射到内核 `nudge.maxContextLimitPct`。 |
166
+ | `emergencyThresholdPercent` | `"95%"` | 上下文使用率达到此值时触发**紧急截断**,硬截断大块工具输出以保住会话。支持比例(`0.95`)或百分比字符串(`"95%"`)。必须 ≥ `maxContextLimit`。映射到内核 `nudge.emergencyThresholdPct` + `truncate.threshold`。 |
167
+ | `nudgeGrowthTokens` | `50000` | 软压缩 nudge 的增长步长(token)。大约每积累这么多可压缩 token 就触发一次 nudge。调小 → 压得更频繁;调大 → 压得更少。映射到内核 `nudge.growthFloor` + `nudge.growthCap`。 |
162
168
 
163
- > **只有这六个 key 会被 `acp.json` 读取。** 其他调优参数(`preserveRecentMessages`、`protectedTools`、nudge 阈值)是代码级的,不向用户开放。
169
+ > **只有这九个 key 会被 `acp.json` 读取。** 其他调优参数(`preserveRecentMessages`、`protectedTools`)是代码级的,不向用户开放。三个 nudge 阈值(`maxContextLimit`、`emergencyThresholdPercent`、`nudgeGrowthTokens`)构成三级触发:增长驱动的软 nudge → `maxContextLimit` 强制 nudge → `emergencyThresholdPercent` 紧急截断。
164
170
 
165
171
  ### 环境变量
166
172
 
package/dist/config.d.ts CHANGED
@@ -1,4 +1,33 @@
1
1
  import { type Config } from "acp-kernel";
2
+ /** Delegate sub-agent configuration. */
3
+ export interface DelegateConfig {
4
+ /** Enable acp_delegate tools (delegate/wait/cancel) and their system-prompt
5
+ * section. Default: true. Set `enabled: false` to skip registering them. */
6
+ enabled?: boolean;
7
+ /** How delegate usage is reported back to the main session.
8
+ * "separate" (default) — delegate tokens tracked in a separate accumulator;
9
+ * main session totals stay clean, delegate usage shows as its own block in
10
+ * acp_status (excluded from main totals).
11
+ * "merged" — delegate token usage folded into the tool-result usage field,
12
+ * counted as part of the main session totals. */
13
+ displayUsage?: "merged" | "separate";
14
+ }
15
+ /** Compression tuning. All fields accept a ratio (0.75) or percent string
16
+ * ("75%") where noted. */
17
+ export interface CompressConfig {
18
+ /** Context usage percentage that triggers forced compression nudges
19
+ * (bypasses growth-gate + cadence). Accepts a ratio (0.75) or percent
20
+ * string ("75%"). Default: 0.75. Maps to kernel nudge.maxContextLimitPct. */
21
+ maxContextLimit?: number | string;
22
+ /** Context usage percentage that triggers emergency truncation of large
23
+ * tool outputs. Accepts a ratio (0.95) or percent string ("95%").
24
+ * Default: 0.95. Must be >= maxContextLimit. Maps to kernel
25
+ * nudge.emergencyThresholdPct + truncate.threshold. */
26
+ emergencyThresholdPercent?: number | string;
27
+ /** Token growth threshold for soft compression nudges. Default: 50000.
28
+ * Maps to kernel nudge.growthFloor + nudge.growthCap. */
29
+ nudgeGrowthTokens?: number;
30
+ }
2
31
  /**
3
32
  * Adapter configuration. Maps onto acp-kernel's `Config` plus Pi-specific knobs
4
33
  * (live model context window, protected tools, state persistence).
@@ -18,16 +47,6 @@ export interface AdapterConfig {
18
47
  * warnings) are written regardless; `debug` only adds verbose diagnostics.
19
48
  * Default: false (or env ACP_DEBUG=1/true). */
20
49
  debug?: boolean;
21
- /** Enable acp_delegate tools (delegate/wait/cancel) and their system-prompt
22
- * section. Default: true. Set `delegate: false` (adapter config or
23
- * ~/.pi/acp.json) to skip registering them. */
24
- delegate?: boolean;
25
- /** How to display delegate usage in session totals.
26
- * "merged" — delegate usage is added to main session totals (Pi default).
27
- * "separate" — delegate usage is tracked separately, displayed in
28
- * system notifications (excluded from main session totals).
29
- * Default: "separate" */
30
- displayUsage?: "merged" | "separate";
31
50
  /** Default timeout in seconds injected into the bash tool when the model
32
51
  * omits `timeout`. Pi has NO built-in default, so without this a command
33
52
  * that the model forgets to time out can hang for thousands of seconds.
@@ -44,8 +63,24 @@ export interface AdapterConfig {
44
63
  * head-truncated with a notice telling the model how to see the full output
45
64
  * (bash: read BashToolDetails.fullOutputPath). */
46
65
  toolOutputMaxBytes?: number;
66
+ /** Delegate sub-agent config. Accepts a boolean shorthand (`true` →
67
+ * `{ enabled: true }`, `false` → `{ enabled: false }`) or a DelegateConfig
68
+ * object. Default: enabled. */
69
+ delegate?: boolean | DelegateConfig;
70
+ /** Compression tuning. */
71
+ compress?: CompressConfig;
72
+ /** Legacy flat alias for `delegate.displayUsage`. Kept for backward
73
+ * compatibility with existing acp.json files. Prefer `delegate.displayUsage`. */
74
+ displayUsage?: "merged" | "separate";
47
75
  coreOverrides?: Partial<Config>;
48
76
  }
49
77
  export declare const DEFAULT_TOOL_BASH_TIMEOUT = 60;
50
78
  export declare const DEFAULT_TOOL_OUTPUT_MAX_BYTES = 200000;
79
+ /** Resolve delegate config from the adapter, handling the boolean shorthand
80
+ * and the legacy flat `displayUsage` alias. */
81
+ export declare function resolveDelegate(adapter: AdapterConfig): {
82
+ enabled: boolean;
83
+ displayUsage: "merged" | "separate";
84
+ };
51
85
  export declare function resolveConfig(adapter: AdapterConfig, liveContextLimit: number): Config;
86
+ export declare function parsePercent(v: number | string): number;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionFactory } from "@earendil-works/pi-coding-agent";
2
- import type { AdapterConfig } from "./config.js";
2
+ import { type AdapterConfig } from "./config.js";
3
3
  export declare function createAcpExtension(adapter?: AdapterConfig): ExtensionFactory;
4
4
  declare const _default: ExtensionFactory;
5
5
  export default _default;
package/dist/index.js CHANGED
@@ -263,7 +263,7 @@ function defaultConfig(modelContextLimit, overrides = {}) {
263
263
  const base = {
264
264
  tiers: { enabled: true, tier2Trigger: 5, tier3Trigger: 10 },
265
265
  nudge: {
266
- maxContextLimitPct: 0.55,
266
+ maxContextLimitPct: 0.75,
267
267
  minContextLimitPct: 0.45,
268
268
  frequency: 5,
269
269
  iterationThreshold: 15,
@@ -273,10 +273,10 @@ function defaultConfig(modelContextLimit, overrides = {}) {
273
273
  growthCap: 5e4,
274
274
  minGrowthFloor: 2e4,
275
275
  minGrowthRatio: 0.45,
276
- emergencyThresholdPct: 0.8
276
+ emergencyThresholdPct: 0.95
277
277
  },
278
278
  promotionThreshold: 5,
279
- truncate: { threshold: 1 },
279
+ truncate: { threshold: 0.95 },
280
280
  compress: {
281
281
  minCompressRange: 5e3,
282
282
  maxSummaryLength: 2e4,
@@ -306,6 +306,11 @@ function validateConfig(config) {
306
306
  "nudge.minContextLimitPct must not exceed nudge.maxContextLimitPct"
307
307
  );
308
308
  }
309
+ if (config.nudge.maxContextLimitPct > config.nudge.emergencyThresholdPct) {
310
+ errors.push(
311
+ "nudge.maxContextLimitPct must not exceed nudge.emergencyThresholdPct"
312
+ );
313
+ }
309
314
  if (config.promotionThreshold < 1) {
310
315
  errors.push("promotionThreshold must be >= 1");
311
316
  }
@@ -1466,6 +1471,7 @@ function decideNudge(input) {
1466
1471
  const limit = config.modelContextLimit;
1467
1472
  const usage = limit > 0 ? tokenCount / limit : 0;
1468
1473
  const nudgeGrowthTokens = resolveAdaptiveGrowth(limit, config.nudge);
1474
+ const overLimit = usage >= config.nudge.maxContextLimitPct;
1469
1475
  const emergencyOverride = usage >= config.nudge.emergencyThresholdPct;
1470
1476
  const baseline = state.nudge.lastPerMessageNudgeTokens;
1471
1477
  const hadPendingNudge = state.nudge.lastNudgeShownTokens > 0;
@@ -1482,7 +1488,7 @@ function decideNudge(input) {
1482
1488
  let injectedTier = null;
1483
1489
  let injectedReason = "";
1484
1490
  const growthReady = growthSinceReference >= growthFloor;
1485
- if (!emergencyOverride && growthReady) {
1491
+ if (!overLimit && growthReady) {
1486
1492
  for (const tier of [1, 2, 3]) {
1487
1493
  if (!config.tiers.enabled && tier > 1) break;
1488
1494
  const info = tiers[tier];
@@ -1494,11 +1500,26 @@ function decideNudge(input) {
1494
1500
  injectedReason = tier === 1 ? `T1 compressible ${info.pending} >= ${nudgeGrowthTokens}, growth ${growthSinceReference}, usage ${Math.round(usage * 100)}%` : `T${tier} distill ready: ${info.targetBlocks.length} tier-${tier - 1} blocks (${info.pending} tokens) >= ${nudgeGrowthTokens}, usage ${Math.round(usage * 100)}%`;
1495
1501
  break;
1496
1502
  }
1503
+ } else if (overLimit) {
1504
+ for (const tier of [1, 2, 3]) {
1505
+ if (!config.tiers.enabled && tier > 1) break;
1506
+ const info = tiers[tier];
1507
+ if (!info || info.pending < config.compress.minCompressRange) continue;
1508
+ injectedTier = tier;
1509
+ injectedReason = emergencyOverride ? `EMERGENCY: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.emergencyThresholdPct * 100)}%, T${tier} pending ${info.pending}` : `OVER-LIMIT: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.maxContextLimitPct * 100)}%, T${tier} pending ${info.pending}`;
1510
+ break;
1511
+ }
1497
1512
  }
1498
- const shouldInject = emergencyOverride || injectedTier !== null;
1513
+ const shouldInject = injectedTier !== null || overLimit && (rec?.recommendedRanges?.length ?? 0) > 0;
1499
1514
  let reason;
1500
- if (emergencyOverride) {
1501
- reason = `EMERGENCY: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.emergencyThresholdPct * 100)}%`;
1515
+ if (emergencyOverride && injectedTier !== null) {
1516
+ reason = injectedReason;
1517
+ } else if (emergencyOverride) {
1518
+ reason = `EMERGENCY: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.emergencyThresholdPct * 100)}% (no compressible content)`;
1519
+ } else if (overLimit && injectedTier !== null) {
1520
+ reason = injectedReason;
1521
+ } else if (overLimit) {
1522
+ reason = `OVER-LIMIT: usage ${Math.round(usage * 100)}% >= ${Math.round(config.nudge.maxContextLimitPct * 100)}% (no compressible content)`;
1502
1523
  } else if (injectedTier !== null) {
1503
1524
  reason = injectedReason;
1504
1525
  } else {
@@ -1534,6 +1555,7 @@ function decideNudge(input) {
1534
1555
  nudgeGrowthTokens,
1535
1556
  growthFloor,
1536
1557
  hasPendingNudge: hasPendingNudge ? 1 : 0,
1558
+ overLimit: overLimit ? 1 : 0,
1537
1559
  emergencyOverride: emergencyOverride ? 1 : 0,
1538
1560
  pendingT1: tiers[1].pending,
1539
1561
  pendingT2: tiers[2].pending,
@@ -1845,7 +1867,7 @@ function renderNudgeText(decision) {
1845
1867
  ].join("\n")
1846
1868
  };
1847
1869
  }
1848
- const isEmergency = !!decision.breakdown?.emergencyOverride;
1870
+ const isEmergency = !!decision.breakdown?.emergencyOverride || !!decision.breakdown?.overLimit;
1849
1871
  if (isEmergency) {
1850
1872
  return {
1851
1873
  voice: "emergency",
@@ -2403,16 +2425,47 @@ function makePreview(text, query, len) {
2403
2425
  // src/config.ts
2404
2426
  var DEFAULT_TOOL_BASH_TIMEOUT = 60;
2405
2427
  var DEFAULT_TOOL_OUTPUT_MAX_BYTES = 2e5;
2428
+ function resolveDelegate(adapter) {
2429
+ const d = adapter.delegate;
2430
+ if (typeof d === "object" && d !== null) {
2431
+ return {
2432
+ enabled: d.enabled !== false,
2433
+ displayUsage: d.displayUsage ?? adapter.displayUsage ?? "separate"
2434
+ };
2435
+ }
2436
+ return {
2437
+ enabled: d !== false,
2438
+ displayUsage: adapter.displayUsage ?? "separate"
2439
+ };
2440
+ }
2406
2441
  function resolveConfig(adapter, liveContextLimit) {
2407
2442
  const envLimit = process.env.ACP_MODEL_CONTEXT_LIMIT;
2408
2443
  const envLimitNum = envLimit ? Number(envLimit) : NaN;
2409
2444
  const FALLBACK_LIMIT = 15e4;
2410
2445
  const limit = !Number.isNaN(envLimitNum) && envLimitNum > 0 ? envLimitNum : adapter.modelContextLimit && adapter.modelContextLimit > 0 ? adapter.modelContextLimit : liveContextLimit > 0 ? liveContextLimit : FALLBACK_LIMIT;
2411
- return defaultConfig(limit, {
2446
+ const config = defaultConfig(limit, {
2412
2447
  protectedTools: adapter.protectedTools ?? [],
2413
2448
  preserveRecentMessages: adapter.preserveRecentMessages ?? 5,
2414
2449
  ...adapter.coreOverrides
2415
2450
  });
2451
+ const c = adapter.compress;
2452
+ if (c?.maxContextLimit !== void 0) config.nudge.maxContextLimitPct = parsePercent(c.maxContextLimit);
2453
+ if (c?.emergencyThresholdPercent !== void 0) {
2454
+ const pct2 = parsePercent(c.emergencyThresholdPercent);
2455
+ config.nudge.emergencyThresholdPct = pct2;
2456
+ config.truncate.threshold = pct2;
2457
+ }
2458
+ if (c?.nudgeGrowthTokens !== void 0) {
2459
+ config.nudge.growthFloor = c.nudgeGrowthTokens;
2460
+ config.nudge.growthCap = c.nudgeGrowthTokens;
2461
+ }
2462
+ return config;
2463
+ }
2464
+ function parsePercent(v) {
2465
+ if (typeof v === "number") return v;
2466
+ const s = v.trim();
2467
+ if (s.endsWith("%")) return Number(s.slice(0, -1)) / 100;
2468
+ return Number(s);
2416
2469
  }
2417
2470
 
2418
2471
  // src/messages.ts
@@ -9172,7 +9225,7 @@ async function handleStatus(args, runtime, ctx) {
9172
9225
  const costStr = cost > 0 ? ` ($${cost.toFixed(4)})` : "";
9173
9226
  extra.push("\u2500\u2500 Session delegate usage (excluded from main totals) \u2500\u2500");
9174
9227
  extra.push(`Tokens: ${delegateUsage.input.toLocaleString()} in, ${delegateUsage.output.toLocaleString()} out (${delegateUsage.totalTokens.toLocaleString()} total)${costStr}`);
9175
- } else if (runtime.adapter.displayUsage === "merged") {
9228
+ } else if (resolveDelegate(runtime.adapter).displayUsage === "merged") {
9176
9229
  extra.push("");
9177
9230
  extra.push("merged mode: delegate usage is included in main session totals.");
9178
9231
  } else {
@@ -9293,7 +9346,7 @@ async function statusReport(runtime, ctx) {
9293
9346
  const activeBlocksList = state.blocks.filter((b) => b.active);
9294
9347
  const totalBlocksList = state.blocks;
9295
9348
  const lines = [];
9296
- const versionStr = "0.1.35" ? `billion-context-pi@${"0.1.35"}` : "";
9349
+ const versionStr = "0.1.36" ? `billion-context-pi@${"0.1.36"}` : "";
9297
9350
  lines.push("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
9298
9351
  lines.push("\u2502 ACP Context Analysis \u2502");
9299
9352
  lines.push("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
@@ -9669,7 +9722,7 @@ async function checkForUpdate(autoUpdate, notify) {
9669
9722
  const data = await res.json();
9670
9723
  const latest = data.version;
9671
9724
  if (!latest) return;
9672
- const current = runtimeVersion ?? "0.1.35";
9725
+ const current = runtimeVersion ?? "0.1.36";
9673
9726
  const hasUpdate = isNewer(latest, current);
9674
9727
  debug.event("update-check", {
9675
9728
  current,
@@ -9862,7 +9915,16 @@ async function loadUserConfig(cwd) {
9862
9915
  function join8(...parts) {
9863
9916
  return path3.join(...parts);
9864
9917
  }
9865
- var KNOWN = /* @__PURE__ */ new Set(["debug", "autoUpdate", "modelContextLimit", "delegate", "displayUsage", "toolBashDefaultTimeout", "toolOutputMaxBytes"]);
9918
+ var KNOWN = /* @__PURE__ */ new Set([
9919
+ "debug",
9920
+ "autoUpdate",
9921
+ "modelContextLimit",
9922
+ "toolBashDefaultTimeout",
9923
+ "toolOutputMaxBytes",
9924
+ "delegate",
9925
+ "compress",
9926
+ "displayUsage"
9927
+ ]);
9866
9928
  function pickKnown(parsed) {
9867
9929
  const out = {};
9868
9930
  for (const [k, v] of Object.entries(parsed)) {
@@ -9874,8 +9936,6 @@ function applyUserConfig(adapter, user) {
9874
9936
  return {
9875
9937
  ...adapter,
9876
9938
  ...user,
9877
- // coreOverrides / protectedTools / preserveRecentMessages are not overridable
9878
- // from acp.json (keep them from the factory config).
9879
9939
  coreOverrides: adapter.coreOverrides,
9880
9940
  protectedTools: adapter.protectedTools,
9881
9941
  preserveRecentMessages: adapter.preserveRecentMessages
@@ -9911,16 +9971,16 @@ function wireSessionLifecycle(pi, runtime) {
9911
9971
  resetDelegateUsage();
9912
9972
  setDelegateDisplayUsage("separate");
9913
9973
  const sid = ctx.sessionManager.getSessionId();
9914
- logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.35" : null });
9974
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.36" : null });
9915
9975
  try {
9916
9976
  const user = await loadUserConfig(ctx.cwd);
9917
9977
  runtime.setAdapter(applyUserConfig(runtime.adapter, user));
9918
- setDelegateDisplayUsage(runtime.adapter.displayUsage ?? "separate");
9978
+ setDelegateDisplayUsage(resolveDelegate(runtime.adapter).displayUsage);
9919
9979
  if (runtime.adapter.debug !== void 0) setDebugEnabled(runtime.adapter.debug);
9920
9980
  } catch (e) {
9921
9981
  logThrow("config", e, { sid, phase: "session_start" });
9922
9982
  }
9923
- if (runtime.adapter.delegate !== false) {
9983
+ if (resolveDelegate(runtime.adapter).enabled) {
9924
9984
  pi.registerTool(makeDelegateTool(pi));
9925
9985
  pi.registerTool(makeDelegateWaitTool(pi));
9926
9986
  pi.registerTool(makeDelegateCancelTool(pi));