billion-context-pi-lean 0.1.69-lean.1 → 0.2.0
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/LICENSE +1 -0
- package/README.md +141 -81
- package/dist/abort.d.ts +1 -0
- package/dist/branch-guard.d.ts +8 -0
- package/dist/cache-tool.d.ts +12 -0
- package/dist/chunk-PPYOZRUP.js +18 -0
- package/dist/chunk-PPYOZRUP.js.map +1 -0
- package/dist/commands.d.ts +8 -0
- package/dist/compat.d.ts +23 -0
- package/dist/compress-model.d.ts +65 -0
- package/dist/compress-tool.d.ts +76 -0
- package/dist/config-dir.d.ts +32 -0
- package/dist/config-panel.d.ts +61 -0
- package/dist/config.d.ts +296 -0
- package/dist/contract.d.ts +51 -0
- package/dist/contract.js +9 -0
- package/dist/contract.js.map +1 -0
- package/dist/decompress-tool.d.ts +19 -0
- package/dist/degeneration.d.ts +98 -0
- package/dist/export.d.ts +20 -0
- package/dist/floor-stale.d.ts +31 -0
- package/dist/host.d.ts +15 -0
- package/dist/idle-compact.d.ts +39 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +23169 -0
- package/dist/index.js.map +1 -0
- package/dist/live-only-tail.d.ts +10 -0
- package/dist/log.d.ts +18 -0
- package/dist/messages.d.ts +85 -0
- package/dist/omp.d.ts +19 -0
- package/dist/overflow-selfheal.d.ts +132 -0
- package/dist/prompt-pack.d.ts +53 -0
- package/dist/proxy-detect.d.ts +21 -0
- package/dist/reasoning-drop.d.ts +68 -0
- package/dist/rule-tool.d.ts +8 -0
- package/dist/runtime.d.ts +199 -0
- package/dist/search-index.d.ts +20 -0
- package/dist/search-tool.d.ts +10 -0
- package/dist/sequence-match.d.ts +7 -0
- package/dist/session-log.d.ts +23 -0
- package/dist/state-rebuild.d.ts +30 -0
- package/dist/state.d.ts +44 -0
- package/dist/status-tool.d.ts +13 -0
- package/dist/strip-images.d.ts +18 -0
- package/dist/summary-sanitize.d.ts +8 -0
- package/dist/surface.d.ts +17 -0
- package/dist/system-passthrough.d.ts +4 -0
- package/dist/system-prompt.d.ts +20 -0
- package/dist/tag-tokens.d.ts +3 -0
- package/dist/throttle-retry.d.ts +52 -0
- package/dist/tokens.d.ts +61 -0
- package/dist/tool-guardrails.d.ts +33 -0
- package/dist/tool-pair-sanitizer.d.ts +8 -0
- package/dist/turn-boundary.d.ts +53 -0
- package/dist/user-config.d.ts +67 -0
- package/lean/index.ts +376 -0
- package/package.json +85 -36
- package/schema/bcp-block-v1.json +42 -0
- package/README.zh-CN.md +0 -79
- package/index.ts +0 -266
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { type Config, type Prompts } from "acp-kernel";
|
|
2
|
+
import type { CompressReasoningConfig } from "./reasoning-drop.js";
|
|
3
|
+
import type { DegenerationGuardConfig } from "./degeneration.js";
|
|
4
|
+
import type { ThrottleRetryConfig } from "./throttle-retry.js";
|
|
5
|
+
import type { IdleCompactConfig } from "./idle-compact.js";
|
|
6
|
+
import type { PiPromptSections } from "./system-prompt.js";
|
|
7
|
+
import type { NudgeSectionsConfig, ToolPromptsConfig } from "./surface.js";
|
|
8
|
+
/** Compression tuning fields, shared by all three levels (global, provider,
|
|
9
|
+
* model). Percentage fields accept a ratio (0.75) or percent string ("75%").
|
|
10
|
+
* Resolution is per-field, deepest-wins (model > provider > global); an
|
|
11
|
+
* undefined field at a deeper level does NOT clear a shallower value. */
|
|
12
|
+
export interface CompressSettings {
|
|
13
|
+
/** Context usage percentage that triggers forced compression nudges
|
|
14
|
+
* (bypasses growth-gate + cadence). Accepts a ratio (0.75) or percent
|
|
15
|
+
* string ("75%"). Default: 0.75. Maps to kernel nudge.maxContextLimitPct. */
|
|
16
|
+
maxContextLimit?: number | string;
|
|
17
|
+
/** Context usage percentage that triggers emergency truncation of large
|
|
18
|
+
* tool outputs. Accepts a ratio (0.95) or percent string ("95%").
|
|
19
|
+
* Default: 0.95. Must be >= maxContextLimit. Maps to kernel
|
|
20
|
+
* nudge.emergencyThresholdPct + truncate.threshold. */
|
|
21
|
+
emergencyThresholdPercent?: number | string;
|
|
22
|
+
/** Token growth threshold for soft compression nudges. Default: 50000.
|
|
23
|
+
* Maps to kernel nudge.growthFloor + nudge.growthCap. */
|
|
24
|
+
nudgeGrowthTokens?: number;
|
|
25
|
+
/** Minimum reclaimable tokens for a pressure-band nudge (kernel #198).
|
|
26
|
+
* Default: max(5000, round(limit×0.01)). Explicit 0 restores the legacy
|
|
27
|
+
* any-pending behavior — useful for tiny windows (e.g. e2e scenarios with
|
|
28
|
+
* modelContextLimit 1500) where a fixed 5000-token floor exceeds the whole
|
|
29
|
+
* window and would suppress every nudge. Maps to kernel
|
|
30
|
+
* nudge.minPressureBenefitTokens. */
|
|
31
|
+
minPressureBenefitTokens?: number;
|
|
32
|
+
/** [#336] Drop oversized reasoning (thinking) from historical `compress`
|
|
33
|
+
* tool calls — see CompressReasoningConfig in src/reasoning-drop.ts.
|
|
34
|
+
* Merged field-wise (drop, threshold) across the three levels. */
|
|
35
|
+
reasoning?: CompressReasoningConfig;
|
|
36
|
+
/** Active prompt pack name (see CONFIGURATION.md “Prompt packs”). Base
|
|
37
|
+
* level; override per provider/model via `providers`. "default" or unset =
|
|
38
|
+
* built-in defaults. Resolved per request against the live model, so
|
|
39
|
+
* switching models mid-session switches the pack. Packs ship text-level
|
|
40
|
+
* overrides only; a pack's `toolPrompts` follow the base selection (tool
|
|
41
|
+
* definitions freeze at extension load, before the model is known). */
|
|
42
|
+
promptPack?: string;
|
|
43
|
+
/** Opt-in wire-level strip of historical image payloads (issue #321, kernel
|
|
44
|
+
* #215). Default: false (images ride along verbatim, current behavior).
|
|
45
|
+
* When true, every message older than `stripImagesKeepRecent` has its image
|
|
46
|
+
* parts dropped from the outbound provider body (image-only messages
|
|
47
|
+
* collapse to a "[image]" text placeholder). Host-side policy only — the
|
|
48
|
+
* strip primitive lives in acp-kernel's wire layer. */
|
|
49
|
+
stripImages?: boolean;
|
|
50
|
+
/** How many of the MOST RECENT messages keep their image payloads when
|
|
51
|
+
* `stripImages` is enabled. Default: 5. Ignored when stripImages is off. */
|
|
52
|
+
stripImagesKeepRecent?: number;
|
|
53
|
+
}
|
|
54
|
+
/** Per-provider compression overrides. Carries the same tuning fields as the
|
|
55
|
+
* global level, plus an optional per-model map keyed by model id. */
|
|
56
|
+
export interface ProviderCompress extends CompressSettings {
|
|
57
|
+
/** Per-model overrides within this provider, keyed by model id
|
|
58
|
+
* (e.g. "claude-sonnet-4-5"). */
|
|
59
|
+
models?: Record<string, CompressSettings>;
|
|
60
|
+
}
|
|
61
|
+
/** Compression tuning. Top-level fields are global defaults; `providers`
|
|
62
|
+
* optionally narrows them per Pi provider name and per model id. The active
|
|
63
|
+
* entry is resolved live each turn from the current model
|
|
64
|
+
* (`ctx.model.provider` / `ctx.model.id`). */
|
|
65
|
+
export interface CompressConfig extends CompressSettings {
|
|
66
|
+
/** Per-provider (and per-model) overrides, keyed by Pi provider name
|
|
67
|
+
* (e.g. "anthropic", "openai", "zhipu") — the same name used in
|
|
68
|
+
* models.json and `pi --provider`. */
|
|
69
|
+
providers?: Record<string, ProviderCompress>;
|
|
70
|
+
}
|
|
71
|
+
/** Generic tool-call repetition guard. Detects the same tool being called
|
|
72
|
+
* repeatedly with byte-identical arguments — a sequence-level attractor that
|
|
73
|
+
* token-level penalties cannot break (greedy small models loop on e.g.
|
|
74
|
+
* acp_status or bash polls forever, each round adding protected tokens that
|
|
75
|
+
* the compression protection band then refuses to reclaim). Consecutive
|
|
76
|
+
* identical calls accumulate per session: at `warn` a strong warning is
|
|
77
|
+
* appended to that call's toolResult; at `abort` the call is refused (blocked,
|
|
78
|
+
* error toolResult) and the turn aborted so the attractor breaks. Any change
|
|
79
|
+
* to the arguments (or a switch to a different tool) resets the counter.
|
|
80
|
+
* Accepts a boolean shorthand (`false` disables) or an object. Default:
|
|
81
|
+
* enabled, warn=3, abort=5. See issue #308. */
|
|
82
|
+
export interface RepetitionGuardConfig {
|
|
83
|
+
/** Enable/disable the guard. Default: true. */
|
|
84
|
+
enabled?: boolean;
|
|
85
|
+
/** Consecutive identical calls before a strong warning is appended to the
|
|
86
|
+
* matching toolResult. Default: 3. */
|
|
87
|
+
warn?: number;
|
|
88
|
+
/** Consecutive identical calls before the call is refused and the turn
|
|
89
|
+
* aborted. Default: 5. Must be greater than `warn`; clamped up
|
|
90
|
+
* automatically if not. */
|
|
91
|
+
abort?: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Adapter configuration. Maps onto acp-kernel's `Config` plus Pi-specific knobs
|
|
95
|
+
* (live model context window, protected tools, state persistence).
|
|
96
|
+
*/
|
|
97
|
+
export interface AdapterConfig {
|
|
98
|
+
/** Master switch. Default: true. Set `enabled: false` in acp.json (or
|
|
99
|
+
* programmatically) to turn the whole adapter off — no tools, no system
|
|
100
|
+
* prompt, no context transform — for models too small to handle ACP, where
|
|
101
|
+
* Pi's native context management should run instead. Checked at extension
|
|
102
|
+
* load; requires a Pi restart to take effect. */
|
|
103
|
+
enabled?: boolean;
|
|
104
|
+
/** When omitted, the adapter reads `ctx.model.contextWindow` live each turn.
|
|
105
|
+
* Set explicitly for tests/headless runs. */
|
|
106
|
+
modelContextLimit?: number;
|
|
107
|
+
/** Tool-name patterns (glob suffix allowed) whose EVERY call+result pair is
|
|
108
|
+
* hard-excluded from compression — matching refs render as BLOCKED in every
|
|
109
|
+
* view. Default: none. Intended for low-frequency high-value tools whose
|
|
110
|
+
* outputs are independent content (e.g. skill loads); do NOT use for chatty
|
|
111
|
+
* tools — protecting every instance grows context unboundedly (#639
|
|
112
|
+
* rationale). Settable via acp.json since #499. */
|
|
113
|
+
protectedTools?: string[];
|
|
114
|
+
/** Tool-name patterns (glob suffix allowed) whose LATEST call+result pair is
|
|
115
|
+
* hard-excluded from compression; older pairs remain compressible. Default:
|
|
116
|
+
* none. Intended for cumulative-snapshot tools where each call supersedes
|
|
117
|
+
* the last. Settable via acp.json since #499. */
|
|
118
|
+
protectedLatestTools?: string[];
|
|
119
|
+
/** Tool-name patterns (glob suffix allowed) EXCLUDED from the soft-protected
|
|
120
|
+
* recent zone: matching tool results inside the recent window fold
|
|
121
|
+
* immediately instead of aging out first (kernel >= 0.0.92). Default:
|
|
122
|
+
* unset → kernel built-in ["decompress", "search_context", "read",
|
|
123
|
+
* "bash"]. The built-in keeps read/bash compressible — the largest
|
|
124
|
+
* reclaimable mass — but that also folds freshly-read files in batch-read
|
|
125
|
+
* workflows (#1198-style fold→re-read loop, bili #1277). Recommended
|
|
126
|
+
* remedy: ["decompress", "search_context", "bash"] (remove only read).
|
|
127
|
+
* ⚠ Unlike the two protection keys, an EMPTY ARRAY IS VALID — it excludes
|
|
128
|
+
* nothing and gives every tool recent-zone protection (escape hatch); do
|
|
129
|
+
* not re-include decompress/search_context or just-restored blocks get
|
|
130
|
+
* pinned in the recent zone and become unreclaimable. */
|
|
131
|
+
neverPreserveRecentTools?: string[];
|
|
132
|
+
/** The positive counterpart of neverPreserveRecentTools: tool-name
|
|
133
|
+
* patterns REMOVED from the effective recent-zone exclusion list (kernel
|
|
134
|
+
* >= 0.0.93) — `(neverPreserveRecentTools ?? built-in) minus
|
|
135
|
+
* preserveRecentTools`. The one-entry #1198/#1277 batch-read fold→re-read
|
|
136
|
+
* remedy: ["read"] protects fresh read results without restating (or
|
|
137
|
+
* freezing a stale copy of) the built-in list. Default: unset → no
|
|
138
|
+
* subtraction. Unlike neverPreserveRecentTools an EMPTY ARRAY IS
|
|
139
|
+
* INVALID here (pure no-op — use neverPreserveRecentTools: [] for
|
|
140
|
+
* protect-everything instead). */
|
|
141
|
+
preserveRecentTools?: string[];
|
|
142
|
+
preserveRecentMessages?: number;
|
|
143
|
+
/** Enable debug-level events in the ACP log file (default ~/.pi/acp.log).
|
|
144
|
+
* Always-on events (session/turn/compress lifecycle, all errors and
|
|
145
|
+
* warnings) are written regardless; `debug` only adds verbose diagnostics.
|
|
146
|
+
* Default: false (or env ACP_DEBUG=1/true). */
|
|
147
|
+
debug?: boolean;
|
|
148
|
+
/** Default timeout in seconds injected into the bash tool when the model
|
|
149
|
+
* omits `timeout`. Pi has NO built-in default, so without this a command
|
|
150
|
+
* that the model forgets to time out can hang for thousands of seconds.
|
|
151
|
+
* Default: 60 (catches hangs quickly). On timeout the model is guided to
|
|
152
|
+
* re-run with a larger `timeout`. Set to 0 to disable (restore Pi's
|
|
153
|
+
* unbounded behavior). */
|
|
154
|
+
toolBashDefaultTimeout?: number;
|
|
155
|
+
/** Hard byte cap applied to tool result text via the `tool_result` hook.
|
|
156
|
+
* Default: 50000 (~50KB) — aligned with Pi's own bash/read/grep cap so
|
|
157
|
+
* every tool path lands under one ceiling; the net still catches runaway
|
|
158
|
+
* output from tools Pi does not cap (MCP/custom). Set higher for large
|
|
159
|
+
* MCP outputs, lower (e.g. 8192) for a tighter context budget, or 0 to
|
|
160
|
+
* disable. When capped, oversized text is head-truncated with a notice
|
|
161
|
+
* telling the model how to see the full output (bash: read
|
|
162
|
+
* BashToolDetails.fullOutputPath). */
|
|
163
|
+
toolOutputMaxBytes?: number;
|
|
164
|
+
/** Compression tuning. */
|
|
165
|
+
compress?: CompressConfig;
|
|
166
|
+
/** Provider token-throttle (Bedrock "Too many tokens, please wait before
|
|
167
|
+
* trying again.") auto-retry. Accepts a boolean shorthand (`false`
|
|
168
|
+
* disables) or a ThrottleRetryConfig object. Default: enabled, 10 retries,
|
|
169
|
+
* 60s exponential base capped at 300s per kick. */
|
|
170
|
+
throttleRetry?: boolean | ThrottleRetryConfig;
|
|
171
|
+
/** Lean: idle compaction check (src/idle-compact.ts). After `idleMinutes`
|
|
172
|
+
* (default 29) of idle time, one turn shows the main model the compress
|
|
173
|
+
* nudge so it can compress while the prefix is still cached.
|
|
174
|
+
* `true` or `{ enabled: true, idleMinutes: 29 }`. Default: off. */
|
|
175
|
+
idleCompact?: boolean | IdleCompactConfig;
|
|
176
|
+
/** Cap on the output-headroom reservation as a fraction of the context
|
|
177
|
+
* window: reserved = min(model.maxTokens, pct * window). Accepts a ratio
|
|
178
|
+
* (0.25) or percent string ("25%"). Default: 0.25. Set 0 to disable the
|
|
179
|
+
* reservation entirely; >= 1 restores the legacy full-capability
|
|
180
|
+
* reservation (issue #207). */
|
|
181
|
+
outputHeadroomMaxPct?: number | string;
|
|
182
|
+
/** Generic tool-call repetition guard (see RepetitionGuardConfig). Accepts a
|
|
183
|
+
* boolean shorthand (`false` disables) or an object. Default: enabled,
|
|
184
|
+
* warn=3, abort=5. Stops greedy small models looping on byte-identical
|
|
185
|
+
* tool calls (issue #308). */
|
|
186
|
+
repetitionGuard?: boolean | RepetitionGuardConfig;
|
|
187
|
+
/** Character-level degenerate-repeat guard (see DegenerationGuardConfig).
|
|
188
|
+
* Collapses long single-codepoint runs (e.g. 4655×「【」) in assistant
|
|
189
|
+
* text/thinking of the outgoing view and injects a one-shot recovery notice
|
|
190
|
+
* after a degenerated turn — breaking the abort loop where pi replays the
|
|
191
|
+
* degenerated thinking back to the provider on every request (issue #351).
|
|
192
|
+
* Distinct from `repetitionGuard`, which is tool-call level. Accepts a
|
|
193
|
+
* boolean shorthand (`false` disables) or an object. Default: enabled,
|
|
194
|
+
* minRun=200. */
|
|
195
|
+
degenerationGuard?: boolean | DegenerationGuardConfig;
|
|
196
|
+
/** Host multi-session turn-boundary policy (#364). Accepts a boolean
|
|
197
|
+
* shorthand (`true` → count host-injected custom_message entries as turn
|
|
198
|
+
* boundaries) or a HostSessionConfig object. Default: off — pi-native
|
|
199
|
+
* behavior where only genuine user-role messages start a turn, so existing
|
|
200
|
+
* single-session users' nudge cadence is unchanged. Enable for inline
|
|
201
|
+
* multi-session hosts (Prime RLM & co.) whose injected agent messages must
|
|
202
|
+
* delimit real turns. See docs/host-adapter.md. */
|
|
203
|
+
hostSession?: boolean | HostSessionConfig;
|
|
204
|
+
/** Persistent-rules feature gate (#526): opt-in `rules: true` in acp.json.
|
|
205
|
+
* Gates the human `/acp-rule` command (list + record + remove + clear — kernel
|
|
206
|
+
* `listRules`/`addRule`/`removeRule`/`clearRules` against the session `.acp.json` sidecar state) and
|
|
207
|
+
* the model-side `acp_rule` tool (#490). Custom limits via
|
|
208
|
+
* coreOverrides.rules ({ maxRules?, maxRuleChars? }). */
|
|
209
|
+
rules?: boolean;
|
|
210
|
+
/** Override acp-kernel's load-bearing compression prompt rules (the 4
|
|
211
|
+
* Prompts fields). Each set field replaces the kernel default verbatim.
|
|
212
|
+
* Requires acknowledgePromptsRisk: true — without it, overrides are dropped
|
|
213
|
+
* (defaults used) and a warning is logged. Set via ~/.pi/acp.json. */
|
|
214
|
+
prompts?: Partial<Prompts>;
|
|
215
|
+
/** Must be true for `prompts` overrides to take effect. Acknowledges that
|
|
216
|
+
* replacing the kernel's tuned compression rules may reduce summary quality
|
|
217
|
+
* (lost paths/signatures/decisions → worse retrieval). */
|
|
218
|
+
acknowledgePromptsRisk?: boolean;
|
|
219
|
+
/** Override structural sections of the ACP system prompt (ACP TAGS, TOOLS,
|
|
220
|
+
* WHEN TO COMPRESS, ...). Tri-state per section: string = replace, null =
|
|
221
|
+
* remove, omitted = default. Not risk-gated — these are documentation
|
|
222
|
+
* sections, not compression rules. Set via acp.json. */
|
|
223
|
+
promptSections?: Partial<PiPromptSections>;
|
|
224
|
+
/** Override guidance-class nudge texts (efficiencyNote, emergencyHeader,
|
|
225
|
+
* t2Guidance, t3Guidance). Same tri-state semantics. Not risk-gated. */
|
|
226
|
+
nudgeSections?: NudgeSectionsConfig;
|
|
227
|
+
/** Override the four ACP tool definitions' LLM-facing text (description,
|
|
228
|
+
* paramDescriptions, promptSnippet, promptGuidelines). Read synchronously
|
|
229
|
+
* at extension load — tool defs are frozen at registration time. */
|
|
230
|
+
toolPrompts?: ToolPromptsConfig;
|
|
231
|
+
/** Dedicated summary model (#241 port). `session` reuses the active model
|
|
232
|
+
* and the captured prompt prefix; `provider/id` or a bare id resolves via
|
|
233
|
+
* Pi's model registry. Omitted = the main model writes summaries. Managed
|
|
234
|
+
* by `/acp compact`. */
|
|
235
|
+
compressionModelId?: string;
|
|
236
|
+
coreOverrides?: Partial<Config>;
|
|
237
|
+
/** Host-only (never read from acp.json): the embedding extension replaces
|
|
238
|
+
* the ACP system prompt with its own text. The before_agent_start handler
|
|
239
|
+
* still runs its side effects (pack stamp, summary rules), and config keys
|
|
240
|
+
* that only shape the replaced prompt (promptSections) are
|
|
241
|
+
* reported once as ignored instead of silently dropped. */
|
|
242
|
+
externalSystemPrompt?: boolean;
|
|
243
|
+
}
|
|
244
|
+
export declare const DEFAULT_TOOL_BASH_TIMEOUT = 60;
|
|
245
|
+
export declare const DEFAULT_TOOL_OUTPUT_MAX_BYTES = 50000;
|
|
246
|
+
/** Defaults for the generic tool-call repetition guard (issue #308). */
|
|
247
|
+
export declare const REPETITION_GUARD_DEFAULTS: {
|
|
248
|
+
readonly warn: 3;
|
|
249
|
+
readonly abort: 5;
|
|
250
|
+
};
|
|
251
|
+
/** Resolve the repetition-guard configuration from the adapter, handling the
|
|
252
|
+
* boolean shorthand (`false` disables) and clamping `abort` to stay strictly
|
|
253
|
+
* above `warn` (a guard that aborts at or before its warn threshold would be
|
|
254
|
+
* meaningless). Non-numeric / out-of-range values fall back to defaults. */
|
|
255
|
+
export declare function resolveRepetitionGuard(adapter: AdapterConfig): {
|
|
256
|
+
enabled: boolean;
|
|
257
|
+
warn: number;
|
|
258
|
+
abort: number;
|
|
259
|
+
};
|
|
260
|
+
/** Host multi-session turn-boundary policy (#364). See TurnBoundaryPolicy in
|
|
261
|
+
* src/turn-boundary.ts for the semantics this resolves. */
|
|
262
|
+
export interface HostSessionConfig {
|
|
263
|
+
/** Count host-injected custom_message entries (agent_message) as turn
|
|
264
|
+
* boundaries. Default: false (pi-native behavior). */
|
|
265
|
+
countCustomMessages?: boolean;
|
|
266
|
+
/** #578: optional customType allowlist refining countCustomMessages — when
|
|
267
|
+
* set (with countCustomMessages:true), only injected entries whose
|
|
268
|
+
* customType is listed start a turn, keeping metadata injections
|
|
269
|
+
* (harness_digest, ipython_state) out of nudge/retry accounting. Ignored
|
|
270
|
+
* unless countCustomMessages:true; malformed values warn and fall back to
|
|
271
|
+
* "all injected types count". */
|
|
272
|
+
customMessageTypes?: string[];
|
|
273
|
+
}
|
|
274
|
+
export interface ResolvedHostSession {
|
|
275
|
+
countCustomMessages: boolean;
|
|
276
|
+
/** Present only when countCustomMessages is on and a valid allowlist was
|
|
277
|
+
* configured (#578). */
|
|
278
|
+
customMessageTypes?: readonly string[];
|
|
279
|
+
}
|
|
280
|
+
/** Resolve the host-session turn-boundary policy from the adapter, handling
|
|
281
|
+
* the boolean shorthand (`true` enables countCustomMessages). Invalid values
|
|
282
|
+
* fall back to the pi-native default (off) with a logged warning — they never
|
|
283
|
+
* fail the session. */
|
|
284
|
+
export declare function resolveHostSession(adapter: AdapterConfig): ResolvedHostSession;
|
|
285
|
+
/** Per-field deepest-wins merge of the three compression levels (global →
|
|
286
|
+
* provider → model). An undefined field at a deeper level does NOT clear a
|
|
287
|
+
* value set at a shallower level — only a defined value overrides. */
|
|
288
|
+
export declare function mergeCompress(global?: CompressSettings, provider?: CompressSettings, model?: CompressSettings): CompressSettings;
|
|
289
|
+
/** Resolve the effective compression settings for the active model: global →
|
|
290
|
+
* provider (matched by Pi provider name) → model (matched by model id).
|
|
291
|
+
* Returns a CompressSettings whose fields are undefined when nothing is set
|
|
292
|
+
* at any level. The Pi adapter keys providers by name (ctx.model.provider),
|
|
293
|
+
* not URL — it never sees the upstream URL the way the proxy does. */
|
|
294
|
+
export declare function resolveCompress(compress: CompressConfig | undefined, provider: string | undefined, modelId: string | undefined): CompressSettings;
|
|
295
|
+
export declare function resolveConfig(adapter: AdapterConfig, liveContextLimit: number, provider?: string, modelId?: string): Config;
|
|
296
|
+
export declare function parsePercent(v: number | string): number;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Machine-readable contract for the `<sessionFile>.acp.json` sidecar (#368).
|
|
3
|
+
*
|
|
4
|
+
* Downstream tools (e.g. pi-billion-memory) glob the sidecar directly instead
|
|
5
|
+
* of running inside pi. To keep that stable across releases:
|
|
6
|
+
*
|
|
7
|
+
* - `schemaVersion` is written on every save. A MISSING version means v1
|
|
8
|
+
* (pre-contract files). An unknown NEWER version means "fields may have
|
|
9
|
+
* changed" — skip it, log once, never rewrite the file.
|
|
10
|
+
* - `producer` identifies the writer (`billion-context-pi@x.y.z`).
|
|
11
|
+
* - The file is replaced atomically (tmp + rename), so a reader either sees
|
|
12
|
+
* the previous or the next complete file, never a torn write.
|
|
13
|
+
* - Blocks are stored verbatim as the kernel's CompressionBlock; unknown
|
|
14
|
+
* extra fields are additive and must be ignored by readers.
|
|
15
|
+
*/
|
|
16
|
+
export declare const SIDECAR_SCHEMA_VERSION = 1;
|
|
17
|
+
/**
|
|
18
|
+
* One compressed block, as persisted in the sidecar's `blocks[]`. Mirrors the
|
|
19
|
+
* kernel's CompressionBlock (acp-kernel 0.0.81) — inlined rather than aliased
|
|
20
|
+
* so this subpath carries no type dependency on acp-kernel (dev-only, bundled
|
|
21
|
+
* at build time). Fields the kernel adds later are additive; tests assert the
|
|
22
|
+
* kernel shape still covers this one.
|
|
23
|
+
*/
|
|
24
|
+
export interface BcpBlockV1 {
|
|
25
|
+
blockId: string;
|
|
26
|
+
runId: string;
|
|
27
|
+
tier: 1 | 2 | 3;
|
|
28
|
+
topic?: string;
|
|
29
|
+
summary: string;
|
|
30
|
+
directMessageIds: string[];
|
|
31
|
+
effectiveMessageIds: string[];
|
|
32
|
+
directBlockIds: string[];
|
|
33
|
+
compressedTokens: number;
|
|
34
|
+
createdAt: number;
|
|
35
|
+
survivedCount: number;
|
|
36
|
+
generation: "young" | "old";
|
|
37
|
+
active: boolean;
|
|
38
|
+
expanded?: boolean;
|
|
39
|
+
durationMs?: number;
|
|
40
|
+
compressCallId?: string;
|
|
41
|
+
startRef?: string;
|
|
42
|
+
endRef?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Top-level shape of the sidecar (state fields beyond the contract allowed). */
|
|
45
|
+
export interface BcpSidecarV1 {
|
|
46
|
+
schemaVersion: number;
|
|
47
|
+
producer: string;
|
|
48
|
+
blocks: BcpBlockV1[];
|
|
49
|
+
[extra: string]: unknown;
|
|
50
|
+
}
|
|
51
|
+
export declare function sidecarProducer(): string;
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { AcpRuntime } from "./runtime.js";
|
|
4
|
+
import { type ToolPromptOverrides } from "./surface.js";
|
|
5
|
+
declare const DecompressParams: Type.TObject<{
|
|
6
|
+
blockId: Type.TString;
|
|
7
|
+
full: Type.TOptional<Type.TBoolean>;
|
|
8
|
+
toFile: Type.TOptional<Type.TString>;
|
|
9
|
+
inline: Type.TOptional<Type.TBoolean>;
|
|
10
|
+
}>;
|
|
11
|
+
export declare function makeDecompressTool(runtime: AcpRuntime, overrides?: ToolPromptOverrides): ToolDefinition<typeof DecompressParams>;
|
|
12
|
+
/** Generate a unique auto file path for a block. Uses a timestamp so repeated
|
|
13
|
+
* decompressions of the same block never overwrite each other. */
|
|
14
|
+
/** Lean: filename stem for a message ref. Raw entry refs can carry
|
|
15
|
+
* characters Windows rejects in filenames (e.g. `4d9e965e#toolu_x|fc_toolu_x`);
|
|
16
|
+
* keep safe refs readable and give unsafe ones a sanitized prefix plus a
|
|
17
|
+
* short hash so distinct refs never collide. */
|
|
18
|
+
export declare function messageFileStem(ref: string): string;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { SessionMessageEntry } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
type AgentMessage = SessionMessageEntry["message"];
|
|
3
|
+
/** [#351] Character-level degenerate-repeat guard. Models occasionally
|
|
4
|
+
* degenerate into long single-codepoint runs (observed in the wild: a
|
|
5
|
+
* 5968-char thinking block ending in 4655 consecutive 「【」, escalating over
|
|
6
|
+
* turns until the turn aborts). This is distinct from `repetitionGuard`
|
|
7
|
+
* (issue #308), which breaks byte-identical TOOL-CALL loops; here the
|
|
8
|
+
* attractor is inside the generated text/thinking itself.
|
|
9
|
+
*
|
|
10
|
+
* Why the adapter must act: pi replays prior assistant thinking back to the
|
|
11
|
+
* provider on every subsequent request (openai-completions sends it as
|
|
12
|
+
* `reasoning_content`, or as plain text when `requiresThinkingAsText`), and
|
|
13
|
+
* an aborted turn's partial message persists in the session log. A
|
|
14
|
+
* degenerated tail therefore rides along every later prompt, where the model
|
|
15
|
+
* sees its own previous output ending in thousands of repeated characters —
|
|
16
|
+
* a continuation bias that re-triggers the same degeneration, aborting the
|
|
17
|
+
* next turn too. The session dies in an abort loop with no recovery path.
|
|
18
|
+
*
|
|
19
|
+
* The pass below collapses runs >= minRun in assistant text/thinking of the
|
|
20
|
+
* OUTGOING view (persisted history is never modified) and appends a one-shot
|
|
21
|
+
* recovery notice while the degenerated message is the most recent assistant
|
|
22
|
+
* turn. Position-based self-limiting: a fresh model turn makes the old
|
|
23
|
+
* message non-last, so the notice stops appearing without any persistent
|
|
24
|
+
* state (and cannot accumulate — #223 lesson). */
|
|
25
|
+
export interface DegenerationGuardConfig {
|
|
26
|
+
/** Master switch. Default: true. `false` disables the pass entirely
|
|
27
|
+
* (kill-switch). */
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
/** Minimum length of a single-codepoint run (counted in codepoints) before
|
|
30
|
+
* it is treated as degeneration. Legitimate single-char runs in coding
|
|
31
|
+
* sessions (markdown hrules, dotted leaders, comment banners) stay well
|
|
32
|
+
* under this; observed pre-degeneration drift maxed at ~60 before the
|
|
33
|
+
* catastrophic 4655×「【」 run. Default: 200. Values below 8 are raised to
|
|
34
|
+
* 8: the collapse marker must stay shorter than any detectable run, and a
|
|
35
|
+
* sub-8 threshold would start matching ordinary dotted leaders/hrules. */
|
|
36
|
+
minRun?: number;
|
|
37
|
+
}
|
|
38
|
+
export declare const DEFAULT_DEGENERATION_GUARD: Required<DegenerationGuardConfig>;
|
|
39
|
+
/** Resolve the guard config, handling the boolean shorthand (`false`
|
|
40
|
+
* disables). Invalid minRun values (< 2 or non-numeric) fall back to the
|
|
41
|
+
* default with a logged warning — they never fail the session. Valid values
|
|
42
|
+
* below MIN_VALID_MIN_RUN are raised to it. */
|
|
43
|
+
export declare function resolveDegenerationGuard(cfg?: boolean | DegenerationGuardConfig): Required<DegenerationGuardConfig>;
|
|
44
|
+
/** One maximal run of a repeated codepoint. */
|
|
45
|
+
export interface DegenerateRun {
|
|
46
|
+
/** The repeated codepoint (string of length 1 or 2 — surrogate pairs kept whole). */
|
|
47
|
+
char: string;
|
|
48
|
+
/** Run length in codepoints. */
|
|
49
|
+
count: number;
|
|
50
|
+
/** UTF-16 index of the run start in the original string. */
|
|
51
|
+
index: number;
|
|
52
|
+
}
|
|
53
|
+
/** Find maximal runs of a single repeated codepoint with length >= minRun.
|
|
54
|
+
* Codepoint-safe: surrogate pairs count as one unit, so a run of astral
|
|
55
|
+
* characters is detected like any other. Returns [] for empty input or
|
|
56
|
+
* minRun < 2 (a "run" shorter than 2 carries no signal). */
|
|
57
|
+
export declare function findDegenerateRuns(text: string, minRun: number): DegenerateRun[];
|
|
58
|
+
/** Collapse every degenerate run in `text` into a short marker that names the
|
|
59
|
+
* character and its original count. Pure, idempotent, fail-safe. Returns the
|
|
60
|
+
* input unchanged when there is nothing to collapse. */
|
|
61
|
+
export declare function collapseDegenerateRuns(text: string, minRun: number): string;
|
|
62
|
+
/** Evidence of one rewritten message: which blocks changed and what was collapsed. */
|
|
63
|
+
export interface CollapseEvidence {
|
|
64
|
+
/** Index of the rewritten message in the input array. */
|
|
65
|
+
msgIndex: number;
|
|
66
|
+
/** Block kinds rewritten ("content" for string content, else "text"/"thinking"). */
|
|
67
|
+
blocks: string[];
|
|
68
|
+
/** Runs collapsed across those blocks. */
|
|
69
|
+
runs: DegenerateRun[];
|
|
70
|
+
}
|
|
71
|
+
/** Request-time pass: collapse degenerate single-codepoint runs in ASSISTANT
|
|
72
|
+
* messages' text/thinking blocks (string content included). Every position is
|
|
73
|
+
* scanned — including the current turn's partial assistant message, which is
|
|
74
|
+
* exactly the one pi will replay back to the provider on the next request.
|
|
75
|
+
* Tool-call arguments are never touched (rewriting them would desync the
|
|
76
|
+
* model's view from the call that actually executed). Pure: returns the same
|
|
77
|
+
* array reference when nothing changed; idempotent; fail-safe (any error
|
|
78
|
+
* returns the input unchanged). Persisted history is never modified. */
|
|
79
|
+
export declare function collapseAssistantDegeneration(messages: AgentMessage[], cfg?: boolean | DegenerationGuardConfig): {
|
|
80
|
+
messages: AgentMessage[];
|
|
81
|
+
evidence: CollapseEvidence[];
|
|
82
|
+
};
|
|
83
|
+
/** Scan backward for the LAST assistant message and collect its degenerate
|
|
84
|
+
* runs. Call it with the PERSISTED originals in session order (not the
|
|
85
|
+
* outgoing view): thinking-only aborted turns never reach the outgoing view
|
|
86
|
+
* (projectMessage drops them), yet they are still "the previous turn".
|
|
87
|
+
* Returns null when absent or clean. Gates the recovery notice: because the
|
|
88
|
+
* notice fires only while the degenerated message is the most recent
|
|
89
|
+
* assistant turn, it self-limits — once the model produces a new turn the
|
|
90
|
+
* old message is no longer last and the notice disappears on the next
|
|
91
|
+
* rebuild (no persistent state needed). */
|
|
92
|
+
export declare function lastAssistantRuns(messages: AgentMessage[], minRun: number): DegenerateRun[] | null;
|
|
93
|
+
/** One-shot recovery notice appended while a degenerated assistant message is
|
|
94
|
+
* the most recent turn: tells the model the repeated segment carries no
|
|
95
|
+
* information and was truncated, and to resume from the last valid step
|
|
96
|
+
* instead of continuing the attractor. */
|
|
97
|
+
export declare function degenerationNotice(runs: DegenerateRun[]): AgentMessage;
|
|
98
|
+
export {};
|
package/dist/export.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface ExportOptions {
|
|
2
|
+
output?: string;
|
|
3
|
+
full?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface SessionSummary {
|
|
6
|
+
id: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
savedAt?: number;
|
|
10
|
+
contextTokens?: number;
|
|
11
|
+
blocks: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function listSessions(sessionDir: string): Promise<SessionSummary[]>;
|
|
14
|
+
export declare function exportSession(selector: string | undefined, opts: ExportOptions, sessionDir: string): Promise<string>;
|
|
15
|
+
export declare function parseExportArgs(args: string): {
|
|
16
|
+
selector?: string;
|
|
17
|
+
full: boolean;
|
|
18
|
+
output?: string;
|
|
19
|
+
error?: string;
|
|
20
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { CompressionBlock } from "acp-kernel";
|
|
2
|
+
type UsageLike = {
|
|
3
|
+
totalTokens?: number;
|
|
4
|
+
input?: number;
|
|
5
|
+
output?: number;
|
|
6
|
+
cacheRead?: number;
|
|
7
|
+
cacheWrite?: number;
|
|
8
|
+
} | null | undefined;
|
|
9
|
+
type AnchorEntry = {
|
|
10
|
+
type: string;
|
|
11
|
+
message?: {
|
|
12
|
+
role?: string;
|
|
13
|
+
stopReason?: string;
|
|
14
|
+
usage?: UsageLike;
|
|
15
|
+
toolName?: string;
|
|
16
|
+
toolCallId?: string;
|
|
17
|
+
isError?: boolean;
|
|
18
|
+
content?: unknown;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
/** True when the last valid assistant usage anchor comes strictly BEFORE the
|
|
22
|
+
* last successful compress toolResult — the host's provider-usage number
|
|
23
|
+
* still reflects the pre-compression request. Failed/no-op compresses don't
|
|
24
|
+
* count (nothing was reclaimed). */
|
|
25
|
+
export declare function usageAnchorPredatesCompression(entries: AnchorEntry[]): boolean;
|
|
26
|
+
export interface AnchorStaleness {
|
|
27
|
+
predates: boolean;
|
|
28
|
+
netReclaimed: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function compressionAnchorStaleness(entries: AnchorEntry[], blocks: readonly CompressionBlock[], countTokens: (text: string) => number): AnchorStaleness;
|
|
31
|
+
export {};
|
package/dist/host.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
/** Which session-entry source API the host exposes (same precedence as readContextEntries). */
|
|
3
|
+
export type EntrySourceKind = "buildContextEntries" | "getBranch";
|
|
4
|
+
export declare function entrySourceOf(sm: ExtensionContext["sessionManager"]): EntrySourceKind | null;
|
|
5
|
+
/**
|
|
6
|
+
* Whether the host declared itself a Pi-compatible fork via environment.
|
|
7
|
+
*
|
|
8
|
+
* The SessionManager shape alone cannot distinguish e.g. Prime from OMP — both are
|
|
9
|
+
* Pi forks exposing `getBranch()` but not `buildContextEntries()` — so an
|
|
10
|
+
* unsupported shape requires an explicit declaration before the adapter runs.
|
|
11
|
+
* Read at call time so hosts can set it per-launch. See docs/host-adapter.md.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isDeclaredForkHost(): boolean;
|
|
14
|
+
/** Unsupported host: not Pi-shaped and not declared as a fork. OMP stays blocked by default. */
|
|
15
|
+
export declare function isUnsupportedHost(sm: ExtensionContext["sessionManager"]): boolean;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { NudgeDecision } from "acp-kernel";
|
|
3
|
+
import type { AcpRuntime } from "./runtime.js";
|
|
4
|
+
/**
|
|
5
|
+
* Idle compaction check (Lean). After the session has been idle for
|
|
6
|
+
* `idleMinutes` (default 29, just inside a 30-minute prompt cache), start one
|
|
7
|
+
* turn that shows the main model the regular compress nudge and lets it decide
|
|
8
|
+
* whether to compress. If the cache is still warm, that turn reads the long
|
|
9
|
+
* prefix from cache, and the request the user sends on return carries the
|
|
10
|
+
* shorter context instead of rewriting the long one uncached.
|
|
11
|
+
*
|
|
12
|
+
* Timing follows pi-keepwarm: the anchor is the start of the last provider
|
|
13
|
+
* request of a run. Any run counts (user input, sub-agent wake, throttle kick)
|
|
14
|
+
* except the idle check itself, so one idle period gets at most one check.
|
|
15
|
+
*/
|
|
16
|
+
export declare const IDLE_CHECK_CUSTOM_TYPE = "acp-idle-check";
|
|
17
|
+
export declare const DEFAULT_IDLE_MINUTES = 29;
|
|
18
|
+
/** Timer woke this much later than planned (machine slept): the cache is likely cold, skip. */
|
|
19
|
+
export declare const IDLE_LATE_MS = 60000;
|
|
20
|
+
export interface IdleCompactConfig {
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
/** Minutes of idle time before the check. Default 29. */
|
|
23
|
+
idleMinutes?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface ResolvedIdleCompact {
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
idleMinutes: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function resolveIdleCompact(cfg: boolean | IdleCompactConfig | undefined): ResolvedIdleCompact;
|
|
30
|
+
export declare function idleCheckText(awayMinutes: number): string;
|
|
31
|
+
export interface IdleCompactHandle {
|
|
32
|
+
/** Record the last context event's compressible mass (context hook, every event). */
|
|
33
|
+
noteNudge(sid: string, nudge: NudgeDecision | undefined): void;
|
|
34
|
+
/** True once per idle check: the context hook appends the nudge even below its usual thresholds. */
|
|
35
|
+
takeForcedNudge(sid: string): boolean;
|
|
36
|
+
/** Re-plan the pending timer after the config changed (settings panel). */
|
|
37
|
+
rearm(): void;
|
|
38
|
+
}
|
|
39
|
+
export declare function wireIdleCompact(pi: ExtensionAPI, runtime: AcpRuntime): IdleCompactHandle;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ExtensionFactory } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { NudgeDecision } from "acp-kernel";
|
|
3
|
+
import { type AdapterConfig } from "./config.js";
|
|
4
|
+
export { createRuntime } from "./runtime.js";
|
|
5
|
+
export type { AcpRuntime, SessionRef } from "./runtime.js";
|
|
6
|
+
export { deriveChildState } from "./state.js";
|
|
7
|
+
export declare function createAcpExtension(adapter?: AdapterConfig): ExtensionFactory;
|
|
8
|
+
declare const _default: ExtensionFactory;
|
|
9
|
+
export default _default;
|
|
10
|
+
export declare function formatNudgeRecord(nudge: NudgeDecision, emergency: boolean): string;
|