@xynogen/pix-runtime 0.4.0 → 0.5.1

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/DESIGN.md CHANGED
@@ -132,7 +132,6 @@ interface PixConfigV1 {
132
132
  optimizer: {
133
133
  caveman: "off" | "lite" | "full" | "ultra" | "micro"; // default off
134
134
  rtk: "off" | "on"; // default on
135
- toon: "off" | "on"; // default on
136
135
  ponytail: "off" | "lite" | "full" | "ultra"; // default off
137
136
  };
138
137
  gate: {
package/README.md CHANGED
@@ -10,7 +10,7 @@ See `DESIGN.md` for the full contract.
10
10
  ## What it does
11
11
 
12
12
  - Versioned, sparse `pix.json` (`$version: 1`) — defaults resolve in code.
13
- - Typed sections: `collapse`, `pretty`, `io`, `optimizer`, `gate`.
13
+ - Typed sections: `collapse`, `pretty`, `io`, `compaction`, `optimizer`, `gate`.
14
14
  - Atomic writes behind a serialized in-process queue and a short-lived
15
15
  cross-process lock. A failed write leaves the old file intact.
16
16
  - Immutable, deeply frozen config snapshots with a monotonic revision.
@@ -49,6 +49,18 @@ with `/pix`. The default is 30 seconds. It applies to Pix network operations,
49
49
  including remote skills, web fetch/search/transcription, MCP requests and
50
50
  connection bootstrap, background model-data refreshes, and update downloads.
51
51
 
52
+ Set `compaction.triggerPercent` in `~/.pi/agent/pix.json`, or change **Compaction →
53
+ Trigger (% ctx)** with `/pix`. It is the context-window usage percent (0–100) used
54
+ to calculate the trigger; the default is `60` and `0` disables the self-trigger
55
+ (pi decides when to compact). The `/pix` picker offers 0, 5, 10, 15, 20, 25, 30,
56
+ 40, 50, 60, 70, 80, 90.
57
+
58
+ `compaction.minimumTokens` is the absolute floor for that calculation. The
59
+ effective threshold is `max(contextWindow × triggerPercent, minimumTokens)`, so
60
+ a 300K-context model at 10% waits for 100K tokens instead of compacting at 30K.
61
+ The default floor is 100K; `/pix` offers 100K, 150K, 200K, 300K, 400K, 500K, and
62
+ 600K. pix-core consumes both settings.
63
+
52
64
  Collapse policy helpers:
53
65
 
54
66
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-runtime",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Pix shared runtime — versioned pix.json config, atomic persistence, typed change events",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/index.ts CHANGED
@@ -31,7 +31,9 @@ export { CONFIG_FORMAT_VERSION, defineSection } from "./schema.ts";
31
31
  export {
32
32
  builtinSections,
33
33
  type CollapseConfig,
34
+ type CompactionConfig,
34
35
  collapseSection,
36
+ compactionSection,
35
37
  type GateConfig,
36
38
  type GateRuleConfig,
37
39
  gateSection,
package/src/migrations.ts CHANGED
@@ -25,7 +25,7 @@ const LEGACY_DIFF_COLOR_KEYS = [
25
25
  "fgDel",
26
26
  ] as const;
27
27
 
28
- const OPTIMIZER_KEYS = ["caveman", "rtk", "toon", "ponytail"] as const;
28
+ const OPTIMIZER_KEYS = ["caveman", "rtk", "ponytail"] as const;
29
29
 
30
30
  export interface MigrationResult {
31
31
  document: RawDocument;
@@ -80,6 +80,14 @@ export function migrate(input: RawDocument, ctx: ParseContext): MigrationResult
80
80
  doc.pretty = pretty;
81
81
  }
82
82
 
83
+ // TOON is now an on-demand pix-skills capability, not optimizer state.
84
+ if (isObj(doc.optimizer) && "toon" in doc.optimizer) {
85
+ doc.optimizer = Object.fromEntries(
86
+ Object.entries(doc.optimizer).filter(([key]) => key !== "toon"),
87
+ );
88
+ changed = true;
89
+ }
90
+
83
91
  if (doc.$version !== CONFIG_FORMAT_VERSION) {
84
92
  doc.$version = CONFIG_FORMAT_VERSION;
85
93
  changed = true;
@@ -128,6 +136,7 @@ export function importOptimizerSidecar(
128
136
  }
129
137
  if (changed) doc.optimizer = existing;
130
138
 
139
+ const hasLegacyToon = typeof raw.toon === "string";
131
140
  const archive = () => {
132
141
  let target = `${sidecarPath}.migrated-v1`;
133
142
  if (existsSync(target)) target = `${target}.${Date.now()}`;
@@ -138,5 +147,5 @@ export function importOptimizerSidecar(
138
147
  }
139
148
  };
140
149
 
141
- return { changed, archive };
150
+ return { changed: changed || hasLegacyToon, archive };
142
151
  }
@@ -18,6 +18,7 @@ import {
18
18
  import type { PixRuntime } from "./runtime.ts";
19
19
  import type { DeepPartial, SectionHandle } from "./schema.ts";
20
20
  import { collapseSection } from "./sections/collapse.ts";
21
+ import { compactionSection } from "./sections/compaction.ts";
21
22
  import { gateSection } from "./sections/gate.ts";
22
23
  import { ioSection } from "./sections/io.ts";
23
24
  import { prettySection } from "./sections/pretty.ts";
@@ -77,6 +78,22 @@ const SETTINGS: SettingRow<unknown>[] = [
77
78
  read: (v) => String(v.timeoutSec),
78
79
  patch: (value) => ({ timeoutSec: Number(value) }),
79
80
  }),
81
+ row({
82
+ section: "Compaction",
83
+ label: "Trigger (% ctx)",
84
+ handle: compactionSection,
85
+ values: ["0", "5", "10", "15", "20", "25", "30", "40", "50", "60", "70", "80", "90"],
86
+ read: (v) => String(v.triggerPercent),
87
+ patch: (value) => ({ triggerPercent: Number(value) }),
88
+ }),
89
+ row({
90
+ section: "Compaction",
91
+ label: "Minimum tokens",
92
+ handle: compactionSection,
93
+ values: ["100k", "150k", "200k", "300k", "400k", "500k", "600k"],
94
+ read: (v) => `${v.minimumTokens / 1000}k`,
95
+ patch: (value) => ({ minimumTokens: Number.parseInt(value, 10) * 1000 }),
96
+ }),
80
97
  row({
81
98
  section: "Gate",
82
99
  label: "Guardrails",
@@ -129,6 +146,9 @@ export function registerPixCommand(pi: ExtensionAPI, runtime: PixRuntime): void
129
146
  kb: KeybindingsManager,
130
147
  done: (v: null) => void,
131
148
  ) => {
149
+ const guide = (key: string, action: string) =>
150
+ theme.fg("text", key) + theme.fg("dim", ` ${action}`);
151
+ const guideSep = theme.fg("dim", " · ");
132
152
  let selected = 0;
133
153
  let bodyOffset = 0;
134
154
  let visibleBodyLines = 1;
@@ -177,7 +197,13 @@ export function registerPixCommand(pi: ExtensionAPI, runtime: PixRuntime): void
177
197
  body,
178
198
  footer: [
179
199
  "",
180
- theme.fg("dim", "←→ change · ↑↓ move · PgUp/PgDn inspect · esc close"),
200
+ guide("←→", "change") +
201
+ guideSep +
202
+ guide("↑↓", "move") +
203
+ guideSep +
204
+ guide("PgUp/PgDn", "inspect") +
205
+ guideSep +
206
+ guide("esc", "close"),
181
207
  ],
182
208
  bodyOffset,
183
209
  selectedBodyLine: inspectingPage ? undefined : settingBodyLines[selected],
@@ -0,0 +1,46 @@
1
+ import { defineSection, isObj } from "../schema.ts";
2
+
3
+ /**
4
+ * Custom compaction config. pix-core always replaces pi's built-in compaction
5
+ * summary. When `triggerPercent > 0` it also drives its own trigger from live
6
+ * context usage. The effective threshold is the larger of that percentage of
7
+ * the active model's context window and `minimumTokens`. `0` disables the
8
+ * self-trigger (pi decides when to compact).
9
+ */
10
+ export interface CompactionConfig {
11
+ /** Context-window usage percent that fires compaction; 0 = off. */
12
+ triggerPercent: number;
13
+ /** Absolute floor for a percentage-based trigger. */
14
+ minimumTokens: number;
15
+ }
16
+
17
+ const MINIMUM_TOKEN_FLOOR = 100_000;
18
+
19
+ const DEFAULTS: Readonly<CompactionConfig> = {
20
+ triggerPercent: 60,
21
+ minimumTokens: MINIMUM_TOKEN_FLOOR,
22
+ };
23
+
24
+ /** Clamp to [0, 100]; non-numbers fall back. */
25
+ function pctOr(v: unknown, fallback: number): number {
26
+ if (typeof v !== "number" || !Number.isFinite(v)) return fallback;
27
+ return Math.min(100, Math.max(0, v));
28
+ }
29
+
30
+ /** Require a finite token floor of at least 100k. */
31
+ function minimumTokensOr(v: unknown, fallback: number): number {
32
+ if (typeof v !== "number" || !Number.isFinite(v)) return fallback;
33
+ return Math.max(MINIMUM_TOKEN_FLOOR, Math.round(v));
34
+ }
35
+
36
+ export const compactionSection = defineSection<"compaction", CompactionConfig>({
37
+ key: "compaction",
38
+ defaults: DEFAULTS,
39
+ parse(raw) {
40
+ if (!isObj(raw)) return { ...DEFAULTS };
41
+ return {
42
+ triggerPercent: pctOr(raw.triggerPercent, DEFAULTS.triggerPercent),
43
+ minimumTokens: minimumTokensOr(raw.minimumTokens, DEFAULTS.minimumTokens),
44
+ };
45
+ },
46
+ });
@@ -1,4 +1,5 @@
1
1
  export { type CollapseConfig, collapseSection } from "./collapse.ts";
2
+ export { type CompactionConfig, compactionSection } from "./compaction.ts";
2
3
  export {
3
4
  type GateConfig,
4
5
  type GateRuleConfig,
@@ -22,6 +23,7 @@ export {
22
23
  } from "./pretty.ts";
23
24
 
24
25
  import { collapseSection } from "./collapse.ts";
26
+ import { compactionSection } from "./compaction.ts";
25
27
  import { gateSection } from "./gate.ts";
26
28
  import { ioSection } from "./io.ts";
27
29
  import { optimizerSection } from "./optimizer.ts";
@@ -34,4 +36,5 @@ export const builtinSections = [
34
36
  ioSection,
35
37
  optimizerSection,
36
38
  gateSection,
39
+ compactionSection,
37
40
  ] as const;
@@ -7,7 +7,6 @@ export type Toggle = "off" | "on";
7
7
  export interface OptimizerConfig {
8
8
  caveman: CavemanLevel;
9
9
  rtk: Toggle;
10
- toon: Toggle;
11
10
  ponytail: PonytailLevel;
12
11
  }
13
12
 
@@ -18,7 +17,6 @@ const TOGGLE: readonly Toggle[] = ["off", "on"];
18
17
  const DEFAULTS: Readonly<OptimizerConfig> = {
19
18
  caveman: "off",
20
19
  rtk: "on",
21
- toon: "on",
22
20
  ponytail: "off",
23
21
  };
24
22
 
@@ -30,7 +28,6 @@ export const optimizerSection = defineSection<"optimizer", OptimizerConfig>({
30
28
  return {
31
29
  caveman: enumOr(raw.caveman, CAVEMAN, DEFAULTS.caveman),
32
30
  rtk: enumOr(raw.rtk, TOGGLE, DEFAULTS.rtk),
33
- toon: enumOr(raw.toon, TOGGLE, DEFAULTS.toon),
34
31
  ponytail: enumOr(raw.ponytail, PONYTAIL, DEFAULTS.ponytail),
35
32
  };
36
33
  },