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.
Files changed (60) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +141 -81
  3. package/dist/abort.d.ts +1 -0
  4. package/dist/branch-guard.d.ts +8 -0
  5. package/dist/cache-tool.d.ts +12 -0
  6. package/dist/chunk-PPYOZRUP.js +18 -0
  7. package/dist/chunk-PPYOZRUP.js.map +1 -0
  8. package/dist/commands.d.ts +8 -0
  9. package/dist/compat.d.ts +23 -0
  10. package/dist/compress-model.d.ts +65 -0
  11. package/dist/compress-tool.d.ts +76 -0
  12. package/dist/config-dir.d.ts +32 -0
  13. package/dist/config-panel.d.ts +61 -0
  14. package/dist/config.d.ts +296 -0
  15. package/dist/contract.d.ts +51 -0
  16. package/dist/contract.js +9 -0
  17. package/dist/contract.js.map +1 -0
  18. package/dist/decompress-tool.d.ts +19 -0
  19. package/dist/degeneration.d.ts +98 -0
  20. package/dist/export.d.ts +20 -0
  21. package/dist/floor-stale.d.ts +31 -0
  22. package/dist/host.d.ts +15 -0
  23. package/dist/idle-compact.d.ts +39 -0
  24. package/dist/index.d.ts +10 -0
  25. package/dist/index.js +23169 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/live-only-tail.d.ts +10 -0
  28. package/dist/log.d.ts +18 -0
  29. package/dist/messages.d.ts +85 -0
  30. package/dist/omp.d.ts +19 -0
  31. package/dist/overflow-selfheal.d.ts +132 -0
  32. package/dist/prompt-pack.d.ts +53 -0
  33. package/dist/proxy-detect.d.ts +21 -0
  34. package/dist/reasoning-drop.d.ts +68 -0
  35. package/dist/rule-tool.d.ts +8 -0
  36. package/dist/runtime.d.ts +199 -0
  37. package/dist/search-index.d.ts +20 -0
  38. package/dist/search-tool.d.ts +10 -0
  39. package/dist/sequence-match.d.ts +7 -0
  40. package/dist/session-log.d.ts +23 -0
  41. package/dist/state-rebuild.d.ts +30 -0
  42. package/dist/state.d.ts +44 -0
  43. package/dist/status-tool.d.ts +13 -0
  44. package/dist/strip-images.d.ts +18 -0
  45. package/dist/summary-sanitize.d.ts +8 -0
  46. package/dist/surface.d.ts +17 -0
  47. package/dist/system-passthrough.d.ts +4 -0
  48. package/dist/system-prompt.d.ts +20 -0
  49. package/dist/tag-tokens.d.ts +3 -0
  50. package/dist/throttle-retry.d.ts +52 -0
  51. package/dist/tokens.d.ts +61 -0
  52. package/dist/tool-guardrails.d.ts +33 -0
  53. package/dist/tool-pair-sanitizer.d.ts +8 -0
  54. package/dist/turn-boundary.d.ts +53 -0
  55. package/dist/user-config.d.ts +67 -0
  56. package/lean/index.ts +376 -0
  57. package/package.json +85 -36
  58. package/schema/bcp-block-v1.json +42 -0
  59. package/README.zh-CN.md +0 -79
  60. package/index.ts +0 -266
@@ -0,0 +1,10 @@
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 SearchParams: Type.TObject<{
6
+ query: Type.TString;
7
+ limit: Type.TOptional<Type.TNumber>;
8
+ }>;
9
+ export declare function makeSearchTool(runtime: AcpRuntime, overrides?: ToolPromptOverrides): ToolDefinition<typeof SearchParams>;
10
+ export {};
@@ -0,0 +1,7 @@
1
+ export interface MatchRange {
2
+ candidateStart: number;
3
+ liveStart: number;
4
+ length: number;
5
+ }
6
+ export declare function findUniqueLongestRun<Key>(candidates: readonly Key[], live: readonly Key[]): MatchRange | undefined;
7
+ export declare function findPositionalPrefixRun<Key>(candidates: readonly Key[], live: readonly Key[]): MatchRange | undefined;
@@ -0,0 +1,23 @@
1
+ import type { SessionEntry } from "@earendil-works/pi-coding-agent";
2
+ /** Parse a session jsonl into entries — mirrors pi's own loadEntriesFromFile
3
+ * (JSON.parse per line; blank and malformed lines skipped). */
4
+ export declare function parseSessionLog(text: string): SessionEntry[];
5
+ /** Read-only lookup of entries in ANCESTOR sessions (issue #531): a derived
6
+ * child session (Prime RLM inline) inherits blocks whose message ids exist
7
+ * only in the parent's session log, so decompress must fall back up the
8
+ * parentSession header chain. Walks upward from `sessionFile` (nearest
9
+ * ancestor first), cycle-safe and depth-capped like state inheritance, and
10
+ * returns entries whose base id is in `wantedBaseIds`. The starting session
11
+ * itself is never included; nearest-ancestor entries win on duplicate ids. */
12
+ export declare function loadAncestorEntries(sessionFile: string | undefined, wantedBaseIds: Set<string>): Promise<SessionEntry[]>;
13
+ /** Read-only content recovery for content-addressed `live-*` refs (issue
14
+ * #579): fork hosts alias not-yet-persisted tail messages with `live-*` ids
15
+ * (runtime.ts mergeLiveEntries); those aliases appear in NO jsonl, so the
16
+ * entry-id lookup above can never find them. The rawId→identity bridge lives
17
+ * in each session's OWN sidecar (liveRefOrigins). Walks the chain — itself
18
+ * first, then ancestors, same depth/cycle rules as loadAncestorEntries — and
19
+ * at each level pairs that level's declared origins with that same level's
20
+ * log entries matched by messageIdentity. First matching entry wins per rawId
21
+ * (identical identities are interchangeable: identity covers full normalized
22
+ * content). Never writes any sidecar. */
23
+ export declare function loadLiveRefEntries(sessionFile: string | undefined, wantedRawIds: Set<string>): Promise<Map<string, SessionEntry>>;
@@ -0,0 +1,30 @@
1
+ import type { CompressionCore, CompressionState } from "acp-kernel";
2
+ import type { SessionEntry } from "@earendil-works/pi-coding-agent";
3
+ type ApplyInput = Parameters<CompressionCore["applyCompression"]>[0];
4
+ export interface RebuildReport {
5
+ blocks: number;
6
+ callsApplied: number;
7
+ callsSkipped: number;
8
+ errors: string[];
9
+ }
10
+ export interface RebuildResult {
11
+ state: CompressionState;
12
+ report: RebuildReport;
13
+ }
14
+ /** Cheap scan: does this log contain at least one non-error compress toolResult? */
15
+ export declare function hasCompressHistory(entries: SessionEntry[]): boolean;
16
+ /**
17
+ * Replay successful compress calls from the session log against `state`
18
+ * (normally a fresh createInitialState()). Failed, errored, no-op and
19
+ * unparseable calls are skipped — applyCompression batches are atomic, so a
20
+ * rejected call leaves state untouched. `report.blocks` tells the caller
21
+ * whether anything was actually rebuilt (state is returned unchanged when
22
+ * nothing applied).
23
+ */
24
+ export declare function rebuildStateFromLog(input: {
25
+ entries: SessionEntry[];
26
+ state: CompressionState;
27
+ config: ApplyInput["config"];
28
+ core: CompressionCore;
29
+ }): RebuildResult;
30
+ export {};
@@ -0,0 +1,44 @@
1
+ import { type CompressionState } from "acp-kernel";
2
+ export declare const STATE_SUFFIX = ".acp.json";
3
+ export interface LiveRefOrigin {
4
+ rawId: string;
5
+ identity: string;
6
+ }
7
+ /** One-time marker persisted in a child sidecar when deriveChildState ran
8
+ * (#364): proves this session's state was explicitly derived from a parent,
9
+ * so later loads never re-derive. */
10
+ export interface DerivedFrom {
11
+ parentSessionId: string;
12
+ derivedAt: number;
13
+ }
14
+ export declare function readParentSessionPath(sessionFile: string): Promise<string | undefined>;
15
+ export declare class SessionStateStore {
16
+ private cache;
17
+ private writes;
18
+ private tmpSeq;
19
+ load(sessionFile: string | undefined, sessionId: string): Promise<CompressionState>;
20
+ /** Stamp the effective prompt pack for the live session (audit trail:
21
+ * persisted into the sidecar on the next save). */
22
+ setActivePack(sessionFile: string | undefined, sessionId: string, activePack: string): void;
23
+ getActivePack(sessionFile: string | undefined, sessionId: string): string | undefined;
24
+ save(state: CompressionState, sessionFile: string | undefined, sessionId: string): Promise<void>;
25
+ getLiveRefOrigins(sessionFile: string | undefined, sessionId: string): LiveRefOrigin[];
26
+ setLiveRefOrigins(sessionFile: string | undefined, sessionId: string, origins: LiveRefOrigin[]): void;
27
+ getDerivedFrom(sessionFile: string | undefined, sessionId: string): DerivedFrom | null;
28
+ setDerivedFrom(sessionFile: string | undefined, sessionId: string, mark: DerivedFrom | null): void;
29
+ invalidate(): void;
30
+ private tryLoadParentState;
31
+ }
32
+ /** #364: derive an INLINE child session's compression state from its parent's
33
+ * (same-process sub-sessions, e.g. Prime RLM). Inherits exactly what makes
34
+ * inherited blocks usable — blocks (deep-copied: they carry mutable fields),
35
+ * message refs, the per-message token snapshot, the id counters so new
36
+ * child blocks cannot collide with inherited ids, and the persistent
37
+ * acp_rule reminders (kernel cloneState precedent: never silently drop
38
+ * model-set rules) — and resets every rhythm ledger (nudge cadence
39
+ * baseline, stats counters, absorb records) so the
40
+ * child starts its own clock. Separate-process pi-native delegates must NOT
41
+ * use this: their session files carry a parentSession header that already
42
+ * inherits the parent state verbatim. */
43
+ export declare function deriveChildState(parent: CompressionState): CompressionState;
44
+ export declare function parseLiveRefOrigins(value: unknown): LiveRefOrigin[];
@@ -0,0 +1,13 @@
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 StatusParams: Type.TObject<{
6
+ scope: Type.TOptional<Type.TUnion<[Type.TLiteral<"compressed">, Type.TLiteral<"uncompressed">]>>;
7
+ view: Type.TOptional<Type.TUnion<[Type.TLiteral<"ranges">, Type.TLiteral<"messages">]>>;
8
+ tool: Type.TOptional<Type.TString>;
9
+ sort: Type.TOptional<Type.TUnion<[Type.TLiteral<"size">, Type.TLiteral<"time">, Type.TLiteral<"tool">, Type.TLiteral<"age">]>>;
10
+ limit: Type.TOptional<Type.TNumber>;
11
+ }>;
12
+ export declare function makeStatusTool(runtime: AcpRuntime, overrides?: ToolPromptOverrides): ToolDefinition<typeof StatusParams>;
13
+ export {};
@@ -0,0 +1,18 @@
1
+ import type { StripProtocol } from "acp-kernel/wire";
2
+ /** Map pi-ai's Api id to the kernel wire dialect. Only dialects the kernel
3
+ * strip primitive explicitly supports are mapped; everything else returns
4
+ * null (= no-op), so exotic or future APIs are never mangled. */
5
+ export declare function apiToStripProtocol(api: string | undefined): StripProtocol;
6
+ export interface StripImagesOutcome {
7
+ /** Replacement body to send, or undefined when the payload is unchanged. */
8
+ body?: unknown;
9
+ /** Number of image parts removed (0 = untouched). */
10
+ removed: number;
11
+ }
12
+ /** Strip historical images from a provider request body. Pure: returns
13
+ * { removed: 0 } (and NO body) when disabled, the protocol is unsupported,
14
+ * the body is not strip-shaped, or nothing is older than keepRecent. */
15
+ export declare function applyStripImages(body: unknown, api: string | undefined, settings: {
16
+ enabled: boolean;
17
+ keepRecent: number;
18
+ }): StripImagesOutcome;
@@ -0,0 +1,8 @@
1
+ export declare function countUnicodeEscapes(s: string): number;
2
+ export declare function decodeUnicodeEscapes(s: string): string;
3
+ export declare const UNESCAPE_THRESHOLD = 20;
4
+ export declare function sanitizeSummary(s: string): {
5
+ text: string;
6
+ unescaped: boolean;
7
+ };
8
+ export declare function findUnverifiableUserQuote(summary: string): string | null;
@@ -0,0 +1,17 @@
1
+ import type { TSchema } from "typebox";
2
+ import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
3
+ import type { AdapterConfig } from "./config.js";
4
+ export interface ToolPromptOverrides {
5
+ description?: string;
6
+ paramDescriptions?: Record<string, string>;
7
+ promptSnippet?: string;
8
+ promptGuidelines?: string | string[];
9
+ }
10
+ export type AcpToolName = "compress" | "decompress" | "search_context" | "acp_status" | "acp_cache";
11
+ export type ToolPromptsConfig = Partial<Record<AcpToolName, ToolPromptOverrides>>;
12
+ export type NudgeSectionsConfig = Partial<Record<"efficiencyNote" | "emergencyHeader" | "t2Guidance" | "t3Guidance", string | null>>;
13
+ export declare function sanitizeToolPrompts(raw: unknown): ToolPromptsConfig;
14
+ export declare function sanitizeNudgeSections(raw: unknown): NudgeSectionsConfig;
15
+ export declare function applyToolPromptOverrides<TParams extends TSchema>(def: ToolDefinition<TParams>, overrides?: ToolPromptOverrides): ToolDefinition<TParams>;
16
+ export declare function readToolSurfaceSync(cwd: string): ToolPromptsConfig;
17
+ export declare function sanitizeSurfaceConfig(adapter: AdapterConfig): AdapterConfig;
@@ -0,0 +1,4 @@
1
+ import type { SessionMessageEntry } from "@earendil-works/pi-coding-agent";
2
+ type AgentMessage = SessionMessageEntry["message"];
3
+ export declare function carryHostSystemMessages(rebuilt: AgentMessage[], input: AgentMessage[]): AgentMessage[];
4
+ export {};
@@ -0,0 +1,20 @@
1
+ import type { Prompts } from "acp-kernel";
2
+ import { type SectionOverride } from "acp-kernel";
3
+ export interface PiPromptSections {
4
+ acpTags?: SectionOverride;
5
+ summariesInContext?: SectionOverride;
6
+ tools?: SectionOverride;
7
+ whenToCompress?: SectionOverride;
8
+ whenNotToCompress?: SectionOverride;
9
+ multiTierIntro?: SectionOverride;
10
+ decompressPhilosophy?: SectionOverride;
11
+ contextBreakdown?: SectionOverride;
12
+ throttleRetry?: SectionOverride;
13
+ philosophy?: SectionOverride;
14
+ howToCompress?: SectionOverride;
15
+ tier2?: SectionOverride;
16
+ tier3?: SectionOverride;
17
+ }
18
+ export declare const SECTION_KEYS: ReadonlySet<string>;
19
+ export declare function sanitizePromptSections(raw: unknown): Partial<PiPromptSections>;
20
+ export declare function buildAcpSystemPrompt(prompts: Prompts, sections?: Partial<PiPromptSections>): string;
@@ -0,0 +1,3 @@
1
+ export declare function formatTokens(tokens: number): string;
2
+ export declare function stableTagTokens(text: string): string;
3
+ export declare function rewriteTagTokens(tag: string, body: string): string;
@@ -0,0 +1,52 @@
1
+ export declare const THROTTLE_RETRY_ERROR_MESSAGE = "429 rate limit: Too many tokens, please wait before trying again.";
2
+ export declare const THROTTLE_KICK_SENTINEL = "[ACP:provider-throttle]";
3
+ export declare const THROTTLE_KICK_TEXT = "[ACP:provider-throttle] The previous assistant response was interrupted by a provider rate limit (transient, not a real failure). Resume the task exactly where it left off \u2014 do not re-run completed steps and do not discuss the interruption unless asked.";
4
+ export interface ThrottleErrorProbe {
5
+ role: string;
6
+ stopReason?: string;
7
+ errorMessage?: string;
8
+ content: unknown;
9
+ }
10
+ export declare function isThrottleError(msg: ThrottleErrorProbe): boolean;
11
+ export declare function isKickMessage(msg: {
12
+ role: string;
13
+ content: unknown;
14
+ }): boolean;
15
+ export interface ThrottleRetryConfig {
16
+ enabled?: boolean;
17
+ maxRetries?: number;
18
+ baseDelayMs?: number;
19
+ maxDelayMs?: number;
20
+ backoffMode?: "exponential" | "fixed";
21
+ }
22
+ export interface ResolvedThrottleRetry {
23
+ enabled: boolean;
24
+ maxRetries: number;
25
+ baseDelayMs: number;
26
+ maxDelayMs: number;
27
+ backoffMode: "exponential" | "fixed";
28
+ }
29
+ export declare const DEFAULT_THROTTLE_RETRY: ResolvedThrottleRetry;
30
+ export declare function resolveThrottleRetry(cfg: boolean | ThrottleRetryConfig | undefined): ResolvedThrottleRetry;
31
+ export declare function throttleDelayMs(kickNumber: number, r: ResolvedThrottleRetry): number;
32
+ export interface ThrottleEpisodeState {
33
+ attempts: number;
34
+ kicks: number;
35
+ candidate: boolean;
36
+ }
37
+ export declare const INITIAL_THROTTLE_STATE: ThrottleEpisodeState;
38
+ export declare class ThrottleEpisode {
39
+ state: ThrottleEpisodeState;
40
+ private cancel;
41
+ reset(): void;
42
+ onProgress(): void;
43
+ onUserMessage(kick: boolean): void;
44
+ onThrottleError(maxRetries: number): "rewrite" | "exhausted";
45
+ onNonThrottleError(): void;
46
+ readyToKick(maxRetries: number): boolean;
47
+ onKickStarted(): void;
48
+ onKickCancelled(): void;
49
+ sleepController(): AbortController;
50
+ cancelSleep(): void;
51
+ }
52
+ export declare function abortableSleep(ms: number, signal: AbortSignal): Promise<"ok" | "aborted">;
@@ -0,0 +1,61 @@
1
+ import { type CompressionCore, type CompressionState, type Config, type CoreMessage } from "acp-kernel";
2
+ import type { SessionMessageEntry } from "@earendil-works/pi-coding-agent";
3
+ type AgentMessage = SessionMessageEntry["message"];
4
+ export declare function collectCoveredMessageIds(state: {
5
+ blocks: {
6
+ active: boolean;
7
+ effectiveMessageIds: string[];
8
+ }[];
9
+ }): Set<string>;
10
+ export declare const IMAGE_TOKEN_COST = 1600;
11
+ export declare function modelSupportsImages(model: unknown): boolean;
12
+ export declare function collectImageTokens(entries: {
13
+ id: string;
14
+ type?: string;
15
+ message?: AgentMessage;
16
+ }[], visionCapable: boolean): Map<string, number>;
17
+ export declare function estimateTokens(messages: CoreMessage[], coveredIds?: Set<string>, imageTokensById?: Map<string, number>): number;
18
+ export declare function sentViewTokenCount(core: CompressionCore, messages: CoreMessage[], state: CompressionState, config: Config, prelim: number, imageTokensById?: Map<string, number>, systemPromptTokens?: number): {
19
+ viewTokens: number;
20
+ drifted: boolean;
21
+ };
22
+ export declare function adjustedTokenCount(core: CompressionCore, messages: CoreMessage[], state: CompressionState, config: Config, prelim: number, imageTokensById?: Map<string, number>, systemPromptTokens?: number): number;
23
+ /** Per-session record of the EXACT sent view measured off the previous real
24
+ * processTurn output (issue #561) — replaces re-running a probe processTurn
25
+ * (clone + full second pass over the whole history) on every steady-state
26
+ * turn. */
27
+ export interface SentViewMeterRecord {
28
+ viewTokens: number;
29
+ /** The raw (pre-prune) estimate of the same turn, on the sentTokens scale.
30
+ * Lean: the next turn adds its raw growth since then to viewTokens. */
31
+ rawTokens: number;
32
+ /** blockSignature() of the state the view was measured on. */
33
+ blockSig: string;
34
+ /** historyCanary() of the projected messages the view was measured on. */
35
+ history: HistoryCanary;
36
+ limit: number;
37
+ /** False when the measured view may be dishonest (post-truncation output,
38
+ * or the record is absent) — callers must then run the full probe. */
39
+ usable: boolean;
40
+ }
41
+ /** Block count plus each active block's id, coverage size, run, tier and
42
+ * summary length: a decompress + compress pair, an absorb that keeps the
43
+ * active count, or an in-place refold (same id and coverage, rewritten
44
+ * summary) still changes it. Length suffices — only the size is metered. */
45
+ export declare function blockSignature(state: CompressionState): string;
46
+ export interface HistoryCanary {
47
+ count: number;
48
+ firstId: string | null;
49
+ lastId: string | null;
50
+ }
51
+ export declare function historyCanary(messages: CoreMessage[]): HistoryCanary;
52
+ /** Signature check for adopting the previous turn's measured view (issue #561):
53
+ * the meter describes LAST turn's (state, config, history); it is only
54
+ * transferable when nothing structural moved — same window, same block
55
+ * signature, history only appended to — and the raw estimate did not shrink.
56
+ * Pure function, unit-testable. */
57
+ export declare function sentViewMeterMatches(meter: SentViewMeterRecord | undefined, state: CompressionState, config: Config, rawTokens: number, messages: CoreMessage[]): boolean;
58
+ /** Lean: the previous view plus everything appended since. Adopting the bare
59
+ * previous viewTokens dropped the new messages' growth from the meter. */
60
+ export declare function adoptedViewTokens(meter: SentViewMeterRecord, rawTokens: number): number;
61
+ export {};
@@ -0,0 +1,33 @@
1
+ import { type ExtensionAPI, type ToolResultEvent } from "@earendil-works/pi-coding-agent";
2
+ import type { AcpRuntime } from "./runtime.js";
3
+ export type BashToolResultEvent = Extract<ToolResultEvent, {
4
+ toolName: "bash";
5
+ }>;
6
+ export declare function isBashToolResult(e: ToolResultEvent): e is BashToolResultEvent;
7
+ export declare function resolveBashTimeout(input: {
8
+ timeout?: number;
9
+ }, defaultTimeout: number | undefined): number | undefined;
10
+ export declare function capToolOutput(content: ToolResultEvent["content"], maxBytes: number | undefined, fullPath?: string): ToolResultEvent["content"] | undefined;
11
+ export declare function detectBashTimeout(content: ToolResultEvent["content"]): number | undefined;
12
+ export declare function appendTimeoutNotice(content: ToolResultEvent["content"], secs: number): ToolResultEvent["content"];
13
+ export declare function canonicalStringify(value: unknown): string;
14
+ export declare function repetitionFingerprint(toolName: string, input: unknown): string;
15
+ export type RepetitionAction = "none" | "warn" | "abort";
16
+ export interface RepetitionDecision {
17
+ action: RepetitionAction;
18
+ count: number;
19
+ fingerprint: string;
20
+ toolName: string;
21
+ }
22
+ export declare class RepetitionTracker {
23
+ private readonly thresholds;
24
+ private lastFp;
25
+ private count;
26
+ constructor(thresholds: {
27
+ warn: number;
28
+ abort: number;
29
+ });
30
+ note(toolName: string, input: unknown): RepetitionDecision;
31
+ reset(): void;
32
+ }
33
+ export declare function wireToolGuardrails(pi: ExtensionAPI, runtime: AcpRuntime): void;
@@ -0,0 +1,8 @@
1
+ import type { SessionMessageEntry } from "@earendil-works/pi-coding-agent";
2
+ type AgentMessage = SessionMessageEntry["message"];
3
+ export interface ToolPairSanitizeResult {
4
+ messages: AgentMessage[];
5
+ droppedResults: string[];
6
+ }
7
+ export declare function sanitizeToolPairing(messages: AgentMessage[]): ToolPairSanitizeResult;
8
+ export {};
@@ -0,0 +1,53 @@
1
+ /** Single source of truth for turn-boundary decisions (#364).
2
+ *
3
+ * "What counts as a new-turn start" used to be decided independently in three
4
+ * places (per-turn token estimation, turnKey scoping, context-entry
5
+ * projection) and disagreed on host-injected custom_message entries: they
6
+ * enter LLM context but started no turn, so multiple real turns collapsed
7
+ * into one turnKey — nudge ledger cells misaligned, retry-cap and throttle
8
+ * cycle stats distorted, reasoning-drop closing against the wrong turn.
9
+ * Every turn-boundary check in the adapter goes through isTurnBoundary.
10
+ */
11
+ /** Minimal structural shape of a session-log entry for boundary checks. Pi's
12
+ * SessionEntry and the narrower arrays used by token accounting both satisfy
13
+ * it without casts. */
14
+ export interface TurnBoundaryEntry {
15
+ type?: string;
16
+ id?: string;
17
+ customType?: string;
18
+ content?: unknown;
19
+ message?: {
20
+ role?: string;
21
+ };
22
+ }
23
+ /** Host multi-session policy (#364). Unset/false = pi-native behavior: only
24
+ * genuine user-role messages start a turn. Inline multi-session hosts (Prime
25
+ * RLM & co.) inject agent turns as custom_message entries; set
26
+ * countCustomMessages to make those delimit turns too. Default-off keeps
27
+ * standalone pi users' nudge cadence unchanged. */
28
+ export interface TurnBoundaryPolicy {
29
+ countCustomMessages?: boolean;
30
+ /** #578: optional customType allowlist refining countCustomMessages. When
31
+ * set, only injected entries whose customType is listed delimit turns —
32
+ * host metadata injections (harness_digest, ipython_state, …) that enter
33
+ * LLM context without being a real host turn stay out of turn accounting.
34
+ * Unset = every non-empty injected message counts (pre-#578 behavior).
35
+ * Entries without a customType never match; UI-only types stay excluded
36
+ * even when listed. Ignored unless countCustomMessages is true. */
37
+ customMessageTypes?: readonly string[];
38
+ }
39
+ /** The ONE turn-boundary predicate (#364): does this entry start a new turn?
40
+ * Genuine user-role messages always do (pi-native); host-injected
41
+ * custom_message entries do only when the host opts in via policy, optionally
42
+ * refined by a customMessageTypes allowlist so metadata injections
43
+ * (harness_digest, ipython_state) stay out of turn accounting (#578). */
44
+ export declare function isTurnBoundary(entry: TurnBoundaryEntry, policy?: TurnBoundaryPolicy): boolean;
45
+ /** Id of the last turn-boundary entry — the per-turn key for nudge
46
+ * accounting, retry caps and outcome scoping. Defaults to pi-native
47
+ * boundaries (user-role only); pass a host policy to also count injected
48
+ * custom_message entries. Returns undefined when no boundary exists yet. */
49
+ export declare function lastTurnBoundaryId(entries: readonly TurnBoundaryEntry[], policy?: TurnBoundaryPolicy): string | undefined;
50
+ /** Index of the last turn-boundary entry — the start of the current turn for
51
+ * compress-outcome scoping. Same policy semantics as lastTurnBoundaryId;
52
+ * -1 when no boundary has been seen yet. */
53
+ export declare function lastTurnBoundaryIndex(entries: readonly TurnBoundaryEntry[], policy?: TurnBoundaryPolicy): number;
@@ -0,0 +1,67 @@
1
+ import type { Prompts } from "acp-kernel";
2
+ import type { AdapterConfig, CompressConfig, HostSessionConfig, RepetitionGuardConfig } from "./config.js";
3
+ import type { PiPromptSections } from "./system-prompt.js";
4
+ import type { NudgeSectionsConfig, ToolPromptsConfig } from "./surface.js";
5
+ import type { DegenerationGuardConfig } from "./degeneration.js";
6
+ import type { ThrottleRetryConfig } from "./throttle-retry.js";
7
+ import type { IdleCompactConfig } from "./idle-compact.js";
8
+ /** User-facing config keys (subset of AdapterConfig). Loaded from
9
+ * ~/.<CONFIG_DIR_NAME>/acp.json (global) and <cwd>/.<CONFIG_DIR_NAME>/acp.json
10
+ * (project-local overrides project-global). Project wins over global. */
11
+ export interface UserAcpConfig {
12
+ enabled?: boolean;
13
+ debug?: boolean;
14
+ modelContextLimit?: number;
15
+ protectedTools?: string[];
16
+ protectedLatestTools?: string[];
17
+ neverPreserveRecentTools?: string[];
18
+ preserveRecentTools?: string[];
19
+ toolBashDefaultTimeout?: number;
20
+ toolOutputMaxBytes?: number;
21
+ compress?: CompressConfig;
22
+ outputHeadroomMaxPct?: number | string;
23
+ throttleRetry?: boolean | ThrottleRetryConfig;
24
+ idleCompact?: boolean | IdleCompactConfig;
25
+ repetitionGuard?: boolean | RepetitionGuardConfig;
26
+ degenerationGuard?: boolean | DegenerationGuardConfig;
27
+ prompts?: Partial<Prompts>;
28
+ acknowledgePromptsRisk?: boolean;
29
+ promptSections?: PiPromptSections;
30
+ nudgeSections?: NudgeSectionsConfig;
31
+ toolPrompts?: ToolPromptsConfig;
32
+ hostSession?: boolean | HostSessionConfig;
33
+ rules?: boolean;
34
+ compressionModelId?: string;
35
+ }
36
+ /** Read global + project acp.json, project overrides global. Returns {} on any
37
+ * error (missing file, bad JSON) — never throws. Malformed-but-repairable
38
+ * files are salvaged with a loud warning instead of silently meaning "not
39
+ * disabled" / "no config" (#467). */
40
+ export declare function loadUserConfig(cwd: string): Promise<UserAcpConfig>;
41
+ export interface AcpJsonParse {
42
+ status: "ok" | "repaired" | "failed";
43
+ value?: Record<string, unknown>;
44
+ reason?: string;
45
+ }
46
+ /** Lenient parse of a hand-edited acp.json (#467): strict JSON first, then
47
+ * repair the common hand-edit shapes (BOM head, trailing commas, unquoted
48
+ * keys) with a loud warning, then give up with a diagnosed reason. The
49
+ * enabled:false master switch must survive a notepad edit — silently
50
+ * treating the user's file as absent is the exact opposite of intent. */
51
+ export declare function parseAcpJson(file: string, raw: string): AcpJsonParse;
52
+ /** Merge user config onto an adapter config: user config wins for the keys it
53
+ * sets. Used at session_start to apply runtime-discovered config. The two
54
+ * protection keys are shape-checked here because acp.json is hand-edited JSON
55
+ * and a malformed value must warn + fall back, never fail the session or feed
56
+ * garbage to the kernel (#499). */
57
+ export declare function applyUserConfig(adapter: AdapterConfig, user: UserAcpConfig): AdapterConfig;
58
+ /** Persist compressionModelId in the GLOBAL acp.json while preserving every
59
+ * other key (#241 port, `/acp compact`). null removes it. Project-level
60
+ * acp.json still overrides the global value. A present-but-unparseable file
61
+ * is never overwritten. */
62
+ export declare function saveCompressionModelId(value: string | null): Promise<void>;
63
+ /** Read-modify-write the GLOBAL acp.json, keeping every key the mutator does
64
+ * not touch. A present-but-unparseable file is never overwritten. Calls are
65
+ * serialized so quick successive changes (settings panel) never read the same
66
+ * old snapshot and drop each other. */
67
+ export declare function updateGlobalAcpConfig(mutate: (current: Record<string, unknown>) => void): Promise<void>;