pi-harness-runtime 1.1.394 → 1.1.395

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 (214) hide show
  1. package/AGENTS.md +125 -3
  2. package/harness/glm-quota-logger.ts +17 -0
  3. package/harness/langchain/planner-logger.ts +15 -0
  4. package/harness/service-diagnostics.ts +32 -1
  5. package/index.ts +92 -3
  6. package/package.json +1 -1
  7. package/packages/a2a-adapter/package.json +1 -1
  8. package/packages/agent-policy/package.json +1 -1
  9. package/packages/architecture-generator/package.json +1 -1
  10. package/packages/auth/package.json +1 -1
  11. package/packages/autonomous-refactor/package.json +1 -1
  12. package/packages/autonomous-runtime/package.json +1 -1
  13. package/packages/cache-strategy/package.json +1 -1
  14. package/packages/capability-registry/package.json +1 -1
  15. package/packages/checkpoint/package.json +1 -1
  16. package/packages/cli-plugin-sdk/package.json +1 -1
  17. package/packages/clipboard/package.json +1 -1
  18. package/packages/clipboard-plugin/package.json +1 -1
  19. package/packages/code-generation/package.json +1 -1
  20. package/packages/code-review/package.json +1 -1
  21. package/packages/codex-adapter/package.json +1 -1
  22. package/packages/codex-watcher/package.json +1 -1
  23. package/packages/config-capture/package.json +1 -1
  24. package/packages/connection-runtime/package.json +1 -1
  25. package/packages/context-compiler/package.json +1 -1
  26. package/packages/context-discovery/package.json +1 -1
  27. package/packages/context-manager/package.json +1 -1
  28. package/packages/contracts/dist/workspace-env-format.d.ts +63 -0
  29. package/packages/contracts/dist/workspace-env-format.js +133 -5
  30. package/packages/contracts/dist/workspace-env.js +114 -6
  31. package/packages/contracts/package.json +1 -1
  32. package/packages/contracts/src/workspace-env-format.ts +171 -5
  33. package/packages/contracts/src/workspace-env.ts +120 -4
  34. package/packages/contracts/test/workspace-env.test.ts +129 -0
  35. package/packages/cookie-sanitizer/package.json +1 -1
  36. package/packages/cost-optimizer/package.json +1 -1
  37. package/packages/dependency-analyzer/package.json +1 -1
  38. package/packages/django-plugin/package.json +1 -1
  39. package/packages/doc-generator/package.json +1 -1
  40. package/packages/durable-review/package.json +1 -1
  41. package/packages/e2e-tester/package.json +1 -1
  42. package/packages/evaluation-engine/package.json +1 -1
  43. package/packages/evaluation-runner/package.json +1 -1
  44. package/packages/event-bus/package.json +1 -1
  45. package/packages/event-sink/package.json +1 -1
  46. package/packages/event-store/package.json +1 -1
  47. package/packages/experience-replay/package.json +1 -1
  48. package/packages/feedback-collector/package.json +1 -1
  49. package/packages/file-copy-helper/package.json +1 -1
  50. package/packages/framework-detector/package.json +1 -1
  51. package/packages/framework-plugin-sdk/package.json +1 -1
  52. package/packages/frappe-plugin/package.json +1 -1
  53. package/packages/generic-web-plugin/package.json +1 -1
  54. package/packages/health-monitor/package.json +1 -1
  55. package/packages/intent-analyzer/package.json +1 -1
  56. package/packages/jev-judge/dist/continuation-controller.d.ts +47 -0
  57. package/packages/jev-judge/dist/continuation-controller.d.ts.map +1 -1
  58. package/packages/jev-judge/dist/continuation-controller.js +142 -16
  59. package/packages/jev-judge/dist/continuation-controller.js.map +1 -1
  60. package/packages/jev-judge/dist/index.d.ts +6 -0
  61. package/packages/jev-judge/dist/index.d.ts.map +1 -1
  62. package/packages/jev-judge/dist/index.js +6 -0
  63. package/packages/jev-judge/dist/index.js.map +1 -1
  64. package/packages/jev-judge/dist/validation-ledger.d.ts +129 -0
  65. package/packages/jev-judge/dist/validation-ledger.d.ts.map +1 -0
  66. package/packages/jev-judge/dist/validation-ledger.js +234 -0
  67. package/packages/jev-judge/dist/validation-ledger.js.map +1 -0
  68. package/packages/jev-judge/dist/validation-plan.d.ts +176 -0
  69. package/packages/jev-judge/dist/validation-plan.d.ts.map +1 -0
  70. package/packages/jev-judge/dist/validation-plan.js +293 -0
  71. package/packages/jev-judge/dist/validation-plan.js.map +1 -0
  72. package/packages/jev-judge/dist/validation-tie-breaker.d.ts +48 -0
  73. package/packages/jev-judge/dist/validation-tie-breaker.d.ts.map +1 -0
  74. package/packages/jev-judge/dist/validation-tie-breaker.js +83 -0
  75. package/packages/jev-judge/dist/validation-tie-breaker.js.map +1 -0
  76. package/packages/jev-judge/package.json +1 -1
  77. package/packages/jev-judge/src/continuation-controller.ts +179 -20
  78. package/packages/jev-judge/src/index.ts +41 -0
  79. package/packages/jev-judge/src/validation-ledger.ts +290 -0
  80. package/packages/jev-judge/src/validation-plan.ts +406 -0
  81. package/packages/jev-judge/src/validation-tie-breaker.ts +123 -0
  82. package/packages/jev-judge/tests/continuation-controller.test.ts +167 -0
  83. package/packages/jev-judge/tests/validation-ledger.test.ts +181 -0
  84. package/packages/jev-judge/tests/validation-plan.test.ts +170 -0
  85. package/packages/jev-judge/tests/validation-tie-breaker.test.ts +88 -0
  86. package/packages/knowledge-graph/package.json +1 -1
  87. package/packages/knowledge-retrieval/package.json +1 -1
  88. package/packages/laravel-plugin/package.json +1 -1
  89. package/packages/learning-engine/package.json +1 -1
  90. package/packages/logging/README.md +37 -0
  91. package/packages/logging/dist/buffer.d.ts +33 -0
  92. package/packages/logging/dist/buffer.d.ts.map +1 -0
  93. package/packages/logging/dist/buffer.js +60 -0
  94. package/packages/logging/dist/buffer.js.map +1 -0
  95. package/packages/logging/dist/config.d.ts +40 -0
  96. package/packages/logging/dist/config.d.ts.map +1 -0
  97. package/packages/logging/dist/config.js +114 -0
  98. package/packages/logging/dist/config.js.map +1 -0
  99. package/packages/logging/dist/index.d.ts +32 -0
  100. package/packages/logging/dist/index.d.ts.map +1 -0
  101. package/packages/logging/dist/index.js +31 -0
  102. package/packages/logging/dist/index.js.map +1 -0
  103. package/packages/logging/dist/redact.d.ts +43 -0
  104. package/packages/logging/dist/redact.d.ts.map +1 -0
  105. package/packages/logging/dist/redact.js +220 -0
  106. package/packages/logging/dist/redact.js.map +1 -0
  107. package/packages/logging/dist/sampler.d.ts +37 -0
  108. package/packages/logging/dist/sampler.d.ts.map +1 -0
  109. package/packages/logging/dist/sampler.js +78 -0
  110. package/packages/logging/dist/sampler.js.map +1 -0
  111. package/packages/logging/dist/shipper.d.ts +82 -0
  112. package/packages/logging/dist/shipper.d.ts.map +1 -0
  113. package/packages/logging/dist/shipper.js +238 -0
  114. package/packages/logging/dist/shipper.js.map +1 -0
  115. package/packages/logging/dist/singleton.d.ts +38 -0
  116. package/packages/logging/dist/singleton.d.ts.map +1 -0
  117. package/packages/logging/dist/singleton.js +95 -0
  118. package/packages/logging/dist/singleton.js.map +1 -0
  119. package/packages/logging/dist/spool.d.ts +39 -0
  120. package/packages/logging/dist/spool.d.ts.map +1 -0
  121. package/packages/logging/dist/spool.js +110 -0
  122. package/packages/logging/dist/spool.js.map +1 -0
  123. package/packages/logging/dist/transports/r2.d.ts +31 -0
  124. package/packages/logging/dist/transports/r2.d.ts.map +1 -0
  125. package/packages/logging/dist/transports/r2.js +60 -0
  126. package/packages/logging/dist/transports/r2.js.map +1 -0
  127. package/packages/logging/dist/transports/webhook.d.ts +22 -0
  128. package/packages/logging/dist/transports/webhook.d.ts.map +1 -0
  129. package/packages/logging/dist/transports/webhook.js +37 -0
  130. package/packages/logging/dist/transports/webhook.js.map +1 -0
  131. package/packages/logging/dist/types.d.ts +73 -0
  132. package/packages/logging/dist/types.d.ts.map +1 -0
  133. package/packages/logging/dist/types.js +19 -0
  134. package/packages/logging/dist/types.js.map +1 -0
  135. package/packages/logging/package.json +37 -0
  136. package/packages/logging/src/buffer.ts +77 -0
  137. package/packages/logging/src/config.ts +163 -0
  138. package/packages/logging/src/index.ts +87 -0
  139. package/packages/logging/src/redact.ts +271 -0
  140. package/packages/logging/src/sampler.ts +97 -0
  141. package/packages/logging/src/shipper.ts +281 -0
  142. package/packages/logging/src/singleton.ts +142 -0
  143. package/packages/logging/src/spool.ts +139 -0
  144. package/packages/logging/src/transports/r2.ts +86 -0
  145. package/packages/logging/src/transports/webhook.ts +54 -0
  146. package/packages/logging/src/types.ts +85 -0
  147. package/packages/logging/test/buffer.test.ts +44 -0
  148. package/packages/logging/test/config.test.ts +97 -0
  149. package/packages/logging/test/integration.test.ts +135 -0
  150. package/packages/logging/test/redact.test.ts +109 -0
  151. package/packages/logging/test/sampler.test.ts +57 -0
  152. package/packages/logging/test/shipper.test.ts +152 -0
  153. package/packages/logging/test/spool.test.ts +93 -0
  154. package/packages/mcp-adapter/package.json +1 -1
  155. package/packages/memory-engine/package.json +1 -1
  156. package/packages/milestone-manager/package.json +1 -1
  157. package/packages/model-registry/package.json +1 -1
  158. package/packages/nextjs-plugin/package.json +1 -1
  159. package/packages/notification/package.json +1 -1
  160. package/packages/observability/package.json +1 -1
  161. package/packages/okf-indexer/package.json +1 -1
  162. package/packages/paperclip-integration/package.json +1 -1
  163. package/packages/performance-optimizer/package.json +1 -1
  164. package/packages/permission-mode/package.json +1 -1
  165. package/packages/privilege-broker/package.json +1 -1
  166. package/packages/project-analyzer/package.json +1 -1
  167. package/packages/project-bootstrap/package.json +1 -1
  168. package/packages/projection-engine/package.json +1 -1
  169. package/packages/prompt-compiler/package.json +1 -1
  170. package/packages/prompt-versioning/package.json +1 -1
  171. package/packages/provider-adapter-sdk/package.json +1 -1
  172. package/packages/provider-router/package.json +1 -1
  173. package/packages/provider-selector/package.json +1 -1
  174. package/packages/providers/package.json +1 -1
  175. package/packages/qdrant-skills/package.json +1 -1
  176. package/packages/quota-manager/package.json +1 -1
  177. package/packages/rate-limiter/package.json +1 -1
  178. package/packages/react-vite-plugin/package.json +1 -1
  179. package/packages/relay/package.json +1 -1
  180. package/packages/release-manager/package.json +1 -1
  181. package/packages/requirement-compiler/package.json +1 -1
  182. package/packages/runtime/package.json +1 -1
  183. package/packages/scheduler/package.json +1 -1
  184. package/packages/scheduler-adapter/package.json +1 -1
  185. package/packages/session/package.json +1 -1
  186. package/packages/session-api/package.json +1 -1
  187. package/packages/session-export/package.json +1 -1
  188. package/packages/shared-context/package.json +1 -1
  189. package/packages/skill-mcp-client/package.json +1 -1
  190. package/packages/skill-registry/package.json +1 -1
  191. package/packages/skills/package.json +1 -1
  192. package/packages/sprint-planner/package.json +1 -1
  193. package/packages/subscription-engine/package.json +1 -1
  194. package/packages/task-compiler/package.json +1 -1
  195. package/packages/telegram/package.json +1 -1
  196. package/packages/tencentdb-memory/package.json +1 -1
  197. package/packages/tencentdb-sync/package.json +1 -1
  198. package/packages/test-data-generator/package.json +1 -1
  199. package/packages/test-generator/package.json +1 -1
  200. package/packages/todo-bd-sync/package.json +1 -1
  201. package/packages/token-estimation/package.json +1 -1
  202. package/packages/token-optimizer/package.json +1 -1
  203. package/packages/trajectory/package.json +1 -1
  204. package/packages/tui/package.json +1 -1
  205. package/packages/types/package.json +1 -1
  206. package/packages/voice-api/package.json +1 -1
  207. package/packages/webmcp-tester/package.json +1 -1
  208. package/packages/wiki/package.json +1 -1
  209. package/packages/workflow-events/package.json +1 -1
  210. package/packages/workspace-scanner/package.json +1 -1
  211. package/packages/worktree/package.json +1 -1
  212. package/packages/write-review/package.json +1 -1
  213. package/scripts/unified-webhook-server.ts +102 -0
  214. package/scripts/validation-run.ts +191 -0
package/AGENTS.md CHANGED
@@ -36,6 +36,56 @@ bd list --status pending
36
36
 
37
37
  **Why**: Agent often builds/commits without checking todos. This causes incomplete tasks to be forgotten.
38
38
 
39
+ ## Staged Validation & Test-Cost Discipline (CRITICAL)
40
+
41
+ A task with a cheap tier and an expensive tier MUST use the cheap tier while the code is still changing. Repeating an expensive regression after every fix is the most common way an autonomous run burns an entire night.
42
+
43
+ 1. **Declare the tiers.** Put the plan in the todo metadata (`metadata.validationPlan`):
44
+
45
+ ```json
46
+ {
47
+ "metadata": {
48
+ "validationPlan": {
49
+ "fast": { "command": "bun test tests/fast", "expectedDurationMs": 18000, "datasetSize": 100 },
50
+ "full": { "command": "bun test tests/full", "expectedDurationMs": 2400000, "datasetSize": 10000 },
51
+ "budgetMinutes": 90,
52
+ "finalTierRequired": true
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ The harness never guesses a tier from a command string. Without a declared plan the current behaviour is unchanged.
59
+
60
+ 2. **Iterate on the fast tier.** After every code edit, rerun the fast tier plus the focused unit/type checks the edit touches.
61
+
62
+ 3. **Run the full tier once.** Run the full tier as the final acceptance check, when the implementation and the fast tier are stable.
63
+
64
+ 4. **Do not repeat the full tier blindly.** Never rerun the full tier after an edit that cannot affect the behavior it covers. If it genuinely must be repeated, say why in one line.
65
+
66
+ 5. **Make the cost visible.** Report each validation run compactly: `[TEST] fast: bun test tests/fast - passed (18s, 100 records)`. A test that is not reported cannot be budgeted.
67
+
68
+ 6. **No false completion.** A todo whose `finalTierRequired` plan is set must not be marked completed while that tier is pending or failed. Pause instead and report what passed, what is pending, and why.
69
+
70
+ 7. **Respect the time budget.** `budgetMinutes` is a soft budget. When it is reached, stop at a safe boundary, report completed and skipped checks, and leave the work open. Never kill a destructive or non-interruptible test mid-run.
71
+
72
+ 8. **The fast tier must be representative.** A small fixture keeps the shape of the data (optional fields, relations, uniqueness, boundary values, aggregates under test), not just the row count. Use deterministic data with cleanup so repeated runs do not accumulate rows.
73
+
74
+ 9. **Record every tier run in the ledger.** The harness never runs the tests, so the run is recorded by whoever knows the result:
75
+
76
+ ```bash
77
+ bun scripts/validation-run.ts --item 12 --tier fast --status passed --duration 18s --dataset 100
78
+ bun scripts/validation-run.ts --item 12 --tier full --status passed --duration 41m -- "bun test tests/full"
79
+ ```
80
+
81
+ The ledger is plain NDJSON under `~/.pi-harness-runtime/logs/validation-runs.jsonl` (inspectable with `tail` or `jq`) and every record is mirrored into the central logging pipeline. The harness reads it to know which tier already passed, so an unreported run looks like a run that never happened.
82
+
83
+ **What the harness does with the plan:**
84
+
85
+ - The continuation message carries the plan, the recorded tier state and the remaining budget, so the agent never re-runs a tier that already passed.
86
+ - When `budgetMinutes` is spent, the item pauses at a safe boundary: nothing is dispatched, the work stays open, and the reason is reported.
87
+ - When a todo with `finalTierRequired` is marked complete while that tier is pending or failed, the completion report is suppressed and the item is named instead. A todo may not be reported as done on the strength of an unreported run.
88
+
39
89
  ## Versioning Convention
40
90
 
41
91
  **Only patch-level updates (`0.10.x`)** — no minor/major bumps until the app is fully functional.
@@ -502,6 +552,9 @@ bun run build && bun run start # Server deployment
502
552
  mode (`*_ENV`, `*_ENVIRONMENT`, `*_MODE`, `*_STAGE`, e.g. `AMOS_ENV`), then
503
553
  any other environment shaped key - and only then an environment shaped key
504
554
  of the process env → development/production/test
555
+ - The facts about the repository (only when NO variable names the environment
556
+ at all) → a published source repository without a runtime of its own is a
557
+ development checkout; a `develop`/`development` branch is the fallback
505
558
  - System RAM → Low RAM warning (<16GB total or <4GB available)
506
559
  - Running processes → dev/build server status
507
560
 
@@ -513,8 +566,26 @@ A value that is not provably harmless is masked (`<set>`): only the
513
566
  shown. A key that only looks like a branch (`BRANCH=production`) never
514
567
  decides the mode.
515
568
 
569
+ **Session variables of a desktop are not environment selectors.**
570
+ `GNOME_SHELL_SESSION_MODE`, `XDG_*`, `DESKTOP_*`, `DBUS_*`, ... name the
571
+ desktop session, never the environment of a workspace. Some of them end in
572
+ `_MODE` or `_ENV`, so they are excluded from the candidates by NAMESPACE,
573
+ not by value: their value ("ubuntu", "wayland") says nothing about the mode.
574
+
575
+ **The repository tier (last resort, never production).** It runs only when
576
+ no variable names the environment at all. A published package (a
577
+ `package.json` with a name, a version and declared content - `files`,
578
+ `bin` or `publishConfig`) without a deployment configuration (no Dockerfile,
579
+ compose file, Procfile, fly.toml, render.yaml, railway.json, vercel.json,
580
+ netlify.toml, serverless.yml, app.yaml, pm2 ecosystem file, no deploy
581
+ workflow) and without a running server is SOURCE CODE that the user runs on
582
+ their own machine → `DEVELOPMENT (source-only repository)`. A branch named
583
+ `develop` or `development` is the fallback → `DEVELOPMENT (git branch
584
+ develop)`. A production branch is NEVER a development checkout, and this
585
+ tier never claims production.
586
+
516
587
  **This info is shown in UI and delivered to agent context:**
517
- ```
588
+ ```text
518
589
  Workspace: ~/pi-harness-runtime | OS: Linux | mode: DEVELOPMENT (from AMOS_ENV in .env) | dev server: running | dev script: available | build script: available | RAM: 10/16
519
590
  ```
520
591
 
@@ -522,7 +593,8 @@ The mode words are exactly what the detector emits: `DEVELOPMENT`, `PRODUCTION`,
522
593
  `TEST` or `UNKNOWN` (with its reason, e.g.
523
594
  `UNKNOWN (no NODE_ENV or environment variable found)` or
524
595
  `UNKNOWN (AMOS_ENV in .env names no known mode)`). A known mode also
525
- names its source: `DEVELOPMENT (from AMOS_ENV in .env)`.
596
+ names its source: `DEVELOPMENT (from AMOS_ENV in .env)` or
597
+ `DEVELOPMENT (source-only repository)`.
526
598
  A dev/build script is a CAPABILITY, not a state: `dev script: available` means
527
599
  the script exists, not that a server runs. The server state is separate and
528
600
  never claims more than it knows: `running`, `not detected` (a check ran and
@@ -545,12 +617,15 @@ command to use.
545
617
  | `Dev server running` | Reuse it, don't restart |
546
618
 
547
619
  **Decision flow:**
548
- ```
620
+ ```text
549
621
  Check process.env.NODE_ENV first (it decides, also when it names no known mode)
550
622
  Check the .env and .env.* files of the workspace second
551
623
  (NODE_ENV, then an environment shaped key whose value names a mode,
552
624
  then any other environment shaped key)
553
625
  Only then an environment shaped key of the process env (e.g. AMOS_ENV)
626
+ Only then the facts about the repository
627
+ (a published source repository without a runtime of its own,
628
+ or a develop/development branch)
554
629
  Presence of dev script = capability, not proof of mode
555
630
  UNKNOWN requires investigation, not auto-build
556
631
  Production detection ≠ deployment authorization
@@ -561,3 +636,50 @@ Production detection ≠ deployment authorization
561
636
  ```
562
637
  ~/.pi-harness-runtime/workspace-env.sqlite
563
638
  ```
639
+
640
+ ## Centralized Application Logging
641
+
642
+ All application logs can be shipped to **Cloudflare R2** (durable archive) and to the
643
+ **unified webhook server** (live path). The pipeline lives in `packages/logging`
644
+ (`@pi-harness/logging`); the harness uses the process-wide singleton from it.
645
+
646
+ **Delivery order** (first success wins, the rest are skipped):
647
+
648
+ ```text
649
+ producer -> redact -> sample -> buffer -> R2 -> webhook -> local spool (~/.pi-harness-runtime/logs/spool)
650
+ ```
651
+
652
+ - The local spool is the fallback: if every transport fails the batch is written to
653
+ disk as NDJSON and re-shipped on the next successful flush. Nothing is lost, only delayed.
654
+ - The local log files (`~/.pi-harness-runtime/logs/*.jsonl`, `harness/*.log`) stay the
655
+ source of truth. The centralized pipeline is a copy for the archive.
656
+
657
+ **Configuration** (environment first, then `~/.pi-harness-runtime/keys/`):
658
+
659
+ | Variable | Meaning | Default |
660
+ |----------|---------|---------|
661
+ | `PI_LOG_SINK` | `off` \| `auto` \| `r2` \| `webhook` \| `r2+webhook` | `auto` |
662
+ | `PI_LOG_LEVEL` | Minimum level that is shipped | `info` |
663
+ | `PI_LOG_SAMPLE_RATE` | Share of `debug` records kept (0..1) | `0.1` |
664
+ | `PI_LOG_RATE_LIMIT` | Max records per source+event per minute | `60` |
665
+ | `PI_LOG_BATCH_SIZE` | Records per batch | `100` |
666
+ | `PI_LOG_BUFFER_SIZE` | Records held in memory | `1000` |
667
+ | `PI_LOG_FLUSH_INTERVAL_MS` | Automatic flush interval | `15000` |
668
+ | `PI_LOG_WEBHOOK_URL` | Ingest URL of the webhook server | unset |
669
+ | `PI_LOG_WEBHOOK_TOKEN` | Bearer token for the ingest endpoint | unset |
670
+ | `PI_LOG_R2_BUCKET` / `PI_LOG_R2_ENDPOINT` / `PI_LOG_R2_ACCESS_KEY_ID` / `PI_LOG_R2_SECRET_ACCESS_KEY` | R2 credentials | key files |
671
+ | `PI_LOG_SPOOL_DIR` | Local spool directory | `~/.pi-harness-runtime/logs/spool` |
672
+
673
+ R2 credentials are read from the key files `r2-bucket-name.txt`, `r2-endpoint.txt`,
674
+ `r2-access-key-id.txt`, `r2-secret-access-key.txt` and `r2-account-id.txt`.
675
+
676
+ **Rules for producers:**
677
+
678
+ - Emit through the singleton: `centralInfo/centralWarn/centralError(source, event, message?, fields?)`.
679
+ The `source` identifies the component, the `event` is a dotted name.
680
+ - Redaction is automatic (key names, credential patterns, truncation), but producers must
681
+ not pass user content: prompts, raw model output and message bodies are dropped by the
682
+ redactor, so shipping them is pointless.
683
+ - Never put a credential in a log message. The redactor is a safety net, not a licence.
684
+ - The webhook server exposes `POST /api/logs` for the ingest path. Set `LOG_INGEST_TOKEN`
685
+ to require a bearer token; the server re-redacts every batch and relays it to R2.
@@ -15,6 +15,8 @@
15
15
  import { appendFileSync, existsSync, mkdirSync } from "node:fs";
16
16
  import { join } from "node:path";
17
17
  import { homedir } from "node:os";
18
+ // Centralized pipeline (R2 / webhook) - a copy of the local log.
19
+ import { centralError, centralInfo, centralWarn } from "../packages/logging/dist/index.js";
18
20
 
19
21
  export type LogLevel = "INFO" | "WARN" | "ERROR";
20
22
 
@@ -55,6 +57,21 @@ function writeLog(
55
57
  // Non-fatal - just log to console
56
58
  console.error(`[GLMQuotaLogger] Failed to write log: ${message}`);
57
59
  }
60
+
61
+ // Ship a copy to the centralized pipeline. The job id is an identifier,
62
+ // not content, so it is safe to include.
63
+ try {
64
+ const fields = { jobId: jobId ?? "system" };
65
+ if (level === "ERROR") {
66
+ centralError("harness.glm-quota", "quota", message, fields);
67
+ } else if (level === "WARN") {
68
+ centralWarn("harness.glm-quota", "quota", message, fields);
69
+ } else {
70
+ centralInfo("harness.glm-quota", "quota", message, fields);
71
+ }
72
+ } catch {
73
+ // Logging must never break the quota flow
74
+ }
58
75
  }
59
76
 
60
77
  // --- Public API ---
@@ -14,6 +14,8 @@
14
14
  import { mkdirSync, appendFileSync, existsSync } from "node:fs";
15
15
  import { join } from "node:path";
16
16
  import { homedir } from "node:os";
17
+ // Centralized pipeline (R2 / webhook) - a copy of the local log.
18
+ import { centralInfo } from "../../packages/logging/dist/index.js";
17
19
 
18
20
  export interface PlannerDecision {
19
21
  timestamp: string;
@@ -179,6 +181,19 @@ export async function logPlannerDecision(decision: PlannerLogEntry): Promise<voi
179
181
  appendFileSync(LOG_FILE, fileLog);
180
182
  console.log(fileLog);
181
183
 
184
+ // Ship a copy to the centralized pipeline. Only the decision itself is
185
+ // shipped - the request and the raw planner output stay local.
186
+ try {
187
+ centralInfo("harness.planner", "decision", undefined, {
188
+ provider: decision.provider,
189
+ escalate: decision.escalate,
190
+ escalateReason: decision.escalateReason,
191
+ userInputType: decision.userInput?.type ?? null,
192
+ });
193
+ } catch {
194
+ // Logging must never break the planner flow
195
+ }
196
+
182
197
  // Send to Telegram (async, non-blocking for main flow)
183
198
  const telegramMsg = formatTelegramMessage(decision);
184
199
  // Fire-and-forget: don't await, let it run in background
@@ -8,6 +8,9 @@
8
8
  import { appendFileSync, existsSync, mkdirSync } from "node:fs";
9
9
  import { join } from "node:path";
10
10
  import { homedir } from "node:os";
11
+ // Centralized pipeline (R2 / webhook). The local JSONL file stays the
12
+ // source of truth; the shipper is a copy for the archive.
13
+ import { centralError, centralInfo, centralWarn } from "../packages/logging/dist/index.js";
11
14
 
12
15
  export type ServiceLevel = "info" | "warn" | "error" | "debug";
13
16
  export type ServiceName = "jev" | "honcho" | "qdrant";
@@ -41,16 +44,44 @@ function ensureLogDir(): void {
41
44
  * Sensitive fields (keys, tokens, full content) are never logged.
42
45
  */
43
46
  export function logServiceEvent(event: ServiceEvent): void {
47
+ let safeEvent: Record<string, unknown> = { ...event };
44
48
  try {
45
49
  ensureLogDir();
46
50
 
47
51
  // Redact sensitive fields
48
- const safeEvent = redactSensitive(event);
52
+ safeEvent = redactSensitive(event);
49
53
 
50
54
  appendFileSync(LOG_FILE, JSON.stringify(safeEvent) + "\n");
51
55
  } catch {
52
56
  // Silently fail if logging fails - don't crash the service
53
57
  }
58
+
59
+ shipServiceEvent(safeEvent);
60
+ }
61
+
62
+ /**
63
+ * Send a service event to the centralized pipeline.
64
+ *
65
+ * The event was already redacted above; the shipper redacts again, which
66
+ * is cheap and keeps the guarantee local to one place.
67
+ */
68
+ function shipServiceEvent(event: Record<string, unknown>): void {
69
+ try {
70
+ const level = typeof event.level === "string" ? event.level : "info";
71
+ const fields: Record<string, unknown> = { ...event };
72
+ delete fields.level;
73
+ delete fields.ts;
74
+
75
+ if (level === "error") {
76
+ centralError("harness.service-diagnostics", String(event.operation ?? "event"), undefined, fields);
77
+ } else if (level === "warn") {
78
+ centralWarn("harness.service-diagnostics", String(event.operation ?? "event"), undefined, fields);
79
+ } else {
80
+ centralInfo("harness.service-diagnostics", String(event.operation ?? "event"), undefined, fields);
81
+ }
82
+ } catch {
83
+ // Never let shipping break the service
84
+ }
54
85
  }
55
86
 
56
87
  /**
package/index.ts CHANGED
@@ -100,6 +100,10 @@ import {
100
100
  hasPaperclipConfig,
101
101
  PaperclipWatcher,
102
102
  } from "./packages/paperclip-integration/dist/index.js";
103
+ // Centralized application logging: R2 (durable archive) + webhook (live),
104
+ // with a local spool as the fallback. Optional - without credentials the
105
+ // logs simply stay local.
106
+ import { centralInfo, initCentralLogging } from "./packages/logging/dist/index.js";
103
107
 
104
108
  // --- Jev Auto-Continue: Agent autonomous decision-making --------------------
105
109
  // Lazy import - only loads when packages/jev-judge exists and API key available
@@ -642,12 +646,29 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
642
646
 
643
647
  // Get task context from local todo controller (not bd)
644
648
  const snapshot = await (todoContinuationController?.getSnapshot() ?? Promise.resolve({ items: [] }));
649
+ // An item whose final tier is still pending is NOT done, even when
650
+ // it is already marked completed. The judge must see it as
651
+ // remaining work, otherwise the session ends on a false
652
+ // completion.
653
+ const pendingFinalTier = validationLedger
654
+ ? snapshot.items.filter((item) => validationLedger?.isFinalTierPending(item))
655
+ : [];
656
+ const pendingFinalTierIds = new Set(pendingFinalTier.map((item) => item.id));
657
+ if (pendingFinalTier.length > 0) {
658
+ logToFile(
659
+ "[auto-continue]",
660
+ "Final tier still pending for:",
661
+ pendingFinalTier.map((item) => `${item.id}:${item.subject}`).join(", "),
662
+ );
663
+ }
645
664
  const taskContext = {
646
665
  completed: snapshot.items
647
- .filter((item: { status?: string; subject?: string }) => item.status === "completed")
666
+ .filter((item: { id?: number; status?: string; subject?: string }) =>
667
+ item.status === "completed" && !(item.id !== undefined && pendingFinalTierIds.has(item.id)))
648
668
  .map((item: { status?: string; subject?: string }) => item.subject ?? ""),
649
669
  remaining: snapshot.items
650
- .filter((item: { status?: string; subject?: string }) => item.status !== "completed")
670
+ .filter((item: { id?: number; status?: string; subject?: string }) =>
671
+ item.status !== "completed" || (item.id !== undefined && pendingFinalTierIds.has(item.id)))
651
672
  .map((item: { status?: string; subject?: string }) => item.subject ?? ""),
652
673
  total: snapshot.items.length,
653
674
  };
@@ -729,11 +750,35 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
729
750
 
730
751
  // --- todo continuation: Retain one controller per session ----------
731
752
  let todoContinuationController: import("./packages/jev-judge/dist/continuation-controller.js").TodoContinuationController | null = null;
753
+ // Ledger of the validation runs: it is the only mechanical source for
754
+ // "did the final tier pass?" and therefore guards the completion report.
755
+ let validationLedger: import("./packages/jev-judge/dist/validation-ledger.js").ValidationLedger | null = null;
732
756
 
733
757
  // Create controller on session_start with proper dispatch callback
734
758
  pi.on("session_start", async (_event, ctx) => {
735
759
  try {
736
760
  const { TodoContinuationController } = await import("./packages/jev-judge/dist/continuation-controller.js");
761
+ const { ValidationLedger } = await import("./packages/jev-judge/dist/validation-ledger.js");
762
+ // The ledger records which validation tier ran. The controller
763
+ // uses it to tell the agent what already passed and whether the
764
+ // final tier is still pending.
765
+ validationLedger = new ValidationLedger({
766
+ onRecord: (record) => {
767
+ centralInfo(
768
+ "validation",
769
+ "tier.run",
770
+ `${record.tier} tier ${record.status}`,
771
+ {
772
+ itemId: record.itemId,
773
+ tier: record.tier,
774
+ status: record.status,
775
+ durationMs: record.durationMs,
776
+ datasetSize: record.datasetSize,
777
+ command: record.command,
778
+ },
779
+ );
780
+ },
781
+ });
737
782
  // SAFETY: ctx is ExtensionContext with sessionManager, isIdle, hasPendingMessages
738
783
  // dispatch uses pi.sendUserMessage for actual message delivery
739
784
  todoContinuationController = new TodoContinuationController(
@@ -742,6 +787,7 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
742
787
  dispatch: async (content: string) => {
743
788
  await pi.sendUserMessage(content, { deliverAs: "steer" });
744
789
  },
790
+ validationLedger,
745
791
  }
746
792
  );
747
793
  logToFile("[auto-continue]", "Todo continuation controller initialized");
@@ -988,6 +1034,26 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
988
1034
  const isNoPendingTasks = reason === "No pending tasks";
989
1035
 
990
1036
  if (isNoPendingTasks) {
1037
+ // A todo whose final tier is still pending must not be
1038
+ // reported as complete. The item may already be marked
1039
+ // completed by the agent - the ledger is the check that
1040
+ // catches it.
1041
+ const snapshot = await todoContinuationController.getSnapshot();
1042
+ const pendingFinalTier = validationLedger
1043
+ ? snapshot.items.filter((item) => validationLedger?.isFinalTierPending(item))
1044
+ : [];
1045
+ if (pendingFinalTier.length > 0) {
1046
+ const details = pendingFinalTier
1047
+ .map((item) => `${item.id}:${item.subject}`)
1048
+ .join(", ");
1049
+ logToFile(
1050
+ "[auto-continue]",
1051
+ "Completion suppressed - final tier still pending for:",
1052
+ details,
1053
+ );
1054
+ updateAutoContinueStatus("ERROR", "final tier pending");
1055
+ return;
1056
+ }
991
1057
  // Check if there's a pending question - suppress completion if so
992
1058
  if (pqController.hasPendingQuestion()) {
993
1059
  logToFile("[auto-continue]", "Suppressing completion - pending question exists");
@@ -1005,7 +1071,6 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
1005
1071
  _completedNotifiedFor = completionKey;
1006
1072
 
1007
1073
  // Get full todo list for notification
1008
- const snapshot = await todoContinuationController.getSnapshot();
1009
1074
  const todoList = snapshot.items
1010
1075
  .map((item) => {
1011
1076
  const status = item.status === "completed" ? "[DONE]" : "[PENDING]";
@@ -2883,6 +2948,14 @@ function logToFile(...parts: string[]): void {
2883
2948
  } catch {
2884
2949
  // Ignore file write errors
2885
2950
  }
2951
+
2952
+ // Mirror the line into the centralized pipeline. The sampler keeps the
2953
+ // volume bounded; the local file above stays the source of truth.
2954
+ try {
2955
+ centralInfo("harness", "log", msg);
2956
+ } catch {
2957
+ // Logging must never break the caller.
2958
+ }
2886
2959
  }
2887
2960
 
2888
2961
  /** Log a harness startup message to file and track for UI */
@@ -3330,6 +3403,22 @@ export default async function (pi: ExtensionAPI) {
3330
3403
  const mirrorStore = new MirrorStore();
3331
3404
  ensureHarnessDir();
3332
3405
 
3406
+ // --- central-logging: ship structured logs to R2 / the webhook server ---
3407
+ // The pipeline is opt-in by configuration, never by code: without R2
3408
+ // credentials and without a webhook URL nothing leaves the machine.
3409
+ try {
3410
+ const logConfig = initCentralLogging();
3411
+ if (logConfig?.enabled) {
3412
+ logToFile(
3413
+ "[CentralLogging]",
3414
+ "enabled -",
3415
+ logConfig.notes.length > 0 ? logConfig.notes.join("; ") : "no transport configured",
3416
+ );
3417
+ }
3418
+ } catch (e) {
3419
+ logToFile("[CentralLogging]", "init failed:", String(e));
3420
+ }
3421
+
3333
3422
  // Collect async init promises so we can wait for them before showing startup notification
3334
3423
  const asyncInitPromises: Promise<unknown>[] = [];
3335
3424
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-harness-runtime",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "description": "[BETA] Codex-style /usage status + autonomous coding harness for pi. Not production ready — expect breaking changes.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/a2a-adapter",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "description": "Google Agent-to-Agent (A2A) protocol adapter for pi-harness",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/agent-policy",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "description": "Agent policy runtime for pi-harness - enforces mandatory rule delivery and mutation gating",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/architecture-generator",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "description": "Generate ADR and Mermaid diagrams from project analysis",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/auth",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "description": "pi-harness-runtime — auth",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/autonomous-refactor",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "description": "Autonomous refactoring engine",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/autonomous-runtime",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "description": "Autonomous Operations Runtime — task inbox, lease management, and worker lifecycle (RFC-0101)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/cache-strategy",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "description": "Smart caching with TTL and invalidation strategies (RFC-0022)",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/capability-registry",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "description": "Capability Registry for pi-harness-runtime — tracks model capabilities and enables capability-based routing",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/checkpoint",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "description": "Enhanced checkpoint system with incremental snapshots, diff-based storage, and recovery strategies",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/cli-plugin-sdk",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "description": "CLI Plugin SDK for pi-harness — install, list, remove, update, invoke plugins",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/clipboard",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/clipboard-plugin",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/code-generation",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "description": "Code generation pipeline for pi-harness-runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/code-review",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "description": "Automated code review engine for pi-harness-runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/codex-adapter",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "description": "OpenAI Codex provider adapter for pi-harness",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/codex-watcher",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "description": "Watches Codex CLI sessions and emits visible messages for PING-PONG monitoring",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moocoding/config-capture",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "description": "Auto-capture API-fetched configuration values to AGENTS.md",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/connection-runtime",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "description": "Connection management with jittered exponential backoff for pi-harness",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/context-compiler",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "description": "Bounded context selection engine for pi-harness-runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/context-discovery",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/context-manager",
3
- "version": "1.1.394",
3
+ "version": "1.1.395",
4
4
  "description": "Context Window Manager — prevents model context overflow via compaction and resume prompts (RFC-0010)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",