@fiale-plus/pi-rogue 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 (68) hide show
  1. package/README.md +50 -0
  2. package/node_modules/@fiale-plus/pi-core/README.md +13 -0
  3. package/node_modules/@fiale-plus/pi-core/package.json +25 -0
  4. package/node_modules/@fiale-plus/pi-core/src/context-broker.ts +109 -0
  5. package/node_modules/@fiale-plus/pi-core/src/index.ts +5 -0
  6. package/node_modules/@fiale-plus/pi-core/src/paths.ts +36 -0
  7. package/node_modules/@fiale-plus/pi-core/src/risk.test.ts +129 -0
  8. package/node_modules/@fiale-plus/pi-core/src/risk.ts +97 -0
  9. package/node_modules/@fiale-plus/pi-core/src/storage.ts +39 -0
  10. package/node_modules/@fiale-plus/pi-core/src/text.test.ts +36 -0
  11. package/node_modules/@fiale-plus/pi-core/src/text.ts +14 -0
  12. package/node_modules/@fiale-plus/pi-rogue-advisor/README.md +59 -0
  13. package/node_modules/@fiale-plus/pi-rogue-advisor/advisor/index.ts +1 -0
  14. package/node_modules/@fiale-plus/pi-rogue-advisor/assets/binary-gate-model.json +24026 -0
  15. package/node_modules/@fiale-plus/pi-rogue-advisor/package.json +50 -0
  16. package/node_modules/@fiale-plus/pi-rogue-advisor/skills/advisor/SKILL.md +51 -0
  17. package/node_modules/@fiale-plus/pi-rogue-advisor/src/binary-gate-features.test.ts +19 -0
  18. package/node_modules/@fiale-plus/pi-rogue-advisor/src/binary-gate-features.ts +248 -0
  19. package/node_modules/@fiale-plus/pi-rogue-advisor/src/binary-gate.test.ts +66 -0
  20. package/node_modules/@fiale-plus/pi-rogue-advisor/src/completions.test.ts +28 -0
  21. package/node_modules/@fiale-plus/pi-rogue-advisor/src/completions.ts +79 -0
  22. package/node_modules/@fiale-plus/pi-rogue-advisor/src/extension.test.ts +364 -0
  23. package/node_modules/@fiale-plus/pi-rogue-advisor/src/extension.ts +1677 -0
  24. package/node_modules/@fiale-plus/pi-rogue-advisor/src/index.ts +3 -0
  25. package/node_modules/@fiale-plus/pi-rogue-advisor/src/internal.ts +63 -0
  26. package/node_modules/@fiale-plus/pi-rogue-advisor/src/loop-convergence.test.ts +512 -0
  27. package/node_modules/@fiale-plus/pi-rogue-advisor/src/preflight-signals.test.ts +22 -0
  28. package/node_modules/@fiale-plus/pi-rogue-advisor/src/preflight-signals.ts +21 -0
  29. package/node_modules/@fiale-plus/pi-rogue-advisor/src/router.test.ts +126 -0
  30. package/node_modules/@fiale-plus/pi-rogue-advisor/src/router.ts +580 -0
  31. package/node_modules/@fiale-plus/pi-rogue-advisor/src/state-versioning.test.ts +227 -0
  32. package/node_modules/@fiale-plus/pi-rogue-context-broker/README.md +53 -0
  33. package/node_modules/@fiale-plus/pi-rogue-context-broker/package.json +31 -0
  34. package/node_modules/@fiale-plus/pi-rogue-context-broker/src/extension.test.ts +749 -0
  35. package/node_modules/@fiale-plus/pi-rogue-context-broker/src/extension.ts +818 -0
  36. package/node_modules/@fiale-plus/pi-rogue-context-broker/src/file.ts +191 -0
  37. package/node_modules/@fiale-plus/pi-rogue-context-broker/src/index.test.ts +302 -0
  38. package/node_modules/@fiale-plus/pi-rogue-context-broker/src/index.ts +369 -0
  39. package/node_modules/@fiale-plus/pi-rogue-context-broker/src/sqlite.test.ts +122 -0
  40. package/node_modules/@fiale-plus/pi-rogue-context-broker/src/sqlite.ts +561 -0
  41. package/node_modules/@fiale-plus/pi-rogue-orchestration/README.md +56 -0
  42. package/node_modules/@fiale-plus/pi-rogue-orchestration/orchestration/index.ts +1 -0
  43. package/node_modules/@fiale-plus/pi-rogue-orchestration/package.json +44 -0
  44. package/node_modules/@fiale-plus/pi-rogue-orchestration/skills/orchestration/SKILL.md +44 -0
  45. package/node_modules/@fiale-plus/pi-rogue-orchestration/src/advisor-checkins.test.ts +142 -0
  46. package/node_modules/@fiale-plus/pi-rogue-orchestration/src/advisor-checkins.ts +102 -0
  47. package/node_modules/@fiale-plus/pi-rogue-orchestration/src/autoresearch-state.ts +70 -0
  48. package/node_modules/@fiale-plus/pi-rogue-orchestration/src/autoresearch.test.ts +143 -0
  49. package/node_modules/@fiale-plus/pi-rogue-orchestration/src/autoresearch.ts +139 -0
  50. package/node_modules/@fiale-plus/pi-rogue-orchestration/src/completions.test.ts +23 -0
  51. package/node_modules/@fiale-plus/pi-rogue-orchestration/src/completions.ts +53 -0
  52. package/node_modules/@fiale-plus/pi-rogue-orchestration/src/extension.ts +23 -0
  53. package/node_modules/@fiale-plus/pi-rogue-orchestration/src/goal-resolution.ts +36 -0
  54. package/node_modules/@fiale-plus/pi-rogue-orchestration/src/goal.test.ts +182 -0
  55. package/node_modules/@fiale-plus/pi-rogue-orchestration/src/goal.ts +232 -0
  56. package/node_modules/@fiale-plus/pi-rogue-orchestration/src/index.ts +1 -0
  57. package/node_modules/@fiale-plus/pi-rogue-orchestration/src/internal.ts +98 -0
  58. package/node_modules/@fiale-plus/pi-rogue-orchestration/src/loop.ts +274 -0
  59. package/node_modules/@fiale-plus/pi-rogue-orchestration/src/novelty-guard.test.ts +35 -0
  60. package/node_modules/@fiale-plus/pi-rogue-orchestration/src/novelty-guard.ts +145 -0
  61. package/node_modules/@fiale-plus/pi-rogue-orchestration/src/state.ts +24 -0
  62. package/package.json +51 -0
  63. package/src/context-broker-file.ts +1 -0
  64. package/src/context-broker-sqlite.ts +1 -0
  65. package/src/context-broker.ts +1 -0
  66. package/src/extension.test.ts +68 -0
  67. package/src/extension.ts +27 -0
  68. package/src/index.ts +1 -0
@@ -0,0 +1,227 @@
1
+ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
2
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
3
+ import { join, dirname } from "node:path";
4
+ import { homedir } from "node:os";
5
+ import { registerAdvisor } from "./extension.js";
6
+
7
+ const testHome = vi.hoisted(() => `/tmp/pi-rogue-advisor-state-versioning-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
8
+
9
+ vi.mock("node:os", async () => {
10
+ const actual = await vi.importActual<typeof import("node:os")>("node:os");
11
+ return { ...actual, homedir: () => testHome };
12
+ });
13
+
14
+ vi.mock("@earendil-works/pi-ai", async () => {
15
+ const actual = await vi.importActual<typeof import("@earendil-works/pi-ai")>("@earendil-works/pi-ai");
16
+ return { ...actual, completeSimple: vi.fn() };
17
+ });
18
+
19
+ type Handler = (event: any, ctx: any) => any;
20
+ type HandlerMap = Record<string, Handler[]>;
21
+ type CommandMap = Record<string, { handler: (args: string, ctx: any) => any }>;
22
+
23
+ function makeHandlers() {
24
+ const handlers: HandlerMap = {};
25
+ const commands: CommandMap = {};
26
+ const sendMessage = vi.fn();
27
+ const pi = {
28
+ on: (event: string, handler: Handler) => { handlers[event] ??= []; handlers[event].push(handler); },
29
+ registerMessageRenderer: () => undefined,
30
+ registerCommand: (name: string, command: { handler: (args: string, ctx: any) => any }) => { commands[name] = command; },
31
+ registerTool: vi.fn(),
32
+ sendMessage,
33
+ sendUserMessage: () => undefined,
34
+ ui: { setStatus: () => undefined, notify: () => undefined },
35
+ };
36
+ return { handlers, commands, pi: pi as any, sendMessage };
37
+ }
38
+
39
+ const ADVISOR_STATE_DIR = join(homedir(), ".pi", "agent", "pi-rogue", "advisor");
40
+ const ADVISOR_STATE_PATH = join(ADVISOR_STATE_DIR, "state.json");
41
+ const ADVISOR_CONFIG_PATH = join(ADVISOR_STATE_DIR, "config.json");
42
+
43
+ function readAdvisorState(): any {
44
+ return JSON.parse(readFileSync(ADVISOR_STATE_PATH, "utf8"));
45
+ }
46
+
47
+ function mkCtx() {
48
+ return {
49
+ sessionManager: {
50
+ getSessionFile: () => join(homedir(), ".pi", "agent", "pi-rogue", "advisor", "session.jsonl"),
51
+ },
52
+ isIdle: () => true,
53
+ modelRegistry: {
54
+ find: (provider: string, model: string) => {
55
+ if (provider === "openai-codex") return { id: `${provider}/${model}`, provider, input: ["text"] };
56
+ return null;
57
+ },
58
+ getAvailable: () => [{ id: "provider/text-light", provider: "provider", input: ["text"] }],
59
+ getApiKeyAndHeaders: async () => ({ ok: true, apiKey: "k", headers: {} }),
60
+ },
61
+ ui: { setStatus: () => undefined, notify: () => undefined },
62
+ } as any;
63
+ }
64
+
65
+ describe("state versioning and recovery", () => {
66
+ let priorState: string | null = null;
67
+ let priorConfig: string | null = null;
68
+
69
+ beforeEach(() => {
70
+ priorState = existsSync(ADVISOR_STATE_PATH) ? readFileSync(ADVISOR_STATE_PATH, "utf8") : null;
71
+ priorConfig = existsSync(ADVISOR_CONFIG_PATH) ? readFileSync(ADVISOR_CONFIG_PATH, "utf8") : null;
72
+
73
+ const setup = makeHandlers();
74
+ const { handlers, commands, pi } = setup;
75
+ handlers; commands;
76
+
77
+ mkdirSync(dirname(ADVISOR_STATE_PATH), { recursive: true });
78
+ writeFileSync(ADVISOR_CONFIG_PATH, JSON.stringify({ mode: "auto", review: "light", checkins: "off", checkinIntervalMinutes: 30 }, null, 2), "utf8");
79
+ writeFileSync(ADVISOR_STATE_PATH, JSON.stringify({
80
+ turns: 0,
81
+ lastTask: "",
82
+ notes: [],
83
+ files: [],
84
+ errors: [],
85
+ advisorCalls: 0,
86
+ cacheHits: 0,
87
+ followUp: "",
88
+ router: {},
89
+ checkin: { queued: false },
90
+ reviewControl: { status: "idle", pending: false, consumed: true, running: false },
91
+ }, null, 2), "utf8");
92
+
93
+ registerAdvisor(pi);
94
+ });
95
+
96
+ afterEach(() => {
97
+ if (priorState === null) {
98
+ writeFileSync(ADVISOR_STATE_PATH, "{}", "utf8");
99
+ } else {
100
+ writeFileSync(ADVISOR_STATE_PATH, priorState, "utf8");
101
+ }
102
+ if (priorConfig === null) {
103
+ writeFileSync(ADVISOR_CONFIG_PATH, "{}", "utf8");
104
+ } else {
105
+ writeFileSync(ADVISOR_CONFIG_PATH, priorConfig, "utf8");
106
+ }
107
+ });
108
+
109
+ it("loads state with version field and preserves existing data", () => {
110
+ // Write a state without _v to simulate old state
111
+ writeFileSync(ADVISOR_STATE_PATH, JSON.stringify({
112
+ turns: 0,
113
+ lastTask: "",
114
+ notes: [],
115
+ files: [],
116
+ errors: [],
117
+ advisorCalls: 0,
118
+ cacheHits: 0,
119
+ followUp: "",
120
+ router: {},
121
+ checkin: { queued: false },
122
+ reviewControl: { status: "idle", pending: false, consumed: true, running: false },
123
+ }, null, 2), "utf8");
124
+
125
+ // Load state directly (simulates what loadState does)
126
+ const raw = JSON.parse(readFileSync(ADVISOR_STATE_PATH, "utf8"));
127
+ expect(raw._v).toBeUndefined(); // Old state has no _v
128
+
129
+ // After loadState + saveState cycle, _v should be present
130
+ const setup = makeHandlers();
131
+ const { handlers: h, pi } = setup;
132
+ registerAdvisor(pi);
133
+ const ctx = mkCtx();
134
+ void h.session_start?.[0]?.({}, ctx);
135
+
136
+ const state = JSON.parse(readFileSync(ADVISOR_STATE_PATH, "utf8"));
137
+ // The session_start handler calls loadState() which adds _v, then saveState() writes it
138
+ expect(state._v).toBe(1);
139
+ expect(state.turns).toBe(0);
140
+ expect(state.lastTask).toBe("");
141
+ });
142
+
143
+ it("recovers corrupted state gracefully", () => {
144
+ // Write corrupted state
145
+ writeFileSync(ADVISOR_STATE_PATH, "{ corrupted json", "utf8");
146
+ const handlers = makeHandlers();
147
+ const { handlers: h, pi } = handlers;
148
+ registerAdvisor(pi);
149
+ // Loading state should not throw
150
+ const ctx = mkCtx();
151
+ void h.session_start?.[0]?.({}, ctx);
152
+ // State should be recovered to default
153
+ const recovered = readAdvisorState();
154
+ expect(recovered._v).toBe(1);
155
+ expect(recovered.turns).toBe(0);
156
+ });
157
+
158
+ it("migrates old state without _v field to current version", () => {
159
+ // Write state without _v field
160
+ writeFileSync(ADVISOR_STATE_PATH, JSON.stringify({
161
+ turns: 5,
162
+ lastTask: "old task",
163
+ notes: [],
164
+ files: [],
165
+ errors: [],
166
+ advisorCalls: 3,
167
+ cacheHits: 1,
168
+ followUp: "",
169
+ router: {},
170
+ checkin: { queued: false },
171
+ reviewControl: { status: "idle", pending: false, consumed: true, running: false },
172
+ }, null, 2), "utf8");
173
+
174
+ const handlers = makeHandlers();
175
+ const { handlers: h, pi } = handlers;
176
+ registerAdvisor(pi);
177
+ const ctx = mkCtx();
178
+ void h.session_start?.[0]?.({}, ctx);
179
+
180
+ const migrated = readAdvisorState();
181
+ expect(migrated._v).toBe(1);
182
+ expect(migrated.turns).toBe(5);
183
+ expect(migrated.lastTask).toBe("old task");
184
+ expect(migrated.advisorCalls).toBe(3);
185
+ });
186
+
187
+ it("handles missing state file by creating default", () => {
188
+ writeFileSync(ADVISOR_STATE_PATH, "{}", "utf8");
189
+ const handlers = makeHandlers();
190
+ const { handlers: h, pi } = handlers;
191
+ registerAdvisor(pi);
192
+ const ctx = mkCtx();
193
+ void h.session_start?.[0]?.({}, ctx);
194
+
195
+ const loaded = readAdvisorState();
196
+ expect(loaded._v).toBe(1);
197
+ expect(loaded.turns).toBe(0);
198
+ expect(loaded.reviewControl.status).toBe("idle");
199
+ });
200
+
201
+ it("preserves reviewControl state across loads", () => {
202
+ const state = readAdvisorState();
203
+ state.reviewControl = {
204
+ status: "needed",
205
+ pending: true,
206
+ consumed: false,
207
+ running: false,
208
+ lastDecision: "review",
209
+ lastMaterialSignature: "test-sig",
210
+ lastReason: "test reason",
211
+ lastTrigger: "turn-1",
212
+ lastAppliedAt: new Date().toISOString(),
213
+ };
214
+ writeFileSync(ADVISOR_STATE_PATH, JSON.stringify(state, null, 2), "utf8");
215
+
216
+ const handlers = makeHandlers();
217
+ const { handlers: h, pi } = handlers;
218
+ registerAdvisor(pi);
219
+ const ctx = mkCtx();
220
+ void h.session_start?.[0]?.({}, ctx);
221
+
222
+ const recovered = readAdvisorState();
223
+ expect(recovered.reviewControl.status).toBe("needed");
224
+ expect(recovered.reviewControl.pending).toBe(true);
225
+ expect(recovered.reviewControl.lastDecision).toBe("review");
226
+ });
227
+ });
@@ -0,0 +1,53 @@
1
+ # Pi-Rogue Context Broker
2
+
3
+ Beta context broker runtime for Pi-Rogue.
4
+
5
+ This package contains the executable in-memory bounded broker implementation:
6
+
7
+ - `createInMemoryContextBroker()` stores artifacts behind stable `ctx://...` handles.
8
+ - Lookups support handle, session, kind, tag, path, command prefix, branch, tier, and text filters.
9
+ - Omitted summaries become metadata-only placeholders, keeping raw payloads out of prompt briefs by default.
10
+ - Artifacts are classified as hot/warm/cold on publish; prompt briefs render hot first, warm second, and exclude cold unless explicitly queried.
11
+ - Pruning enforces per-session record/byte caps, optional global (cross-session) record/byte caps, tier-specific caps, TTL expiry on reads, and pinned-artifact retention.
12
+
13
+ It is registered by default in the bundle, with an explicit env kill switch.
14
+
15
+ ## Mainline extension
16
+
17
+ The bundle registers a `context_lookup` LLM tool plus `/context` commands by default. To disable the runtime for rollback:
18
+
19
+ ```bash
20
+ PI_CONTEXT_BROKER_ENABLED=false pi
21
+ ```
22
+
23
+ When active, the bundle registers:
24
+
25
+ - `/context status` — enabled state, record/byte counts, pinned counts, and routing telemetry.
26
+ - `/context brief` — bounded prompt-safe broker brief with handles and summaries.
27
+ - `/context lookup <handle|text>` — exact handle rehydration or current-session text search.
28
+ - `/context pin <handle>` — protect an artifact from normal TTL/cap pruning.
29
+ - `/context export <handle>` — write full payload to a temp file without dumping it into prompt.
30
+ - `/context prune` — run TTL/cap pruning immediately.
31
+
32
+ The command includes autocomplete for subcommands and known artifact handles. Exact handle lookup returns clipped payload text; text search returns a smaller clipped excerpt, and truncation is marked explicitly.
33
+
34
+ Optional durability is available with `PI_CONTEXT_BROKER_DURABLE=true` or `PI_CONTEXT_BROKER_STORE_DIR=/path/to/store`. Durable mode now defaults to SQLite (`artifacts.sqlite`) with an FTS index for text lookup, so exact handles, tier, and pin state survive restarts without replay reconstruction. Set `PI_CONTEXT_BROKER_BACKEND=jsonl` to use the legacy JSONL/blob backend.
35
+
36
+ - `PI_CONTEXT_BROKER_REWRITE_THRESHOLD_BYTES` controls when large `toolResult` / `bashExecution` payloads are rewritten in-context. The default is `0` (rewritten), so raw tool evidence is replaced by handles by default.
37
+
38
+ For quieter sessions, set `PI_CONTEXT_BROKER_REWRITE_THRESHOLD_BYTES` to a higher value to only rewrite larger outputs.
39
+
40
+
41
+ ## Session behavior and limits
42
+
43
+ - On session start/reload, the runtime backfills the current Pi session branch from `toolResult` and prompt-visible `bashExecution` entries.
44
+ - Backfill is idempotent by session entry id, skips malformed entries instead of failing the session, and honors Pi's `excludeFromContext` bash entries.
45
+ - Without durable mode, restarting Pi loses broker state until the current branch is backfilled again.
46
+ - Prompt integration injects a bounded, tier-aware broker brief and lookup guidance; the LLM also gets a `context_lookup` tool for exact handle dereferencing. Payloads that hit hostile-binary heuristics are represented in prompt as handles plus short guidance to export the full content.
47
+ - The `context` hook rewrites prompt-visible `toolResult` and `bashExecution` payloads in the LLM-bound message copy to broker handles and summaries, reducing prompt load while preserving exact `/context lookup` rehydration.
48
+ - Pi `excludeFromContext` bash entries are not backfilled or rewritten into broker prompts.
49
+ - Basic secret redaction runs before broker storage and display for common token/password/API-key patterns.
50
+ - Optional global caps can be configured via env vars:
51
+ - `PI_CONTEXT_BROKER_GLOBAL_MAX_RECORDS`
52
+ - `PI_CONTEXT_BROKER_GLOBAL_MAX_BYTES`
53
+ - Rollback is immediate: set `PI_CONTEXT_BROKER_ENABLED=false` and `/reload` or restart Pi. Disable durable writes by unsetting `PI_CONTEXT_BROKER_DURABLE` and `PI_CONTEXT_BROKER_STORE_DIR`.
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@fiale-plus/pi-rogue-context-broker",
3
+ "version": "0.1.0",
4
+ "description": "Context broker runtime for Pi-Rogue with bounded handle-first prompt payload storage.",
5
+ "private": true,
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "pi-package"
10
+ ],
11
+ "scripts": {
12
+ "check": "tsc -p ../../tsconfig.json --noEmit",
13
+ "test": "cd ../.. && vitest run packages/context-broker/src/*.test.ts"
14
+ },
15
+ "main": "./src/index.ts",
16
+ "exports": {
17
+ ".": "./src/index.ts",
18
+ "./extension": "./src/extension.ts",
19
+ "./file": "./src/file.ts",
20
+ "./sqlite": "./src/sqlite.ts"
21
+ },
22
+ "dependencies": {
23
+ "@fiale-plus/pi-core": "^0.1.0",
24
+ "typebox": "^1.1.24"
25
+ },
26
+ "files": [
27
+ "src",
28
+ "README.md",
29
+ "package.json"
30
+ ]
31
+ }