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.
Files changed (96) hide show
  1. package/README.md +12 -11
  2. package/package.json +2 -2
  3. package/schemas/role-manifest.schema.json +7 -1
  4. package/skills/pi-herdr-workflow/SKILL.md +4 -5
  5. package/src/ansi-text.ts +62 -11
  6. package/src/ask-dialog.ts +308 -0
  7. package/src/ask-user.ts +113 -112
  8. package/src/bootstrap.ts +8 -12
  9. package/src/compact-coordinator.ts +278 -261
  10. package/src/compact-economics-core.ts +77 -145
  11. package/src/config-catalog-core.ts +77 -197
  12. package/src/config-command.ts +271 -64
  13. package/src/dashboard-command.ts +38 -19
  14. package/src/efficiency-config-core.ts +287 -484
  15. package/src/efficiency-store.ts +16 -29
  16. package/src/focus-poller.ts +14 -35
  17. package/src/gc-core.ts +10 -36
  18. package/src/herdr-client.ts +82 -208
  19. package/src/history-store.ts +36 -23
  20. package/src/index-gates.ts +124 -0
  21. package/src/index-locks.ts +2 -7
  22. package/src/index-master.ts +24 -18
  23. package/src/index-notices.ts +28 -15
  24. package/src/index-pipe.ts +17 -12
  25. package/src/index-roles.ts +306 -0
  26. package/src/index-runtime.ts +2 -6
  27. package/src/index-worker.ts +4 -8
  28. package/src/index.ts +305 -564
  29. package/src/jev-client.ts +12 -17
  30. package/src/jev-core.ts +137 -87
  31. package/src/ledger.ts +13 -21
  32. package/src/lock-core.ts +28 -56
  33. package/src/manifest-compose.ts +35 -20
  34. package/src/observation-core.ts +52 -100
  35. package/src/pane-title.ts +19 -38
  36. package/src/pi-surface.ts +34 -22
  37. package/src/pier-options.ts +54 -50
  38. package/src/pipe-channel.ts +27 -59
  39. package/src/platform-paths.ts +19 -52
  40. package/src/{core → plugins}/grid-shape.ts +8 -10
  41. package/src/{core → plugins}/heat-plan.ts +20 -22
  42. package/src/plugins/observation.ts +475 -0
  43. package/src/plugins/subagent.ts +728 -0
  44. package/src/{core → plugins}/terminal.ts +167 -127
  45. package/src/{core → plugins}/todo.ts +61 -100
  46. package/src/progress-core.ts +16 -53
  47. package/src/reconcile-core.ts +24 -33
  48. package/src/reducer-core.ts +24 -60
  49. package/src/reducer-invoker.ts +266 -239
  50. package/src/renderers.ts +76 -87
  51. package/src/role-loader.ts +33 -28
  52. package/src/role-manifest.ts +23 -28
  53. package/src/role-state.ts +119 -0
  54. package/src/routing-telemetry.ts +136 -0
  55. package/src/runtime-policy.ts +25 -40
  56. package/src/session-tail.ts +65 -41
  57. package/src/settle-wake-core.ts +52 -21
  58. package/src/slim-frame.ts +25 -122
  59. package/src/stale-core.ts +17 -24
  60. package/src/storage-layout.ts +14 -43
  61. package/src/subagent-core.ts +253 -118
  62. package/src/subagent-gc.ts +109 -67
  63. package/src/subagent-poller.ts +367 -24
  64. package/src/subagent-session.ts +516 -0
  65. package/src/subagent-spawn.ts +530 -52
  66. package/src/swallow.ts +7 -17
  67. package/src/terminal-core.ts +125 -106
  68. package/src/todo-core.ts +57 -109
  69. package/src/todo-read-hook.ts +23 -39
  70. package/src/todo-reminder-core.ts +26 -51
  71. package/src/todo-window.ts +11 -12
  72. package/src/todos-service.ts +15 -35
  73. package/src/tool-error.ts +6 -13
  74. package/src/tool-gate.ts +38 -21
  75. package/src/vocab.ts +52 -25
  76. package/src/ask-multi.ts +0 -260
  77. package/src/config-guide.ts +0 -361
  78. package/src/core/observation.ts +0 -555
  79. package/src/core/subagent.ts +0 -633
  80. package/src/efficiency-config-io.ts +0 -53
  81. package/src/git-adapter.ts +0 -113
  82. package/src/notice-buffer.ts +0 -23
  83. package/src/subagent-execute.ts +0 -328
  84. package/src/subagent-git-io.ts +0 -80
  85. package/src/subagent-launch.ts +0 -114
  86. package/src/subagent-list-action.ts +0 -69
  87. package/src/subagent-output-action.ts +0 -104
  88. package/src/subagent-output-adapter.ts +0 -27
  89. package/src/subagent-output-core.ts +0 -263
  90. package/src/subagent-poll-loop.ts +0 -305
  91. package/src/subagent-port.ts +0 -27
  92. package/src/subagent-ready.ts +0 -89
  93. package/src/subagent-registry.ts +0 -70
  94. package/src/subagent-resolution.ts +0 -41
  95. package/src/subagent-scope.ts +0 -41
  96. package/src/subagent-session-io.ts +0 -230
@@ -1,31 +1,21 @@
1
- /**
2
- * D102 Evidence-Preserving Reducer Core.
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
- export const MAX_EVIDENCE_ITEMS = 12;
16
- export const MAX_QUOTE_CHARS = 600;
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
- * Diagnostic (test/build) command gate for EPR.
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
- * Precision comes from the VALUE shape, not the keyword: after the separator the
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 truncation notice.
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
- * Pi appends one of these to a large bash tool result:
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
- * the VALUE masked to its length — the keyword and separator are not secret and
108
- * identify the false-positive shape (test name vs JSON key vs real credential);
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
- * Reducer models frequently wrap their JSON in a Markdown fence even when told
125
- * not to; strip it before parsing (2026-09-17 trial: 3/8 workbench attempts
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
- export function isRecord(v: unknown): v is Record<string, unknown> {
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
- export function lineNumberOf(body: string, quote: string): number | undefined {
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
- * pi persists a tool result's `usage` into the session and renders it in the footer through
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 {