@xynogen/pix-core 0.5.33 → 0.5.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -53,7 +53,7 @@ Pi activates extensions per installed package via each package's `pi.extensions`
53
53
  | `pix-runtime` | Unified `~/.pi/agent/pix.json` config runtime, `/pix` settings command, once guard, and auto-collapse policy |
54
54
  | `pix-optimizer` | Caveman mode + RTK tool rewriting + ponytail lazy-dev mode (`/optimizer`) |
55
55
  | `pix-gate` | Permission gate for dangerous bash commands |
56
- | `pix-subagent` | `agent` / `agent_result` / `agent_steer` — planner-driven sub-agents with live widget |
56
+ | `pix-subagent` | `agent` / `agent_control` — planner-driven sub-agents with live widget |
57
57
  | `compaction` | Built into pix-core: replaces pi's built-in context compaction with two levers. **Summary prompt is always pix's** — every compaction (manual `/compact`, threshold, overflow) generates the summary with the current conversation model (no silent routing) from an editable source prompt. **Trigger is pix's when `compaction.triggerPercent > 0`** — after each settled turn pix reads live context usage and compacts at `max(contextWindow × triggerPercent, compaction.minimumTokens)`. The default 100K floor prevents low percentages from compacting too early (for example, 10% of a 300K model waits for 100K rather than 30K); `0` disables pix's trigger and lets pi decide, while pix's summary prompt still applies. After a pix-triggered compaction pix sends a short, visible "resume" user message so the agent continues on its own. Every trigger, resume, and summary run emits a notify line (reason, model, token counts). |
58
58
 
59
59
  ## Install
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-core",
3
- "version": "0.5.33",
3
+ "version": "0.5.34",
4
4
  "description": "Pi extension bundle — installs and activates all core pix-* extensions",
5
5
  "type": "module",
6
6
  "main": "src/extension.ts",
@@ -18,6 +18,7 @@
18
18
  },
19
19
  "files": [
20
20
  "src",
21
+ "!src/**/*.test.*",
21
22
  "README.md",
22
23
  "LICENSE"
23
24
  ],
@@ -42,31 +43,31 @@
42
43
  "access": "public"
43
44
  },
44
45
  "dependencies": {
45
- "@xynogen/pix-runtime": "^0.8.1",
46
- "@xynogen/pix-data": "^0.4.6",
47
- "@xynogen/pix-pretty": "^1.19.0",
48
- "@xynogen/pix-welcome": "^0.2.1",
49
- "@xynogen/pix-footer": "^0.2.2",
50
- "@xynogen/pix-diagnostics": "^0.2.2",
51
- "@xynogen/pix-display": "^0.1.16",
52
- "@xynogen/pix-prompts": "^0.4.4",
53
- "@xynogen/pix-skills": "^0.8.3",
54
- "@xynogen/pix-models": "^0.2.4",
55
- "@xynogen/pix-update": "^0.2.0",
56
- "@xynogen/pix-commands": "^0.6.2",
57
- "@xynogen/pix-nudge": "^0.2.3",
58
- "@xynogen/pix-read": "^0.2.3",
59
- "@xynogen/pix-write": "^0.2.3",
60
- "@xynogen/pix-edit": "^0.2.3",
61
- "@xynogen/pix-find": "^0.2.3",
62
- "@xynogen/pix-grep": "^0.2.4",
63
- "@xynogen/pix-ls": "^0.2.3",
64
- "@xynogen/pix-bash": "^0.2.3",
65
- "@xynogen/pix-todo": "^0.3.2",
66
- "@xynogen/pix-ask": "^0.2.26",
67
- "@xynogen/pix-optimizer": "^1.1.28",
68
- "@xynogen/pix-gate": "^0.2.11",
69
- "@xynogen/pix-subagent": "^0.7.3"
46
+ "@xynogen/pix-runtime": "^0.8.2",
47
+ "@xynogen/pix-data": "^0.4.7",
48
+ "@xynogen/pix-pretty": "^1.20.0",
49
+ "@xynogen/pix-welcome": "^0.2.2",
50
+ "@xynogen/pix-footer": "^0.2.3",
51
+ "@xynogen/pix-diagnostics": "^0.2.3",
52
+ "@xynogen/pix-display": "^0.1.17",
53
+ "@xynogen/pix-prompts": "^0.4.5",
54
+ "@xynogen/pix-skills": "^0.8.4",
55
+ "@xynogen/pix-models": "^0.2.5",
56
+ "@xynogen/pix-update": "^0.2.1",
57
+ "@xynogen/pix-commands": "^0.6.3",
58
+ "@xynogen/pix-nudge": "^0.2.4",
59
+ "@xynogen/pix-read": "^0.2.4",
60
+ "@xynogen/pix-write": "^0.2.4",
61
+ "@xynogen/pix-edit": "^0.2.4",
62
+ "@xynogen/pix-find": "^0.2.4",
63
+ "@xynogen/pix-grep": "^0.2.5",
64
+ "@xynogen/pix-ls": "^0.2.4",
65
+ "@xynogen/pix-bash": "^0.2.4",
66
+ "@xynogen/pix-todo": "^0.3.3",
67
+ "@xynogen/pix-ask": "^0.2.27",
68
+ "@xynogen/pix-optimizer": "^1.1.29",
69
+ "@xynogen/pix-gate": "^0.2.12",
70
+ "@xynogen/pix-subagent": "^0.8.0"
70
71
  },
71
72
  "peerDependencies": {
72
73
  "@earendil-works/pi-ai": "*",
@@ -1,82 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { compactionThresholdTokens, resumeDecisionAfterCompaction } from "./compaction.ts";
3
-
4
- describe("compactionThresholdTokens", () => {
5
- it("uses the 100k floor when the percentage threshold is lower", () => {
6
- expect(compactionThresholdTokens(300_000, 10, 100_000)).toBe(100_000);
7
- });
8
-
9
- it("uses the percentage threshold when it is higher than the floor", () => {
10
- expect(compactionThresholdTokens(1_000_000, 60, 100_000)).toBe(600_000);
11
- });
12
- });
13
-
14
- describe("resumeDecisionAfterCompaction", () => {
15
- const threshold = 100_000;
16
-
17
- it("resumes when idle, no pending messages, and under threshold", () => {
18
- expect(
19
- resumeDecisionAfterCompaction({
20
- estimatedTokensAfter: 40_000,
21
- threshold,
22
- idle: true,
23
- hasPending: false,
24
- }),
25
- ).toBe("resume");
26
- });
27
-
28
- it("skips when the agent is busy with the user's in-flight prompt", () => {
29
- expect(
30
- resumeDecisionAfterCompaction({
31
- estimatedTokensAfter: 40_000,
32
- threshold,
33
- idle: false,
34
- hasPending: false,
35
- }),
36
- ).toBe("skip");
37
- });
38
-
39
- it("skips when the user has a prompt queued during compaction", () => {
40
- expect(
41
- resumeDecisionAfterCompaction({
42
- estimatedTokensAfter: 40_000,
43
- threshold,
44
- idle: true,
45
- hasPending: true,
46
- }),
47
- ).toBe("skip");
48
- });
49
-
50
- it("latches off (loop) when still at/above threshold after compaction", () => {
51
- expect(
52
- resumeDecisionAfterCompaction({
53
- estimatedTokensAfter: threshold,
54
- threshold,
55
- idle: true,
56
- hasPending: false,
57
- }),
58
- ).toBe("loop");
59
- });
60
-
61
- it("treats a missing estimate as 0 tokens (does not latch)", () => {
62
- expect(
63
- resumeDecisionAfterCompaction({
64
- estimatedTokensAfter: undefined,
65
- threshold,
66
- idle: true,
67
- hasPending: false,
68
- }),
69
- ).toBe("resume");
70
- });
71
-
72
- it("loop takes priority over a busy agent", () => {
73
- expect(
74
- resumeDecisionAfterCompaction({
75
- estimatedTokensAfter: 200_000,
76
- threshold,
77
- idle: false,
78
- hasPending: true,
79
- }),
80
- ).toBe("loop");
81
- });
82
- });
package/src/once.test.ts DELETED
@@ -1,213 +0,0 @@
1
- import { afterEach, describe, expect, it } from "bun:test";
2
- import registerCommands from "@xynogen/pix-commands/extension";
3
- import registerSubagent from "@xynogen/pix-subagent/extension";
4
- import registerTodo from "@xynogen/pix-todo";
5
-
6
- // Mirror of the per-instance guard. pix-core does not own once.ts (each member
7
- // duplicates it to stay cross-dep-free), so we re-declare the contract here and
8
- // assert the dedupe semantics that the aggregator relies on.
9
- function once(pi: object, key: string, fn: () => void): void {
10
- const g = globalThis as { __pixOnce?: WeakMap<object, Set<string>> };
11
- if (!g.__pixOnce) g.__pixOnce = new WeakMap<object, Set<string>>();
12
- const registry = g.__pixOnce;
13
- let loaded = registry.get(pi);
14
- if (!loaded) {
15
- loaded = new Set<string>();
16
- registry.set(pi, loaded);
17
- }
18
- if (loaded.has(key)) return;
19
- loaded.add(key);
20
- fn();
21
- }
22
-
23
- afterEach(() => {
24
- delete (globalThis as { __pixOnce?: WeakMap<object, Set<string>> }).__pixOnce;
25
- });
26
-
27
- describe("once", () => {
28
- it("runs the factory on first invocation", () => {
29
- const pi = {};
30
- let calls = 0;
31
- once(pi, "pix-footer", () => {
32
- calls++;
33
- });
34
- expect(calls).toBe(1);
35
- });
36
-
37
- it("skips repeated invocations of the same key on the same pi", () => {
38
- const pi = {};
39
- let calls = 0;
40
- const reg = () => {
41
- calls++;
42
- };
43
- once(pi, "pix-footer", reg);
44
- once(pi, "pix-footer", reg);
45
- once(pi, "pix-footer", reg);
46
- expect(calls).toBe(1);
47
- });
48
-
49
- it("isolates distinct keys on the same pi", () => {
50
- const pi = {};
51
- const seen: string[] = [];
52
- once(pi, "pix-footer", () => seen.push("footer"));
53
- once(pi, "pix-welcome", () => seen.push("welcome"));
54
- expect(seen).toEqual(["footer", "welcome"]);
55
- });
56
-
57
- it("shares the registry across calls via globalThis (same pi)", () => {
58
- const pi = {};
59
- let calls = 0;
60
- once(pi, "pix-skills", () => {
61
- calls++;
62
- });
63
- // A second loader pass (e.g. standalone install after pix-core) reuses
64
- // the same globalThis registry for the same pi and must not re-run.
65
- once(pi, "pix-skills", () => {
66
- calls++;
67
- });
68
- expect(calls).toBe(1);
69
- });
70
-
71
- it("re-runs for a new pi instance with the same key (/new rehydration)", () => {
72
- const pi1 = {};
73
- const pi2 = {};
74
- let calls = 0;
75
- once(pi1, "pix-footer", () => {
76
- calls++;
77
- });
78
- // pi2 is a fresh instance (as built by Pi on /new, /resume, /fork, /reload).
79
- // The factory must run again — this is the rehydration guarantee.
80
- once(pi2, "pix-footer", () => {
81
- calls++;
82
- });
83
- expect(calls).toBe(2);
84
- });
85
-
86
- it("same key on two different pi instances dedupes within each instance", () => {
87
- const pi1 = {};
88
- const pi2 = {};
89
- let calls1 = 0;
90
- let calls2 = 0;
91
- once(pi1, "pix-welcome", () => {
92
- calls1++;
93
- });
94
- once(pi1, "pix-welcome", () => {
95
- calls1++;
96
- }); // second on pi1 → skip
97
- once(pi2, "pix-welcome", () => {
98
- calls2++;
99
- });
100
- once(pi2, "pix-welcome", () => {
101
- calls2++;
102
- }); // second on pi2 → skip
103
- expect(calls1).toBe(1); // deduped within pi1
104
- expect(calls2).toBe(1); // deduped within pi2
105
- });
106
- });
107
-
108
- // Behavior pin: prove a REAL guarded member dedupes when its factory runs
109
- // twice against the SAME pi — the exact scenario the aggregator creates when a
110
- // tool is installed both via pix-core (this boots it) and standalone (Pi boots
111
- // it again). The member must register its tool only once or Pi reports a
112
- // tool conflict.
113
- describe("member factory dedupe (pix-todo)", () => {
114
- function makeHost() {
115
- const toolNames: string[] = [];
116
- const pi = {
117
- registerTool(def: { name: string }) {
118
- toolNames.push(def.name);
119
- },
120
- appendEntry() {},
121
- on() {},
122
- } as never;
123
- return { pi, toolNames };
124
- }
125
-
126
- it("registers the tool once across core + standalone activation (same pi)", () => {
127
- const { pi, toolNames } = makeHost();
128
- // First pass: pix-core's aggregator invokes the member factory.
129
- registerTodo(pi);
130
- // Second pass: standalone install loads the same module and invokes it
131
- // again. The globalThis once() registry must suppress re-registration.
132
- registerTodo(pi);
133
- expect(toolNames).toEqual(["todo"]);
134
- });
135
-
136
- it("registers the tool again for a fresh pi instance (/new rehydration)", () => {
137
- const { pi: pi1, toolNames: tools1 } = makeHost();
138
- const { pi: pi2, toolNames: tools2 } = makeHost();
139
- // First session.
140
- registerTodo(pi1);
141
- // /new: a fresh pi is built; factory must run again.
142
- registerTodo(pi2);
143
- expect(tools1).toEqual(["todo"]);
144
- expect(tools2).toEqual(["todo"]);
145
- });
146
- });
147
-
148
- describe("member factory dedupe (pix-commands)", () => {
149
- function makeHost() {
150
- const commandNames: string[] = [];
151
- const pi = {
152
- registerCommand(name: string) {
153
- commandNames.push(name);
154
- },
155
- registerEntryRenderer() {},
156
- on() {},
157
- } as never;
158
- return { pi, commandNames };
159
- }
160
-
161
- it("registers /clear, /btw, /afk, and /yolo once across core + standalone activation", () => {
162
- const { pi, commandNames } = makeHost();
163
- registerCommands(pi);
164
- registerCommands(pi);
165
- expect(commandNames).toEqual(["clear", "btw", "afk", "yolo"]);
166
- });
167
-
168
- it("registers commands again for a fresh pi instance", () => {
169
- const first = makeHost();
170
- const second = makeHost();
171
- registerCommands(first.pi);
172
- registerCommands(second.pi);
173
- expect(first.commandNames).toEqual(["clear", "btw", "afk", "yolo"]);
174
- expect(second.commandNames).toEqual(["clear", "btw", "afk", "yolo"]);
175
- });
176
- });
177
-
178
- describe("member factory dedupe (pix-subagent)", () => {
179
- function makeHost() {
180
- const toolNames: string[] = [];
181
- const pi = {
182
- registerTool(def: { name: string }) {
183
- toolNames.push(def.name);
184
- },
185
- appendEntry() {},
186
- on() {},
187
- getAvailableAgentTypes() {
188
- return [];
189
- },
190
- getAvailableModels() {
191
- return [];
192
- },
193
- registerMessageRenderer() {},
194
- } as never;
195
- return { pi, toolNames };
196
- }
197
-
198
- it("registers agent tools once across core + standalone activation (same pi)", () => {
199
- const { pi, toolNames } = makeHost();
200
- registerSubagent(pi);
201
- registerSubagent(pi);
202
- expect(toolNames).toEqual(["agent_info", "agent", "agent_result", "agent_steer"]);
203
- });
204
-
205
- it("registers agent tools again for a fresh pi instance (/new rehydration)", () => {
206
- const { pi: pi1, toolNames: tools1 } = makeHost();
207
- const { pi: pi2, toolNames: tools2 } = makeHost();
208
- registerSubagent(pi1);
209
- registerSubagent(pi2);
210
- expect(tools1).toEqual(["agent_info", "agent", "agent_result", "agent_steer"]);
211
- expect(tools2).toEqual(["agent_info", "agent", "agent_result", "agent_steer"]);
212
- });
213
- });