pi-pier 0.3.0 → 0.4.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 +16 -12
- package/package.json +3 -3
- 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 +10 -13
- package/src/compact-coordinator.ts +279 -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/cordis-services.ts +64 -0
- package/src/dashboard-command.ts +77 -33
- package/src/delegation-core.ts +115 -0
- package/src/efficiency-config-core.ts +286 -486
- package/src/efficiency-store.ts +16 -29
- package/src/gc-core.ts +10 -36
- package/src/guards.ts +6 -0
- package/src/herdr-client.ts +202 -233
- package/src/history-store.ts +38 -23
- package/src/index-gates.ts +118 -0
- package/src/index-locks.ts +2 -7
- package/src/index-master.ts +31 -29
- package/src/index-notices.ts +28 -15
- package/src/index-pipe.ts +31 -12
- package/src/index-roles.ts +306 -0
- package/src/index-runtime.ts +16 -6
- package/src/index-worker.ts +8 -18
- package/src/index.ts +561 -570
- package/src/jev-client.ts +12 -17
- package/src/jev-core.ts +138 -90
- 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/panel-open.ts +47 -0
- package/src/pi-surface.ts +34 -22
- package/src/pier-options.ts +53 -50
- package/src/pipe-channel.ts +44 -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 +766 -0
- package/src/{core → plugins}/terminal.ts +198 -139
- package/src/{core → plugins}/todo.ts +73 -134
- package/src/progress-core.ts +16 -53
- package/src/reconcile-core.ts +24 -33
- package/src/reducer-core.ts +26 -64
- package/src/reducer-invoker.ts +266 -239
- package/src/renderers.ts +108 -84
- package/src/role-loader.ts +33 -28
- package/src/role-manifest.ts +25 -31
- package/src/role-state.ts +119 -0
- package/src/roles/worker-default.json +3 -3
- 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 +61 -22
- 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 +423 -120
- package/src/subagent-gc.ts +109 -67
- package/src/subagent-poller.ts +383 -24
- package/src/subagent-session.ts +516 -0
- package/src/subagent-spawn.ts +577 -52
- package/src/swallow.ts +7 -17
- package/src/terminal-core.ts +187 -134
- package/src/todo-core.ts +57 -109
- package/src/todo-read-hook.ts +46 -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/tree-view.ts +162 -0
- package/src/vocab.ts +52 -25
- package/src/worker-marker.ts +76 -0
- 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/focus-poller.ts +0 -244
- 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,22 @@
|
|
|
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). */
|
|
3
|
+
import { isPlainObject } from './guards.ts';
|
|
9
4
|
|
|
10
5
|
import { createHash } from 'node:crypto';
|
|
11
6
|
|
|
12
7
|
export const REDUCER_RECEIPT_SCHEMA = 'sol-pi-evidence-receipt/1' as const;
|
|
13
8
|
export const REDUCER_RECEIPT_PREFIX = 'sol_pi_evidence_receipt_v1' as const;
|
|
14
9
|
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
const MAX_EVIDENCE_ITEMS = 12;
|
|
11
|
+
const MAX_QUOTE_CHARS = 600;
|
|
17
12
|
|
|
18
13
|
export const DEFAULT_MIN_BYTES = 4096;
|
|
19
14
|
export const DEFAULT_MAX_CHARS = 600_000;
|
|
20
15
|
export const DEFAULT_MAX_OUTPUT_TOKENS = 2048;
|
|
21
16
|
export const DEFAULT_TIMEOUT_MS = 5000;
|
|
22
17
|
|
|
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
|
-
*/
|
|
18
|
+
/** EPR diagnostic (test/build) gate: any shell separator matches on either side, so subshells and
|
|
19
|
+
* chains like `(npm test)`/`npm test&&echo ok`/`pytest;` still match, while `makefile`/`coqtop` must not. */
|
|
29
20
|
export const DIAGNOSTIC_COMMAND =
|
|
30
21
|
/(?:^|[;&|()\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
22
|
|
|
@@ -33,14 +24,9 @@ export const FAILURE_SIGNAL =
|
|
|
33
24
|
/error|failed|failure|fatal|exception|panic|timeout|unsolved|type mismatch|assert/i;
|
|
34
25
|
|
|
35
26
|
/**
|
|
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.
|
|
27
|
+
* Credential heuristics for the EPR fail-open gate: precision comes from the VALUE shape, not the
|
|
28
|
+
* keyword — after the separator the text must carry a token-looking run (a known credential prefix
|
|
29
|
+
* such as sk-, ghp_, github_pat_, xox*, AKIA or eyJ, or >= 15 contiguous token characters with a digit).
|
|
44
30
|
*/
|
|
45
31
|
export const LIKELY_SECRET =
|
|
46
32
|
/(?: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 +59,11 @@ export function isDiagnosticCommand(command: string): boolean {
|
|
|
73
59
|
}
|
|
74
60
|
|
|
75
61
|
/**
|
|
76
|
-
* Recover the untruncated-log path from Pi's inline
|
|
77
|
-
*
|
|
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]`
|
|
62
|
+
* Recover the untruncated-log path from Pi's inline notice (`[Output truncated. Full output: …]`,
|
|
63
|
+
* `[Showing lines 1-2000 of 5000. Full output: …]`): a replayed or re-shaped event can keep only that
|
|
64
|
+
* text, and verifying a receipt against a truncated preview would be wrong.
|
|
82
65
|
*
|
|
83
|
-
*
|
|
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).
|
|
66
|
+
* The returned path is NOT trusted: callers must still gate it (see `readBashFullOutput`).
|
|
89
67
|
*/
|
|
90
68
|
export function fullOutputPathFromNotice(text: string): string | undefined {
|
|
91
69
|
// Only a bracketed notice counts, and Pi appends it at the very end — so take the LAST match.
|
|
@@ -103,10 +81,9 @@ export function containsLikelySecret(text: string): boolean {
|
|
|
103
81
|
}
|
|
104
82
|
|
|
105
83
|
/**
|
|
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).
|
|
84
|
+
* First matched credential-shaped snippet for reducer.jsonl fallback rows, with the VALUE masked to
|
|
85
|
+
* its length: the keyword and separator identify the false-positive shape (test name vs JSON key vs
|
|
86
|
+
* real credential) while the value never enters telemetry.
|
|
110
87
|
*/
|
|
111
88
|
export function extractLikelySecretMatch(text: string): string | undefined {
|
|
112
89
|
const match = LIKELY_SECRET.exec(text);
|
|
@@ -120,22 +97,16 @@ export function extractLikelySecretMatch(text: string): string | undefined {
|
|
|
120
97
|
return `${snippet.slice(0, sep + 1)} <redacted:${value.length} chars,sha8=${sha256Hex(value).slice(0, 8)}>`;
|
|
121
98
|
}
|
|
122
99
|
|
|
123
|
-
/**
|
|
124
|
-
*
|
|
125
|
-
|
|
126
|
-
* failed as invalid-json, same command as successes).
|
|
127
|
-
*/
|
|
128
|
-
export function stripReceiptJsonFences(raw: string): string {
|
|
100
|
+
/** Reducer models frequently wrap their JSON in a Markdown fence even when told not to; strip it
|
|
101
|
+
* before parsing, or the receipt is rejected as invalid-json. */
|
|
102
|
+
function stripReceiptJsonFences(raw: string): string {
|
|
129
103
|
const trimmed = raw.trim();
|
|
130
104
|
const fenced = /^```[a-zA-Z0-9_-]*\s*\n([\s\S]*?)\n?```$/.exec(trimmed);
|
|
131
105
|
return fenced?.[1]?.trim() ?? trimmed;
|
|
132
106
|
}
|
|
133
107
|
|
|
134
|
-
export function isRecord(v: unknown): v is Record<string, unknown> {
|
|
135
|
-
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
136
|
-
}
|
|
137
108
|
|
|
138
|
-
|
|
109
|
+
function lineNumberOf(body: string, quote: string): number | undefined {
|
|
139
110
|
const index = body.indexOf(quote);
|
|
140
111
|
if (index < 0) return undefined;
|
|
141
112
|
let line = 1;
|
|
@@ -182,9 +153,6 @@ export function reducerInputPrompt(opts: {
|
|
|
182
153
|
].join('\n');
|
|
183
154
|
}
|
|
184
155
|
|
|
185
|
-
/**
|
|
186
|
-
* Validates receipt byte for byte against the actual source text.
|
|
187
|
-
*/
|
|
188
156
|
export function validateReceipt(
|
|
189
157
|
rawJson: string,
|
|
190
158
|
sourceHash: string,
|
|
@@ -198,7 +166,7 @@ export function validateReceipt(
|
|
|
198
166
|
return { ok: false, reason: 'invalid-json' };
|
|
199
167
|
}
|
|
200
168
|
|
|
201
|
-
if (!
|
|
169
|
+
if (!isPlainObject(parsed)) {
|
|
202
170
|
return { ok: false, reason: 'schema-mismatch' };
|
|
203
171
|
}
|
|
204
172
|
|
|
@@ -219,7 +187,7 @@ export function validateReceipt(
|
|
|
219
187
|
const seen = new Set<string>();
|
|
220
188
|
|
|
221
189
|
for (const item of parsed.evidence) {
|
|
222
|
-
if (!
|
|
190
|
+
if (!isPlainObject(item)) {
|
|
223
191
|
return { ok: false, reason: 'schema-mismatch' };
|
|
224
192
|
}
|
|
225
193
|
const kind = item.kind;
|
|
@@ -310,14 +278,9 @@ export function formatReceiptText(opts: {
|
|
|
310
278
|
}
|
|
311
279
|
|
|
312
280
|
/**
|
|
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.
|
|
281
|
+
* Usage shapes accepted from / returned to pi: `addUsageToTotals` reads `usage.cost.total` WITHOUT a
|
|
282
|
+
* guard, so a partial object crashes the whole pi process. Always emit the complete `UsageTotals`
|
|
283
|
+
* below; see docs/session-format.md.
|
|
321
284
|
*/
|
|
322
285
|
export interface UsageLike {
|
|
323
286
|
input?: number;
|
|
@@ -338,7 +301,6 @@ export interface UsageTotals {
|
|
|
338
301
|
cost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };
|
|
339
302
|
}
|
|
340
303
|
|
|
341
|
-
/** Sum two (possibly partial) usage records into one complete `UsageTotals`. */
|
|
342
304
|
export function mergeUsage(a?: UsageLike, b?: UsageLike): UsageTotals {
|
|
343
305
|
const sum = (x?: number, y?: number): number => (x ?? 0) + (y ?? 0);
|
|
344
306
|
return {
|