pi-pier 0.3.0 → 0.3.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/README.md +12 -11
- package/package.json +2 -2
- package/schemas/role-manifest.schema.json +7 -1
- package/skills/pi-herdr-workflow/SKILL.md +4 -5
- package/src/ansi-text.ts +62 -11
- package/src/ask-dialog.ts +308 -0
- package/src/ask-user.ts +113 -112
- package/src/bootstrap.ts +8 -12
- package/src/compact-coordinator.ts +278 -261
- package/src/compact-economics-core.ts +77 -145
- package/src/config-catalog-core.ts +77 -197
- package/src/config-command.ts +271 -64
- package/src/dashboard-command.ts +38 -19
- package/src/efficiency-config-core.ts +287 -484
- package/src/efficiency-store.ts +16 -29
- package/src/focus-poller.ts +14 -35
- package/src/gc-core.ts +10 -36
- package/src/herdr-client.ts +82 -208
- package/src/history-store.ts +36 -23
- package/src/index-gates.ts +124 -0
- package/src/index-locks.ts +2 -7
- package/src/index-master.ts +24 -18
- package/src/index-notices.ts +28 -15
- package/src/index-pipe.ts +17 -12
- package/src/index-roles.ts +306 -0
- package/src/index-runtime.ts +2 -6
- package/src/index-worker.ts +4 -8
- package/src/index.ts +305 -564
- package/src/jev-client.ts +12 -17
- package/src/jev-core.ts +137 -87
- package/src/ledger.ts +13 -21
- package/src/lock-core.ts +28 -56
- package/src/manifest-compose.ts +35 -20
- package/src/observation-core.ts +52 -100
- package/src/pane-title.ts +19 -38
- package/src/pi-surface.ts +34 -22
- package/src/pier-options.ts +54 -50
- package/src/pipe-channel.ts +27 -59
- package/src/platform-paths.ts +19 -52
- package/src/{core → plugins}/grid-shape.ts +8 -10
- package/src/{core → plugins}/heat-plan.ts +20 -22
- package/src/plugins/observation.ts +475 -0
- package/src/plugins/subagent.ts +728 -0
- package/src/{core → plugins}/terminal.ts +167 -127
- package/src/{core → plugins}/todo.ts +61 -100
- package/src/progress-core.ts +16 -53
- package/src/reconcile-core.ts +24 -33
- package/src/reducer-core.ts +24 -60
- package/src/reducer-invoker.ts +266 -239
- package/src/renderers.ts +76 -87
- package/src/role-loader.ts +33 -28
- package/src/role-manifest.ts +23 -28
- package/src/role-state.ts +119 -0
- package/src/routing-telemetry.ts +136 -0
- package/src/runtime-policy.ts +25 -40
- package/src/session-tail.ts +65 -41
- package/src/settle-wake-core.ts +52 -21
- package/src/slim-frame.ts +25 -122
- package/src/stale-core.ts +17 -24
- package/src/storage-layout.ts +14 -43
- package/src/subagent-core.ts +253 -118
- package/src/subagent-gc.ts +109 -67
- package/src/subagent-poller.ts +367 -24
- package/src/subagent-session.ts +516 -0
- package/src/subagent-spawn.ts +530 -52
- package/src/swallow.ts +7 -17
- package/src/terminal-core.ts +125 -106
- package/src/todo-core.ts +57 -109
- package/src/todo-read-hook.ts +23 -39
- package/src/todo-reminder-core.ts +26 -51
- package/src/todo-window.ts +11 -12
- package/src/todos-service.ts +15 -35
- package/src/tool-error.ts +6 -13
- package/src/tool-gate.ts +38 -21
- package/src/vocab.ts +52 -25
- package/src/ask-multi.ts +0 -260
- package/src/config-guide.ts +0 -361
- package/src/core/observation.ts +0 -555
- package/src/core/subagent.ts +0 -633
- package/src/efficiency-config-io.ts +0 -53
- package/src/git-adapter.ts +0 -113
- package/src/notice-buffer.ts +0 -23
- package/src/subagent-execute.ts +0 -328
- package/src/subagent-git-io.ts +0 -80
- package/src/subagent-launch.ts +0 -114
- package/src/subagent-list-action.ts +0 -69
- package/src/subagent-output-action.ts +0 -104
- package/src/subagent-output-adapter.ts +0 -27
- package/src/subagent-output-core.ts +0 -263
- package/src/subagent-poll-loop.ts +0 -305
- package/src/subagent-port.ts +0 -27
- package/src/subagent-ready.ts +0 -89
- package/src/subagent-registry.ts +0 -70
- package/src/subagent-resolution.ts +0 -41
- package/src/subagent-scope.ts +0 -41
- package/src/subagent-session-io.ts +0 -230
package/src/reducer-core.ts
CHANGED
|
@@ -1,31 +1,21 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Pure algorithmic functions for diagnostic command recognition, secret filtering,
|
|
5
|
-
* receipt schema checking, and byte-for-byte quote verification.
|
|
6
|
-
*
|
|
7
|
-
* No I/O in this core; storage I/O lives in efficiency-store.ts.
|
|
8
|
-
*/
|
|
1
|
+
/** D102 Evidence-Preserving Reducer core: diagnostic-command recognition, secret filtering, receipt
|
|
2
|
+
* schema checking, byte-for-byte quote verification (storage I/O lives in efficiency-store.ts). */
|
|
9
3
|
|
|
10
4
|
import { createHash } from 'node:crypto';
|
|
11
5
|
|
|
12
6
|
export const REDUCER_RECEIPT_SCHEMA = 'sol-pi-evidence-receipt/1' as const;
|
|
13
7
|
export const REDUCER_RECEIPT_PREFIX = 'sol_pi_evidence_receipt_v1' as const;
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
const MAX_EVIDENCE_ITEMS = 12;
|
|
10
|
+
const MAX_QUOTE_CHARS = 600;
|
|
17
11
|
|
|
18
12
|
export const DEFAULT_MIN_BYTES = 4096;
|
|
19
13
|
export const DEFAULT_MAX_CHARS = 600_000;
|
|
20
14
|
export const DEFAULT_MAX_OUTPUT_TOKENS = 2048;
|
|
21
15
|
export const DEFAULT_TIMEOUT_MS = 5000;
|
|
22
16
|
|
|
23
|
-
/**
|
|
24
|
-
*
|
|
25
|
-
* Boundaries accept any shell separator on both sides so that subshells and chains such as
|
|
26
|
-
* `(npm test)`, `npm test&&echo ok` or `pytest;` are still recognized; a word character
|
|
27
|
-
* after the keyword (`makefile`, `coqtop`, `npm run test`) must NOT match.
|
|
28
|
-
*/
|
|
17
|
+
/** EPR diagnostic (test/build) gate: any shell separator matches on either side, so subshells and
|
|
18
|
+
* chains like `(npm test)`/`npm test&&echo ok`/`pytest;` still match, while `makefile`/`coqtop` must not. */
|
|
29
19
|
export const DIAGNOSTIC_COMMAND =
|
|
30
20
|
/(?:^|[;&|()\s])(?:lake\s+build|lake\s+env\s+lean|lean|coq|cargo(?:\s+(?:build|test|check))?|zig\s+build|pytest|python(?:3)?\s+-m\s+(?:pytest|unittest|py_compile)|ctest|cmake\s+--build|ninja|make|npm\s+test|pnpm\s+test|yarn\s+test|go\s+test|bazel\s+test|node\s+--test|npx\s+tsx\s+--test|vitest|jest|mvn|mvnw|gradle|gradlew)(?:[;&|()\s]|$)/i;
|
|
31
21
|
|
|
@@ -33,14 +23,9 @@ export const FAILURE_SIGNAL =
|
|
|
33
23
|
/error|failed|failure|fatal|exception|panic|timeout|unsolved|type mismatch|assert/i;
|
|
34
24
|
|
|
35
25
|
/**
|
|
36
|
-
* Credential heuristics for the EPR fail-open gate
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* text must contain a token-looking run — a known credential prefix (sk-, ghp_,
|
|
40
|
-
* github_pat_, xox*, AKIA, eyJ) or >= 15 contiguous token characters containing
|
|
41
|
-
* a digit. Keywords alone matched test names (`containsLikelySecret: detects …`),
|
|
42
|
-
* JSON keys (`"Authorization":[]`), URL paths and function calls, which blocked
|
|
43
|
-
* pier's own `npm test` output 7/7 times during the 2026-09-17 trial.
|
|
26
|
+
* Credential heuristics for the EPR fail-open gate: precision comes from the VALUE shape, not the
|
|
27
|
+
* keyword — after the separator the text must carry a token-looking run (a known credential prefix
|
|
28
|
+
* such as sk-, ghp_, github_pat_, xox*, AKIA or eyJ, or >= 15 contiguous token characters with a digit).
|
|
44
29
|
*/
|
|
45
30
|
export const LIKELY_SECRET =
|
|
46
31
|
/(?:api[_-]?key|api[_-]?secret|access[_-]?token|refresh[_-]?token|secret[_-]?key|client[_-]?secret|authorization|bearer|password|passwd|secret)[^\n]{0,40}[=:][\s"']*(?:bearer\s+|basic\s+)?[\s"']*(?:(?:sk-|ghp_|github_pat_|xox[baprs]-|AKIA|eyJ)[A-Za-z0-9_-]{10,}|(?=[A-Za-z0-9_\-.+~]*[0-9])[A-Za-z0-9_\-.+~]{15,})/i;
|
|
@@ -73,19 +58,11 @@ export function isDiagnosticCommand(command: string): boolean {
|
|
|
73
58
|
}
|
|
74
59
|
|
|
75
60
|
/**
|
|
76
|
-
* Recover the untruncated-log path from Pi's inline
|
|
61
|
+
* Recover the untruncated-log path from Pi's inline notice (`[Output truncated. Full output: …]`,
|
|
62
|
+
* `[Showing lines 1-2000 of 5000. Full output: …]`): a replayed or re-shaped event can keep only that
|
|
63
|
+
* text, and verifying a receipt against a truncated preview would be wrong.
|
|
77
64
|
*
|
|
78
|
-
*
|
|
79
|
-
* `[Output truncated. Full output: /tmp/pi-bash-abc.log]`
|
|
80
|
-
* `[Showing lines 1-2000 of 5000. Full output: /tmp/pi-bash-abc.log]`
|
|
81
|
-
* `[Showing last 50KB of line 12 (line is 80KB). Full output: /tmp/pi-bash-abc.log]`
|
|
82
|
-
*
|
|
83
|
-
* `details.fullOutputPath` carries the same value, but a replayed or re-shaped event can keep
|
|
84
|
-
* only the text. Recovering the path there lets the receipt be verified against the exact bytes
|
|
85
|
-
* the command produced instead of a truncated preview.
|
|
86
|
-
*
|
|
87
|
-
* The returned path is NOT trusted: callers must still gate it (see `readBashFullOutput`, which
|
|
88
|
-
* requires a `pi-bash-*.log` basename resolving to a regular non-symlink file inside tmpdir).
|
|
65
|
+
* The returned path is NOT trusted: callers must still gate it (see `readBashFullOutput`).
|
|
89
66
|
*/
|
|
90
67
|
export function fullOutputPathFromNotice(text: string): string | undefined {
|
|
91
68
|
// Only a bracketed notice counts, and Pi appends it at the very end — so take the LAST match.
|
|
@@ -103,10 +80,9 @@ export function containsLikelySecret(text: string): boolean {
|
|
|
103
80
|
}
|
|
104
81
|
|
|
105
82
|
/**
|
|
106
|
-
* First matched credential-shaped snippet for reducer.jsonl fallback rows, with
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* the value never enters telemetry (contract pinned by reducer-integration test).
|
|
83
|
+
* First matched credential-shaped snippet for reducer.jsonl fallback rows, with the VALUE masked to
|
|
84
|
+
* its length: the keyword and separator identify the false-positive shape (test name vs JSON key vs
|
|
85
|
+
* real credential) while the value never enters telemetry.
|
|
110
86
|
*/
|
|
111
87
|
export function extractLikelySecretMatch(text: string): string | undefined {
|
|
112
88
|
const match = LIKELY_SECRET.exec(text);
|
|
@@ -120,22 +96,19 @@ export function extractLikelySecretMatch(text: string): string | undefined {
|
|
|
120
96
|
return `${snippet.slice(0, sep + 1)} <redacted:${value.length} chars,sha8=${sha256Hex(value).slice(0, 8)}>`;
|
|
121
97
|
}
|
|
122
98
|
|
|
123
|
-
/**
|
|
124
|
-
*
|
|
125
|
-
|
|
126
|
-
* failed as invalid-json, same command as successes).
|
|
127
|
-
*/
|
|
128
|
-
export function stripReceiptJsonFences(raw: string): string {
|
|
99
|
+
/** Reducer models frequently wrap their JSON in a Markdown fence even when told not to; strip it
|
|
100
|
+
* before parsing, or the receipt is rejected as invalid-json. */
|
|
101
|
+
function stripReceiptJsonFences(raw: string): string {
|
|
129
102
|
const trimmed = raw.trim();
|
|
130
103
|
const fenced = /^```[a-zA-Z0-9_-]*\s*\n([\s\S]*?)\n?```$/.exec(trimmed);
|
|
131
104
|
return fenced?.[1]?.trim() ?? trimmed;
|
|
132
105
|
}
|
|
133
106
|
|
|
134
|
-
|
|
107
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
135
108
|
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
136
109
|
}
|
|
137
110
|
|
|
138
|
-
|
|
111
|
+
function lineNumberOf(body: string, quote: string): number | undefined {
|
|
139
112
|
const index = body.indexOf(quote);
|
|
140
113
|
if (index < 0) return undefined;
|
|
141
114
|
let line = 1;
|
|
@@ -182,9 +155,6 @@ export function reducerInputPrompt(opts: {
|
|
|
182
155
|
].join('\n');
|
|
183
156
|
}
|
|
184
157
|
|
|
185
|
-
/**
|
|
186
|
-
* Validates receipt byte for byte against the actual source text.
|
|
187
|
-
*/
|
|
188
158
|
export function validateReceipt(
|
|
189
159
|
rawJson: string,
|
|
190
160
|
sourceHash: string,
|
|
@@ -310,14 +280,9 @@ export function formatReceiptText(opts: {
|
|
|
310
280
|
}
|
|
311
281
|
|
|
312
282
|
/**
|
|
313
|
-
* Usage shapes accepted from / returned to pi.
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
* `addUsageToTotals`, which reads `usage.cost.total` WITHOUT a guard. Returning a partial object
|
|
317
|
-
* (for example only `{ input, output, totalTokens }`) therefore crashes the whole pi process with
|
|
318
|
-
* "TypeError: Cannot read properties of undefined (reading 'total')" — observed 2026-09-13 in a
|
|
319
|
-
* subagent pane (stack: FooterComponent.render -> addUsageToTotals). pi's own definition lives in
|
|
320
|
-
* docs/session-format.md; always emit the complete `UsageTotals` below.
|
|
283
|
+
* Usage shapes accepted from / returned to pi: `addUsageToTotals` reads `usage.cost.total` WITHOUT a
|
|
284
|
+
* guard, so a partial object crashes the whole pi process. Always emit the complete `UsageTotals`
|
|
285
|
+
* below; see docs/session-format.md.
|
|
321
286
|
*/
|
|
322
287
|
export interface UsageLike {
|
|
323
288
|
input?: number;
|
|
@@ -338,7 +303,6 @@ export interface UsageTotals {
|
|
|
338
303
|
cost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };
|
|
339
304
|
}
|
|
340
305
|
|
|
341
|
-
/** Sum two (possibly partial) usage records into one complete `UsageTotals`. */
|
|
342
306
|
export function mergeUsage(a?: UsageLike, b?: UsageLike): UsageTotals {
|
|
343
307
|
const sum = (x?: number, y?: number): number => (x ?? 0) + (y ?? 0);
|
|
344
308
|
return {
|