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.
- package/AGENTS.md +125 -3
- package/harness/glm-quota-logger.ts +17 -0
- package/harness/langchain/planner-logger.ts +15 -0
- package/harness/service-diagnostics.ts +32 -1
- package/index.ts +92 -3
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/dist/workspace-env-format.d.ts +63 -0
- package/packages/contracts/dist/workspace-env-format.js +133 -5
- package/packages/contracts/dist/workspace-env.js +114 -6
- package/packages/contracts/package.json +1 -1
- package/packages/contracts/src/workspace-env-format.ts +171 -5
- package/packages/contracts/src/workspace-env.ts +120 -4
- package/packages/contracts/test/workspace-env.test.ts +129 -0
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/dist/continuation-controller.d.ts +47 -0
- package/packages/jev-judge/dist/continuation-controller.d.ts.map +1 -1
- package/packages/jev-judge/dist/continuation-controller.js +142 -16
- package/packages/jev-judge/dist/continuation-controller.js.map +1 -1
- package/packages/jev-judge/dist/index.d.ts +6 -0
- package/packages/jev-judge/dist/index.d.ts.map +1 -1
- package/packages/jev-judge/dist/index.js +6 -0
- package/packages/jev-judge/dist/index.js.map +1 -1
- package/packages/jev-judge/dist/validation-ledger.d.ts +129 -0
- package/packages/jev-judge/dist/validation-ledger.d.ts.map +1 -0
- package/packages/jev-judge/dist/validation-ledger.js +234 -0
- package/packages/jev-judge/dist/validation-ledger.js.map +1 -0
- package/packages/jev-judge/dist/validation-plan.d.ts +176 -0
- package/packages/jev-judge/dist/validation-plan.d.ts.map +1 -0
- package/packages/jev-judge/dist/validation-plan.js +293 -0
- package/packages/jev-judge/dist/validation-plan.js.map +1 -0
- package/packages/jev-judge/dist/validation-tie-breaker.d.ts +48 -0
- package/packages/jev-judge/dist/validation-tie-breaker.d.ts.map +1 -0
- package/packages/jev-judge/dist/validation-tie-breaker.js +83 -0
- package/packages/jev-judge/dist/validation-tie-breaker.js.map +1 -0
- package/packages/jev-judge/package.json +1 -1
- package/packages/jev-judge/src/continuation-controller.ts +179 -20
- package/packages/jev-judge/src/index.ts +41 -0
- package/packages/jev-judge/src/validation-ledger.ts +290 -0
- package/packages/jev-judge/src/validation-plan.ts +406 -0
- package/packages/jev-judge/src/validation-tie-breaker.ts +123 -0
- package/packages/jev-judge/tests/continuation-controller.test.ts +167 -0
- package/packages/jev-judge/tests/validation-ledger.test.ts +181 -0
- package/packages/jev-judge/tests/validation-plan.test.ts +170 -0
- package/packages/jev-judge/tests/validation-tie-breaker.test.ts +88 -0
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/logging/README.md +37 -0
- package/packages/logging/dist/buffer.d.ts +33 -0
- package/packages/logging/dist/buffer.d.ts.map +1 -0
- package/packages/logging/dist/buffer.js +60 -0
- package/packages/logging/dist/buffer.js.map +1 -0
- package/packages/logging/dist/config.d.ts +40 -0
- package/packages/logging/dist/config.d.ts.map +1 -0
- package/packages/logging/dist/config.js +114 -0
- package/packages/logging/dist/config.js.map +1 -0
- package/packages/logging/dist/index.d.ts +32 -0
- package/packages/logging/dist/index.d.ts.map +1 -0
- package/packages/logging/dist/index.js +31 -0
- package/packages/logging/dist/index.js.map +1 -0
- package/packages/logging/dist/redact.d.ts +43 -0
- package/packages/logging/dist/redact.d.ts.map +1 -0
- package/packages/logging/dist/redact.js +220 -0
- package/packages/logging/dist/redact.js.map +1 -0
- package/packages/logging/dist/sampler.d.ts +37 -0
- package/packages/logging/dist/sampler.d.ts.map +1 -0
- package/packages/logging/dist/sampler.js +78 -0
- package/packages/logging/dist/sampler.js.map +1 -0
- package/packages/logging/dist/shipper.d.ts +82 -0
- package/packages/logging/dist/shipper.d.ts.map +1 -0
- package/packages/logging/dist/shipper.js +238 -0
- package/packages/logging/dist/shipper.js.map +1 -0
- package/packages/logging/dist/singleton.d.ts +38 -0
- package/packages/logging/dist/singleton.d.ts.map +1 -0
- package/packages/logging/dist/singleton.js +95 -0
- package/packages/logging/dist/singleton.js.map +1 -0
- package/packages/logging/dist/spool.d.ts +39 -0
- package/packages/logging/dist/spool.d.ts.map +1 -0
- package/packages/logging/dist/spool.js +110 -0
- package/packages/logging/dist/spool.js.map +1 -0
- package/packages/logging/dist/transports/r2.d.ts +31 -0
- package/packages/logging/dist/transports/r2.d.ts.map +1 -0
- package/packages/logging/dist/transports/r2.js +60 -0
- package/packages/logging/dist/transports/r2.js.map +1 -0
- package/packages/logging/dist/transports/webhook.d.ts +22 -0
- package/packages/logging/dist/transports/webhook.d.ts.map +1 -0
- package/packages/logging/dist/transports/webhook.js +37 -0
- package/packages/logging/dist/transports/webhook.js.map +1 -0
- package/packages/logging/dist/types.d.ts +73 -0
- package/packages/logging/dist/types.d.ts.map +1 -0
- package/packages/logging/dist/types.js +19 -0
- package/packages/logging/dist/types.js.map +1 -0
- package/packages/logging/package.json +37 -0
- package/packages/logging/src/buffer.ts +77 -0
- package/packages/logging/src/config.ts +163 -0
- package/packages/logging/src/index.ts +87 -0
- package/packages/logging/src/redact.ts +271 -0
- package/packages/logging/src/sampler.ts +97 -0
- package/packages/logging/src/shipper.ts +281 -0
- package/packages/logging/src/singleton.ts +142 -0
- package/packages/logging/src/spool.ts +139 -0
- package/packages/logging/src/transports/r2.ts +86 -0
- package/packages/logging/src/transports/webhook.ts +54 -0
- package/packages/logging/src/types.ts +85 -0
- package/packages/logging/test/buffer.test.ts +44 -0
- package/packages/logging/test/config.test.ts +97 -0
- package/packages/logging/test/integration.test.ts +135 -0
- package/packages/logging/test/redact.test.ts +109 -0
- package/packages/logging/test/sampler.test.ts +57 -0
- package/packages/logging/test/shipper.test.ts +152 -0
- package/packages/logging/test/spool.test.ts +93 -0
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/scripts/unified-webhook-server.ts +102 -0
- 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
|
-
|
|
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 }) =>
|
|
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 }) =>
|
|
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/capability-registry",
|
|
3
|
-
"version": "1.1.
|
|
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",
|