@alisio/core 0.8.0 → 0.10.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 (58) hide show
  1. package/dist/analysis/data/engine-client.d.ts +2 -0
  2. package/dist/analysis/data/engine-client.js +11 -4
  3. package/dist/application.d.ts +11 -1
  4. package/dist/application.js +46 -0
  5. package/dist/changelog/data.js +42 -0
  6. package/dist/commands/builtins.js +6 -0
  7. package/dist/commands/catalog.d.ts +4 -2
  8. package/dist/commands/catalog.js +23 -1
  9. package/dist/config.d.ts +31 -0
  10. package/dist/config.js +31 -1
  11. package/dist/context/elide.d.ts +50 -0
  12. package/dist/context/elide.js +222 -0
  13. package/dist/context/format.d.ts +21 -0
  14. package/dist/context/format.js +81 -0
  15. package/dist/context/governance.d.ts +58 -0
  16. package/dist/context/governance.js +96 -0
  17. package/dist/context/invariants.d.ts +88 -0
  18. package/dist/context/invariants.js +141 -0
  19. package/dist/context/inventory.d.ts +22 -0
  20. package/dist/context/inventory.js +178 -0
  21. package/dist/context/report.d.ts +35 -0
  22. package/dist/context/report.js +120 -0
  23. package/dist/context/sources.d.ts +20 -0
  24. package/dist/context/sources.js +41 -0
  25. package/dist/context/types.d.ts +43 -0
  26. package/dist/context/types.js +5 -0
  27. package/dist/context/usage.d.ts +96 -0
  28. package/dist/context/usage.js +264 -0
  29. package/dist/context/util.d.ts +22 -0
  30. package/dist/context/util.js +36 -0
  31. package/dist/core/compaction.d.ts +7 -2
  32. package/dist/core/compaction.js +9 -3
  33. package/dist/core/contracts.d.ts +8 -0
  34. package/dist/core/runner.d.ts +67 -0
  35. package/dist/core/runner.js +272 -10
  36. package/dist/eval/compare.d.ts +1 -1
  37. package/dist/eval/compare.js +18 -1
  38. package/dist/eval/engine.js +31 -1
  39. package/dist/eval/report.js +44 -1
  40. package/dist/forge/javascript/hosts/process-host.d.ts +1 -1
  41. package/dist/forge/javascript/hosts/process-host.js +4 -1
  42. package/dist/forge/python/wasm-node.d.ts +1 -1
  43. package/dist/forge/python/wasm-node.js +4 -1
  44. package/dist/index.d.ts +8 -2
  45. package/dist/index.js +8 -2
  46. package/dist/plugins/host.d.ts +5 -0
  47. package/dist/plugins/host.js +15 -1
  48. package/dist/plugins/install.d.ts +50 -2
  49. package/dist/plugins/install.js +102 -12
  50. package/dist/plugins/local-install.d.ts +33 -0
  51. package/dist/plugins/local-install.js +121 -0
  52. package/dist/plugins/npm-runner.d.ts +30 -0
  53. package/dist/plugins/npm-runner.js +66 -0
  54. package/dist/plugins/tarball.d.ts +39 -0
  55. package/dist/plugins/tarball.js +277 -0
  56. package/dist/plugins/version-compat.d.ts +20 -0
  57. package/dist/plugins/version-compat.js +154 -0
  58. package/package.json +2 -2
@@ -7,6 +7,8 @@ export declare class DataError extends Error {
7
7
  }
8
8
  /** The engine script on disk (`<root>/analysis/engine/data-engine-<hash>.mjs`). */
9
9
  export declare function ensureEngineScript(root: string): Promise<string>;
10
+ /** The whitelisted environment of an engine child, from `parent`. */
11
+ export declare function engineChildEnv(parent?: NodeJS.ProcessEnv): Record<string, string>;
10
12
  export interface DataEngineOptions {
11
13
  /** State folder (`<root>/analysis/engine/…` holds the script). */
12
14
  root: string;
@@ -57,7 +57,17 @@ const ENV_KEYS = [
57
57
  "SYSTEMROOT",
58
58
  "WINDIR",
59
59
  "LANG",
60
+ // Set by hosts that run on the Electron binary (the desktop app): without it the worker, which is
61
+ // spawned from `process.execPath`, would start another GUI instance instead of plain Node.
62
+ "ELECTRON_RUN_AS_NODE",
60
63
  ];
64
+ /** The whitelisted environment of an engine child, from `parent`. */
65
+ export function engineChildEnv(parent = process.env) {
66
+ return {
67
+ ...Object.fromEntries(ENV_KEYS.flatMap((k) => (parent[k] ? [[k, parent[k]]] : []))),
68
+ BUN_BE_BUN: "1",
69
+ };
70
+ }
61
71
  /** One engine child process and its pending request. */
62
72
  class EngineProcess {
63
73
  child;
@@ -74,10 +84,7 @@ class EngineProcess {
74
84
  : [`--max-old-space-size=${memoryMb}`, "--disable-warning=ExperimentalWarning"]),
75
85
  script,
76
86
  ], {
77
- env: {
78
- ...Object.fromEntries(ENV_KEYS.flatMap((k) => (process.env[k] ? [[k, process.env[k]]] : []))),
79
- BUN_BE_BUN: "1",
80
- },
87
+ env: engineChildEnv(),
81
88
  stdio: ["pipe", "pipe", "pipe"],
82
89
  windowsHide: true,
83
90
  });
@@ -1,4 +1,4 @@
1
- import type { AvailableProviderModel, ModelInfo, ModelProvider, Plugin, PluginCategory, ProviderConfigurationValue, ResolvedProviderModel, RunEvent } from "@alisio/sdk";
1
+ import type { AvailableProviderModel, ContextReport, ModelInfo, ModelProvider, Plugin, PluginCategory, ProviderConfigurationValue, ResolvedProviderModel, RunEvent } from "@alisio/sdk";
2
2
  import { AgentDefinitionService } from "./agents/service.ts";
3
3
  import { CapabilityGrants } from "./analysis/capabilities.ts";
4
4
  import { DatasetService } from "./analysis/data/datasets.ts";
@@ -148,6 +148,11 @@ export interface PluginCatalogEntry {
148
148
  enabled: boolean;
149
149
  manageable: boolean;
150
150
  diagnostic?: string;
151
+ /**
152
+ * The npm package name an external plugin was loaded from. Never a path: the catalog stays
153
+ * path-safe, so file and directory plugins carry no `spec`.
154
+ */
155
+ spec?: string;
151
156
  }
152
157
  export interface ConfiguredProviderCatalog {
153
158
  profile: string;
@@ -350,6 +355,11 @@ export declare function createApplication(options?: AppOptions): Promise<{
350
355
  basis: "window" | "unknown";
351
356
  compactionAt: number;
352
357
  };
358
+ /**
359
+ * The Context Governor's explanation of a session (spec 012 phase 5): the one `ContextReport`
360
+ * that `/context`, the web panel and the HTTP route render. Read only.
361
+ */
362
+ contextReport: (sessionId: string) => ContextReport;
353
363
  loadModels: (signal: AbortSignal) => Promise<ModelInfo[]>;
354
364
  /** Creates a provider for validation/model discovery without changing the active provider. */
355
365
  probeProvider(id: string, profile: Record<string, import("@alisio/sdk").ProviderConfigurationValue>, credentials: Record<string, string>, signal: AbortSignal): Promise<ModelInfo[]>;
@@ -15,6 +15,9 @@ import { TaskJanitor } from "./background/janitor.js";
15
15
  import { subagentTasks } from "./background/mirror.js";
16
16
  import { BackgroundTasks } from "./background/service.js";
17
17
  import { configFile, configHome, loadConfigWithProvenance, overridesSavedProviderProfile, setConfigValue, setGlobalMcpAllow, setMcpServerEnabled, setProjectPluginEnabled, setProjectSkillEnabled, stateHome, } from "./config.js";
18
+ import { CONTEXT_POLICY_VERSION } from "./context/elide.js";
19
+ import { buildContextReport } from "./context/report.js";
20
+ import { createInvariantSources } from "./context/sources.js";
18
21
  import { completeText } from "./core/compaction.js";
19
22
  import { HumanWaits } from "./core/human-wait.js";
20
23
  import { DEFAULT_MAX_OUTPUT_TOKENS } from "./core/output-limit.js";
@@ -31,6 +34,7 @@ import { GoalService } from "./goal/service.js";
31
34
  import { GoalStore } from "./goal/store.js";
32
35
  import { HerdrBridge } from "./integrations/herdr.js";
33
36
  import { McpConnector } from "./mcp/connector.js";
37
+ import { currentPlan } from "./plan/state.js";
34
38
  import { discoverPlugins, PluginHost } from "./plugins/host.js";
35
39
  import { ProofVerifierRegistry } from "./proof/plugin-verifiers.js";
36
40
  import { ProofRecorder } from "./proof/recorder.js";
@@ -328,6 +332,7 @@ export async function createApplication(options = {}) {
328
332
  status: enabled ? "active" : "inactive",
329
333
  enabled,
330
334
  manageable: entry.manageable,
335
+ ...(isPathSpec(entry.spec) ? {} : { spec: entry.spec }),
331
336
  });
332
337
  runtimePluginEnabled.set(candidate.plugin.id, enabled);
333
338
  }
@@ -345,6 +350,7 @@ export async function createApplication(options = {}) {
345
350
  enabled: true,
346
351
  manageable: entry.manageable,
347
352
  diagnostic: "Activation failed; registrations were rolled back",
353
+ ...(isPathSpec(entry.spec) ? {} : { spec: entry.spec }),
348
354
  });
349
355
  continue;
350
356
  }
@@ -360,6 +366,7 @@ export async function createApplication(options = {}) {
360
366
  enabled: true,
361
367
  manageable: false,
362
368
  diagnostic: "Load failed; no path or credential details are shown here",
369
+ ...(isPathSpec(entry.spec) ? {} : { spec: entry.spec }),
363
370
  });
364
371
  }
365
372
  }
@@ -982,6 +989,25 @@ export async function createApplication(options = {}) {
982
989
  policy: runtimePolicy,
983
990
  ...config.limits,
984
991
  compaction: config.compaction,
992
+ contextGovernor: {
993
+ mode: config.context.governor,
994
+ ...config.context.elide,
995
+ policyVersion: config.context.policyVersion ?? CONTEXT_POLICY_VERSION,
996
+ invariantMaxChars: config.context.invariants.maxChars,
997
+ },
998
+ invariantSources: createInvariantSources({
999
+ goal: (sessionId) => goals.get(sessionId),
1000
+ plan: (sessionId) => currentPlan(store, sessionId),
1001
+ criteria: (sessionId) => new ProofStore(store.db).criteria(sessionId),
1002
+ ...(analysisEnabled
1003
+ ? {
1004
+ capabilities: {
1005
+ granted: (capability, sessionId) => capabilityGrants.granted(capability, sessionId),
1006
+ },
1007
+ }
1008
+ : {}),
1009
+ externalDirectories: () => pathAccess.approvedDirectories().length,
1010
+ }),
985
1011
  extensions: plugins,
986
1012
  contextWindow,
987
1013
  modelOutputLimit: declaredOutputLimit,
@@ -1586,6 +1612,26 @@ export async function createApplication(options = {}) {
1586
1612
  contextWindow,
1587
1613
  /** Effective context budget + compaction point shared by the runner and the TUI bar. */
1588
1614
  contextBudget: (model) => runner.contextBudget(model),
1615
+ /**
1616
+ * The Context Governor's explanation of a session (spec 012 phase 5): the one `ContextReport`
1617
+ * that `/context`, the web panel and the HTTP route render. Read only.
1618
+ */
1619
+ contextReport: (sessionId) => {
1620
+ const inputs = runner.contextInputs(sessionId);
1621
+ return buildContextReport({
1622
+ sessionId,
1623
+ governor: inputs?.governor ?? {
1624
+ mode: "off",
1625
+ minAgeTurns: 0,
1626
+ epochMessages: 0,
1627
+ policyVersion: CONTEXT_POLICY_VERSION,
1628
+ },
1629
+ messages: inputs?.messages ?? [],
1630
+ ...(inputs ? { invariants: inputs.invariants } : {}),
1631
+ ...(inputs?.ledger ? { ledger: inputs.ledger } : {}),
1632
+ ...(inputs?.governance ? { governance: inputs.governance } : {}),
1633
+ });
1634
+ },
1589
1635
  loadModels,
1590
1636
  /** Creates a provider for validation/model discovery without changing the active provider. */
1591
1637
  async probeProvider(id, profile, credentials, signal) {
@@ -2,6 +2,48 @@
2
2
  export const CHANGELOG_DATA = {
3
3
  schema: 1,
4
4
  entries: [
5
+ {
6
+ version: "0.10.0",
7
+ date: "2026-11-07",
8
+ sections: [
9
+ {
10
+ title: "Added",
11
+ items: [
12
+ "Settings → Plugins → **Plugin catalog**: browse the published plugins with search, category and tag filters and pages of 10, and install one with a click. The catalog is cached for 24 hours and only downloaded again when it changed; **Check for updates** checks now.",
13
+ "**Install from file**: install a private plugin from a `.tgz` made with `npm pack` or `pnpm pack`. The file is validated first (an Alisio plugin, its SDK range, install scripts), the install shows its progress step by step, and temporary files are removed. The package is kept under `~/.config/alisio/plugins/local-packages`, so the original file can be moved or deleted.",
14
+ "**Installed plugins**: uninstall a plugin, see when a catalog plugin has a newer version, and update one or all of them. Installing a plugin that is already there updates it in place.",
15
+ ],
16
+ },
17
+ {
18
+ title: "Improved",
19
+ items: [
20
+ "When `npm` is missing, installing a plugin says so clearly instead of failing with a spawn error. Alisio Desktop bundles its own `npm`, so plugins install without Node.js on the system.",
21
+ ],
22
+ },
23
+ ],
24
+ },
25
+ {
26
+ version: "0.9.0",
27
+ date: "2026-11-07",
28
+ sections: [
29
+ {
30
+ title: "Added",
31
+ items: [
32
+ "Alisio Desktop: install Alisio like any other program on Windows, macOS or Linux, with no terminal and no Node.js. Download it from the documentation home page or the `desktop-v0.9.0` GitHub release. The desktop app shares its sessions and settings with the CLI.",
33
+ "Context Governor (experimental, off by default): `context.governor` (`off`, `shadow`, `on`). In `on` mode, tool results made redundant by a later one (superseded reads, repeated identical `bg_output` polls, duplicate searches) are replaced by a one-line stub in the request, and your instructions, the live goal, the approved plan, Proof criteria and session grants survive compaction verbatim. `shadow` only measures and sends exactly what `off` sends.",
34
+ "`/context`, a **Context** tab in the web UI and `GET /api/sessions/:sid/context/report`: what the next request carries and what the governor elides, with counts and token estimates only.",
35
+ "`/stats` shows a Context Governor section, and the web trajectory shows each governed request.",
36
+ "Reliability Lab: the `long-context-stress` suite, and runs record the provider's cached input tokens (`cachedInputTokens`), with a cached-input ratio in reports and `eval compare`.",
37
+ ],
38
+ },
39
+ {
40
+ title: "Improved",
41
+ items: [
42
+ "Alisio now presents itself as an assistant for everyday work (`alisio --help`, the web UI About text and the documentation); programming remains fully supported as an advanced use.",
43
+ ],
44
+ },
45
+ ],
46
+ },
5
47
  {
6
48
  version: "0.8.0",
7
49
  date: "2026-10-14",
@@ -158,6 +158,12 @@ export const BUILTIN_COMMANDS = [
158
158
  surfaces: INTERACTIVE,
159
159
  execution: "surface",
160
160
  },
161
+ {
162
+ name: "context",
163
+ description: "Show what the next request carries and what the Context Governor elides",
164
+ surfaces: ALL,
165
+ execution: "core",
166
+ },
161
167
  {
162
168
  name: "decisions",
163
169
  description: "Show Decision Intelligence status, provider health and session metrics",
@@ -4,7 +4,7 @@
4
4
  * The catalog reads its sources live from the host on each call, so plugin/template/skill
5
5
  * changes show up without rebuilding it.
6
6
  */
7
- import { type CommandDescriptor, type ToolDefinition } from "@alisio/sdk";
7
+ import { type CommandDescriptor, type ContextReport, type ToolDefinition } from "@alisio/sdk";
8
8
  import type { PluginCatalogEntry } from "../application.ts";
9
9
  import type { SessionStore } from "../core/contracts.ts";
10
10
  import type { AgentRunner } from "../core/runner.ts";
@@ -26,7 +26,9 @@ type PluginCommandHandler = (args: string, context?: {
26
26
  export interface CommandHost {
27
27
  workspace: string;
28
28
  store: SessionStore;
29
- runner: Pick<AgentRunner, "policy" | "approvals" | "compact" | "setModel">;
29
+ runner: Pick<AgentRunner, "policy" | "approvals" | "compact" | "setModel"> & Partial<Pick<AgentRunner, "governance" | "contextUsage">>;
30
+ /** The Context Governor's report of a session (`/context`); `createApplication` provides it. */
31
+ contextReport?(sessionId: string): ContextReport;
30
32
  registry: {
31
33
  list(): ToolDefinition[];
32
34
  };
@@ -6,6 +6,8 @@
6
6
  */
7
7
  import { summarizeDecisionEvents, } from "@alisio/sdk";
8
8
  import { agentCatalogFromState, agentCommandDescriptors, resolveActiveAgent, } from "../agents/active.js";
9
+ import { formatContextGovernorStatsSection, formatContextReport } from "../context/format.js";
10
+ import { governedEventData } from "../context/governance.js";
9
11
  import { formatDecisionStatsSection, formatDecisionsReport, } from "../decisions/index.js";
10
12
  import { formatSideQuestion, SIDE_QUESTION_DESCRIPTION, SIDE_QUESTION_USAGE, } from "../sessions/side-questions.js";
11
13
  import { BUILTIN_COMMANDS } from "./builtins.js";
@@ -174,6 +176,12 @@ const HANDLERS = {
174
176
  output: sum.output + (r.usage?.output ?? 0),
175
177
  }), { input: 0, output: 0 });
176
178
  const decisionSection = formatDecisionStatsSection(summarizeDecisionEvents(decisionEvents(host, ctx.sessionId)));
179
+ const governance = host.runner?.governance?.(session.id);
180
+ const usage = host.runner?.contextUsage?.(session.id);
181
+ const governorSection = formatContextGovernorStatsSection({
182
+ ...(governance ? { governance } : {}),
183
+ ...(usage ? { usage } : {}),
184
+ });
177
185
  return {
178
186
  text: [
179
187
  "**Session statistics**",
@@ -184,10 +192,24 @@ const HANDLERS = {
184
192
  `- Runs: ${runs.length}`,
185
193
  `- Tokens: in ${tokens.input} · out ${tokens.output}`,
186
194
  ...(decisionSection ? ["", decisionSection] : []),
195
+ ...(governorSection ? ["", governorSection] : []),
187
196
  ].join("\n"),
188
- data: { session, runs: runs.length, tokens },
197
+ data: {
198
+ session,
199
+ runs: runs.length,
200
+ tokens,
201
+ ...(governorSection
202
+ ? { contextGovernor: governance ? governedEventData(governance, usage) : { usage } }
203
+ : {}),
204
+ },
189
205
  };
190
206
  },
207
+ async context(_args, ctx, host) {
208
+ if (!host.contextReport)
209
+ return { text: "The context report is not available in this host.", tone: "notice" };
210
+ const report = host.contextReport(ctx.sessionId);
211
+ return { text: formatContextReport(report), data: report };
212
+ },
191
213
  async skills(_args, _ctx, host) {
192
214
  const skills = host.skillCatalog?.() ?? [];
193
215
  if (!skills.length)
package/dist/config.d.ts CHANGED
@@ -161,6 +161,19 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
161
161
  context: z.ZodDefault<z.ZodObject<{
162
162
  claudeMdFallback: z.ZodDefault<z.ZodBoolean>;
163
163
  maxBytes: z.ZodDefault<z.ZodNumber>;
164
+ governor: z.ZodDefault<z.ZodEnum<{
165
+ off: "off";
166
+ on: "on";
167
+ shadow: "shadow";
168
+ }>>;
169
+ policyVersion: z.ZodOptional<z.ZodNumber>;
170
+ elide: z.ZodDefault<z.ZodObject<{
171
+ minAgeTurns: z.ZodDefault<z.ZodNumber>;
172
+ epochMessages: z.ZodDefault<z.ZodNumber>;
173
+ }, z.core.$strict>>;
174
+ invariants: z.ZodDefault<z.ZodObject<{
175
+ maxChars: z.ZodDefault<z.ZodNumber>;
176
+ }, z.core.$strict>>;
164
177
  }, z.core.$strict>>;
165
178
  compaction: z.ZodDefault<z.ZodObject<{
166
179
  auto: z.ZodDefault<z.ZodBoolean>;
@@ -351,6 +364,15 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
351
364
  context: {
352
365
  claudeMdFallback: boolean;
353
366
  maxBytes: number;
367
+ governor: "off" | "on" | "shadow";
368
+ policyVersion?: number | undefined;
369
+ elide: {
370
+ minAgeTurns: number;
371
+ epochMessages: number;
372
+ };
373
+ invariants: {
374
+ maxChars: number;
375
+ };
354
376
  };
355
377
  compaction: {
356
378
  auto: boolean;
@@ -600,6 +622,15 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
600
622
  context: {
601
623
  claudeMdFallback: boolean;
602
624
  maxBytes: number;
625
+ governor: "off" | "on" | "shadow";
626
+ policyVersion?: number | undefined;
627
+ elide: {
628
+ minAgeTurns: number;
629
+ epochMessages: number;
630
+ };
631
+ invariants: {
632
+ maxChars: number;
633
+ };
603
634
  };
604
635
  compaction: {
605
636
  auto: boolean;
package/dist/config.js CHANGED
@@ -2,6 +2,7 @@ import { chmod, mkdir, open, readFile, realpath, rename, unlink } from "node:fs/
2
2
  import { homedir } from "node:os";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { z } from "zod";
5
+ import { CONTEXT_POLICY_VERSION } from "./context/elide.js";
5
6
  import { DEFAULT_DECISIONS_CONFIG } from "./decisions/service.js";
6
7
  import { DEFAULT_FORGE_LIMITS, MAX_FORGE_LIMITS } from "./forge/limits.js";
7
8
  import { exists, readJson } from "./runtime/fs.js";
@@ -230,9 +231,38 @@ const configObjectSchema = z
230
231
  .min(1024)
231
232
  .max(1_048_576)
232
233
  .default(32 * 1024),
234
+ /**
235
+ * Context Governor (spec 012): `off` changes nothing, `shadow` computes what it would elide
236
+ * without changing what is sent, `on` sends the projected history.
237
+ */
238
+ governor: z.enum(["off", "shadow", "on"]).default("off"),
239
+ /** Optional pin of the elision rules; unset follows this build's current version. */
240
+ policyVersion: z.number().int().min(1).max(CONTEXT_POLICY_VERSION).optional(),
241
+ elide: z
242
+ .object({
243
+ /** Assistant messages a redundant result must be older than before it is replaced. */
244
+ minAgeTurns: z.number().int().min(1).max(20).default(2),
245
+ /** Projection grows in steps of this many messages (cache-friendly); 0 is eager. */
246
+ epochMessages: z.number().int().min(0).max(1000).default(32),
247
+ })
248
+ .strict()
249
+ .default(() => ({ minAgeTurns: 2, epochMessages: 32 })),
250
+ invariants: z
251
+ .object({
252
+ /** Size cap of the `<invariants>` block sent after a compaction (`on` mode only). */
253
+ maxChars: z.number().int().min(1000).max(20_000).default(4000),
254
+ })
255
+ .strict()
256
+ .default(() => ({ maxChars: 4000 })),
233
257
  })
234
258
  .strict()
235
- .default(() => ({ claudeMdFallback: false, maxBytes: 32 * 1024 })),
259
+ .default(() => ({
260
+ claudeMdFallback: false,
261
+ maxBytes: 32 * 1024,
262
+ governor: "off",
263
+ elide: { minAgeTurns: 2, epochMessages: 32 },
264
+ invariants: { maxChars: 4000 },
265
+ })),
236
266
  compaction: z
237
267
  .object({
238
268
  auto: z.boolean().default(true),
@@ -0,0 +1,50 @@
1
+ import type { Message } from "@alisio/sdk";
2
+ /**
3
+ * Deterministic elision by substitution (spec 012 phase 2). Pure functions over `Message[]`: they
4
+ * never touch the persisted transcript, only compute what a request MAY send instead.
5
+ */
6
+ /** Version of the elision rules. Bump it whenever a rule or a stub format changes. */
7
+ export declare const CONTEXT_POLICY_VERSION = 1;
8
+ export type ElisionReason = "superseded_read" | "stale_bg_output" | "duplicate_search";
9
+ export interface Elision {
10
+ /** Index of the elided tool message in the projected history. */
11
+ index: number;
12
+ callId: string;
13
+ /** Inventory id of the tool_result segment (same as `ContextSegment.id`). */
14
+ segmentId: string;
15
+ reason: ElisionReason;
16
+ /** Call id of the EARLIEST later result that justifies the elision (stable under append). */
17
+ witnessCallId: string;
18
+ /** Token ESTIMATE saved (`estimateTokens` of the original text minus that of the stub). */
19
+ tokensSaved: number;
20
+ }
21
+ export interface Projection {
22
+ /** The input array itself when nothing is elided, else a copy with only elided entries replaced. */
23
+ messages: Message[];
24
+ elisions: Elision[];
25
+ /** Estimate of the model-visible text of the history before and after the projection. */
26
+ tokensBefore: number;
27
+ tokensAfter: number;
28
+ /** Messages `[0, boundary)` are eligible; the rest is sent as is until the next epoch. */
29
+ boundary: number;
30
+ }
31
+ /**
32
+ * Which tool results a later result makes redundant, regardless of age. `witness` is the index of
33
+ * the EARLIEST later result that proves it, so it never changes when messages are appended. Every
34
+ * rule is existential ("some later result exists"), which is what makes the set grow monotonically.
35
+ * Error results are neither candidates nor witnesses.
36
+ */
37
+ export declare function elisionCandidates(messages: Message[]): Map<number, {
38
+ reason: ElisionReason;
39
+ witness: number;
40
+ }>;
41
+ /**
42
+ * Projects the history a request would carry: tool results that a later result made redundant are
43
+ * replaced by a one-line stub once they are `minAgeTurns` assistant messages old (counted inside the
44
+ * eligible prefix). With `epochMessages` E > 0 only the prefix `floor(n / E) * E` is eligible, so the
45
+ * projected history grows by appending between epochs and a provider prompt cache stays valid.
46
+ */
47
+ export declare function projectContext(messages: Message[], o: {
48
+ minAgeTurns: number;
49
+ epochMessages: number;
50
+ }): Projection;