pi-background-tasks 2.5.0 → 2.6.0
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/BACKGROUND-TASKS-INSTRUCTIONS.md +1 -1
- package/README.md +70 -25
- package/TESTING.md +42 -2
- package/TEST_PLAN.md +7 -4
- package/dist/extensions/anthropic-attribution-child.js +4 -0
- package/dist/extensions/anthropic-attribution-child.js.map +1 -0
- package/dist/extensions/anthropic-attribution.js +107 -0
- package/dist/extensions/anthropic-attribution.js.map +1 -0
- package/dist/extensions/background-tasks.js +2 -0
- package/dist/extensions/background-tasks.js.map +1 -0
- package/dist/extensions/delegate-child.js +2 -0
- package/dist/extensions/delegate-child.js.map +1 -0
- package/dist/extensions/fusion-child.js +2 -0
- package/dist/extensions/fusion-child.js.map +1 -0
- package/dist/package.json +5 -0
- package/dist/src/core/anthropic-attribution-path.js +24 -0
- package/dist/src/core/anthropic-attribution-path.js.map +1 -0
- package/dist/src/core/anthropic-attribution.js +2247 -0
- package/dist/src/core/anthropic-attribution.js.map +1 -0
- package/dist/src/core/attested-pi-contract.js +5 -0
- package/dist/src/core/attested-pi-contract.js.map +1 -0
- package/dist/src/core/attested-pi-run.js +749 -0
- package/dist/src/core/attested-pi-run.js.map +1 -0
- package/dist/src/core/canonical-json.js +19 -0
- package/dist/src/core/canonical-json.js.map +1 -0
- package/dist/src/core/common.js +787 -0
- package/dist/src/core/common.js.map +1 -0
- package/dist/src/core/config.js +78 -0
- package/dist/src/core/config.js.map +1 -0
- package/dist/src/core/context/parent-snapshot.js +75 -0
- package/dist/src/core/context/parent-snapshot.js.map +1 -0
- package/dist/src/core/context/token-budget.js +631 -0
- package/dist/src/core/context/token-budget.js.map +1 -0
- package/dist/src/core/context/visible-conversation-v2.js +390 -0
- package/dist/src/core/context/visible-conversation-v2.js.map +1 -0
- package/dist/src/core/delegate/artifacts.js +357 -0
- package/dist/src/core/delegate/artifacts.js.map +1 -0
- package/dist/src/core/delegate/budget.js +266 -0
- package/dist/src/core/delegate/budget.js.map +1 -0
- package/dist/src/core/delegate/facade-contract.js +7 -0
- package/dist/src/core/delegate/facade-contract.js.map +1 -0
- package/dist/src/core/delegate/hook-contract-evidence.json +18 -0
- package/dist/src/core/delegate/hook-contract.js +124 -0
- package/dist/src/core/delegate/hook-contract.js.map +1 -0
- package/dist/src/core/delegate/launch.js +366 -0
- package/dist/src/core/delegate/launch.js.map +1 -0
- package/dist/src/core/delegate/result-package.js +293 -0
- package/dist/src/core/delegate/result-package.js.map +1 -0
- package/dist/src/core/delegate/runner.js +398 -0
- package/dist/src/core/delegate/runner.js.map +1 -0
- package/dist/src/core/delegate/seed.js +334 -0
- package/dist/src/core/delegate/seed.js.map +1 -0
- package/dist/src/core/delegate/types.js +103 -0
- package/dist/src/core/delegate/types.js.map +1 -0
- package/dist/src/core/durable-fs.js +372 -0
- package/dist/src/core/durable-fs.js.map +1 -0
- package/dist/src/core/extension-api.js +382 -0
- package/dist/src/core/extension-api.js.map +1 -0
- package/dist/src/core/fusion/artifacts.js +654 -0
- package/dist/src/core/fusion/artifacts.js.map +1 -0
- package/dist/src/core/fusion/budget.js +949 -0
- package/dist/src/core/fusion/budget.js.map +1 -0
- package/dist/src/core/fusion/child-protocol.js +176 -0
- package/dist/src/core/fusion/child-protocol.js.map +1 -0
- package/dist/src/core/fusion/claude-cache.js +156 -0
- package/dist/src/core/fusion/claude-cache.js.map +1 -0
- package/dist/src/core/fusion/clean-context.js +64 -0
- package/dist/src/core/fusion/clean-context.js.map +1 -0
- package/dist/src/core/fusion/config.js +349 -0
- package/dist/src/core/fusion/config.js.map +1 -0
- package/dist/src/core/fusion/context.js +184 -0
- package/dist/src/core/fusion/context.js.map +1 -0
- package/dist/src/core/fusion/evaluation.js +696 -0
- package/dist/src/core/fusion/evaluation.js.map +1 -0
- package/dist/src/core/fusion/facade-contract.js +3 -0
- package/dist/src/core/fusion/facade-contract.js.map +1 -0
- package/dist/src/core/fusion/orchestrator.js +867 -0
- package/dist/src/core/fusion/orchestrator.js.map +1 -0
- package/dist/src/core/fusion/output-contract.js +27 -0
- package/dist/src/core/fusion/output-contract.js.map +1 -0
- package/dist/src/core/fusion/pi-child.js +1741 -0
- package/dist/src/core/fusion/pi-child.js.map +1 -0
- package/dist/src/core/fusion/prompts.js +260 -0
- package/dist/src/core/fusion/prompts.js.map +1 -0
- package/dist/src/core/fusion/result-package.js +857 -0
- package/dist/src/core/fusion/result-package.js.map +1 -0
- package/dist/src/core/fusion/source-policy.js +259 -0
- package/dist/src/core/fusion/source-policy.js.map +1 -0
- package/dist/src/core/fusion/types.js +207 -0
- package/dist/src/core/fusion/types.js.map +1 -0
- package/dist/src/core/fusion/web-fetch.js +777 -0
- package/dist/src/core/fusion/web-fetch.js.map +1 -0
- package/dist/src/core/fusion/workflows.js +126 -0
- package/dist/src/core/fusion/workflows.js.map +1 -0
- package/dist/src/core/lazy-module.js +181 -0
- package/dist/src/core/lazy-module.js.map +1 -0
- package/dist/src/core/pi-launch.js +431 -0
- package/dist/src/core/pi-launch.js.map +1 -0
- package/dist/src/core/registry.js +3331 -0
- package/dist/src/core/registry.js.map +1 -0
- package/dist/src/core/reload-shell-owner.js +1396 -0
- package/dist/src/core/reload-shell-owner.js.map +1 -0
- package/dist/src/core/shell-policy.js +80 -0
- package/dist/src/core/shell-policy.js.map +1 -0
- package/dist/src/core/task-durable.js +51 -0
- package/dist/src/core/task-durable.js.map +1 -0
- package/dist/src/core/update-check.js +92 -0
- package/dist/src/core/update-check.js.map +1 -0
- package/dist/src/core/windows-taskkill.js +185 -0
- package/dist/src/core/windows-taskkill.js.map +1 -0
- package/dist/src/delegate-child-extension.js +776 -0
- package/dist/src/delegate-child-extension.js.map +1 -0
- package/dist/src/delegate-extension.js +746 -0
- package/dist/src/delegate-extension.js.map +1 -0
- package/dist/src/extension.js +953 -0
- package/dist/src/extension.js.map +1 -0
- package/dist/src/fusion-child-extension.js +760 -0
- package/dist/src/fusion-child-extension.js.map +1 -0
- package/dist/src/fusion-extension.js +1030 -0
- package/dist/src/fusion-extension.js.map +1 -0
- package/dist/src/ui/background-tasks-manager.js +689 -0
- package/dist/src/ui/background-tasks-manager.js.map +1 -0
- package/dist/src/ui/fusion-model-selector.js +277 -0
- package/dist/src/ui/fusion-model-selector.js.map +1 -0
- package/docs/INDEX.md +35 -33
- package/docs/api/eventbus-v1.md +21 -5
- package/docs/choose-a-workflow.md +2 -0
- package/docs/commands/bg-clear.md +3 -3
- package/docs/commands/bg-update.md +3 -3
- package/docs/commands/bg.md +18 -6
- package/docs/commands/claude-cache.md +4 -4
- package/docs/commands/fusion-models.md +49 -4
- package/docs/commands/fusion.md +3 -3
- package/docs/commands/jobs.md +3 -3
- package/docs/commands/kill.md +3 -3
- package/docs/commands/logs.md +3 -3
- package/docs/commands/task-manager.md +5 -5
- package/docs/concepts/completion-delivery.md +6 -2
- package/docs/getting-started.md +7 -1
- package/docs/manifest.json +228 -42
- package/docs/operations/configuration.md +58 -5
- package/docs/operations/releasing.md +4 -2
- package/docs/operations/testing.md +22 -0
- package/docs/operations/troubleshooting.md +6 -2
- package/docs/read-before-edit.md +44 -31
- package/docs/reference/runtime-contracts.md +87 -51
- package/docs/reference/shortcuts-and-dock.md +28 -14
- package/docs/subsystems/anthropic-attribution.md +23 -11
- package/docs/subsystems/attested-pi-runs.md +12 -7
- package/docs/subsystems/background-task-runtime.md +53 -11
- package/docs/subsystems/child-launch-durability-and-safety.md +28 -9
- package/docs/subsystems/delegation.md +16 -3
- package/docs/subsystems/docs-freshness-gate.md +14 -7
- package/docs/subsystems/fusion.md +36 -11
- package/docs/subsystems/host-ui-and-telemetry.md +22 -9
- package/docs/tools/bg_delegate.md +3 -1
- package/docs/tools/bg_kill.md +3 -1
- package/docs/tools/bg_logs.md +3 -1
- package/docs/tools/bg_result.md +5 -3
- package/docs/tools/bg_run.md +31 -5
- package/docs/tools/bg_run_pi_attested.md +3 -1
- package/docs/tools/bg_status.md +3 -1
- package/docs/tools/fusion_investigate.md +3 -1
- package/docs/tools/fusion_reason.md +3 -1
- package/docs/tools/fusion_research.md +3 -1
- package/docs/tools/fusion_validate.md +3 -1
- package/extensions/anthropic-attribution-child.ts +3 -0
- package/extensions/anthropic-attribution.ts +166 -1
- package/package.json +12 -10
- package/src/core/anthropic-attribution-path.ts +5 -4
- package/src/core/anthropic-attribution.ts +293 -113
- package/src/core/attested-pi-contract.ts +4 -0
- package/src/core/attested-pi-run.ts +468 -81
- package/src/core/canonical-json.ts +21 -0
- package/src/core/common.ts +450 -40
- package/src/core/config.ts +121 -0
- package/src/core/context/visible-conversation-v2.ts +3 -6
- package/src/core/delegate/artifacts.ts +13 -9
- package/src/core/delegate/budget.ts +13 -6
- package/src/core/delegate/facade-contract.ts +6 -0
- package/src/core/delegate/result-package.ts +32 -14
- package/src/core/delegate/runner.ts +129 -58
- package/src/core/delegate/seed.ts +5 -7
- package/src/core/durable-fs.ts +178 -30
- package/src/core/extension-api.ts +36 -8
- package/src/core/fusion/artifacts.ts +17 -16
- package/src/core/fusion/clean-context.ts +8 -5
- package/src/core/fusion/config.ts +2 -1
- package/src/core/fusion/context.ts +2 -6
- package/src/core/fusion/facade-contract.ts +2 -0
- package/src/core/fusion/orchestrator.ts +3 -3
- package/src/core/fusion/prompts.ts +1 -1
- package/src/core/fusion/result-package.ts +259 -72
- package/src/core/fusion/source-policy.ts +43 -17
- package/src/core/lazy-module.ts +215 -0
- package/src/core/pi-launch.ts +390 -65
- package/src/core/registry.ts +2010 -381
- package/src/core/reload-shell-owner.ts +1662 -0
- package/src/core/shell-policy.ts +134 -0
- package/src/core/task-durable.ts +67 -0
- package/src/delegate-extension.ts +404 -144
- package/src/extension.ts +387 -114
- package/src/fusion-extension.ts +202 -96
|
@@ -46,7 +46,7 @@ Current authored ownership map:
|
|
|
46
46
|
- Fusion runtime: `.pi/fusion/<session-id>-<pid>/<run-id>/` under the active project cwd.
|
|
47
47
|
- Delegate artifacts: task-owned artifact directories referenced from task metadata/result packages.
|
|
48
48
|
- Fusion model config: `fusion-models.json` under Pi's agent directory (`getAgentDir()`), not the project `.pi/tasks` tree.
|
|
49
|
-
-
|
|
49
|
+
- Published Pi entrypoints, in load order: `dist/extensions/anthropic-attribution.js`, then `dist/extensions/background-tasks.js`, via `package.json.pi.extensions`. Authoritative TypeScript remains under `extensions/` and `src/`; `npm run build:runtime` produces the shipped JavaScript closure.
|
|
50
50
|
|
|
51
51
|
---
|
|
52
52
|
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
- **Run long work without blocking**: start named shell jobs, keep talking to Pi, and get durable completion notifications when they finish.
|
|
18
18
|
- **Delegate context-aware investigation**: launch one route-pinned, inspect-only child Pi agent seeded with a frozen projection of the current conversation, then retrieve a hash-verified result.
|
|
19
19
|
- **Combine model perspectives through fixed-purpose Fusion**: run three candidate children, blind evaluation, optional bounded evaluator repair, and merger for reasoning, investigation, targeted URL research, or validation review.
|
|
20
|
-
- **Keep Anthropic subscription traffic attributed and compatible**:
|
|
20
|
+
- **Keep Anthropic subscription traffic attributed and compatible**: by default, apply the package-owned Claude Code OAuth attribution, cache policy, and exact-match prompt sanitization to parent Anthropic routes without an external sanitizer dependency; isolated package children retain mandatory attribution even when ambient parent attribution is disabled.
|
|
21
21
|
|
|
22
22
|
<p align="center">
|
|
23
23
|
<img src="docs/assets/architecture.svg" alt="Architecture diagram showing Pi session, background task registry, delegated child agent, and Fusion candidate/evaluator/merger flow" width="760">
|
|
@@ -27,42 +27,63 @@
|
|
|
27
27
|
| Fact | Value |
|
|
28
28
|
| --- | --- |
|
|
29
29
|
| Package | `pi-background-tasks` |
|
|
30
|
-
| Version | `2.
|
|
30
|
+
| Version | `2.6.0` |
|
|
31
31
|
| Node engine | `>=22.19.0` |
|
|
32
|
-
| Pi entrypoints | `./extensions/anthropic-attribution.
|
|
32
|
+
| Pi entrypoints | `./dist/extensions/anthropic-attribution.js`, `./dist/extensions/background-tasks.js` |
|
|
33
33
|
| Package image | [logo.png](https://raw.githubusercontent.com/ismailsaleekh/pi-background-tasks/main/logo.png) |
|
|
34
34
|
<!-- pi-docs:end name="readme-package-facts" -->
|
|
35
35
|
|
|
36
36
|
<!-- pi-docs:begin name="readme-public-surfaces" generator="scripts/docs/generate.mjs" -->
|
|
37
|
-
| Surface kind |
|
|
38
|
-
| --- | --- |
|
|
39
|
-
| command | 11 |
|
|
40
|
-
| tool | 11 |
|
|
41
|
-
| shortcut | 2 |
|
|
42
|
-
| renderer | 2 |
|
|
43
|
-
| eventbus | 1 |
|
|
44
|
-
| workflow | 4 |
|
|
37
|
+
| Surface kind | Configured variants | Available by default |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| command | 11 | 11 |
|
|
40
|
+
| tool | 11 | 11 |
|
|
41
|
+
| shortcut | 3 | 2 |
|
|
42
|
+
| renderer | 2 | 2 |
|
|
43
|
+
| eventbus | 1 | 1 |
|
|
44
|
+
| workflow | 4 | 4 |
|
|
45
45
|
|
|
46
46
|
Public commands: `/bg`, `/bg-clear`, `/bg-tasks`, `/bg-update`, `/claude-cache`, `/fusion`, `/fusion-models`, `/jobs`, `/kill`, `/logs`, `/tasks`.
|
|
47
47
|
|
|
48
48
|
Public tools: `bg_delegate`, `bg_kill`, `bg_logs`, `bg_result`, `bg_run`, `bg_run_pi_attested`, `bg_status`, `fusion_investigate`, `fusion_reason`, `fusion_research`, `fusion_validate`.
|
|
49
49
|
|
|
50
|
+
### Configuration-dependent surfaces
|
|
51
|
+
|
|
52
|
+
| Surface | Availability | Default |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| `command:claude-cache` | `feature:attribution` | yes |
|
|
55
|
+
| `command:fusion` | `feature:fusion` | yes |
|
|
56
|
+
| `command:fusion-models` | `feature:fusion` | yes |
|
|
57
|
+
| `tool:bg_delegate` | `feature:delegate` | yes |
|
|
58
|
+
| `tool:bg_result` | `any(feature:delegate,feature:fusion)` | yes |
|
|
59
|
+
| `tool:bg_run_pi_attested` | `feature:attested` | yes |
|
|
60
|
+
| `tool:fusion_investigate` | `feature:fusion` | yes |
|
|
61
|
+
| `tool:fusion_reason` | `feature:fusion` | yes |
|
|
62
|
+
| `tool:fusion_research` | `feature:fusion` | yes |
|
|
63
|
+
| `tool:fusion_validate` | `feature:fusion` | yes |
|
|
64
|
+
| `shortcut:ctrl+alt+b` | `dock:ctrl+alt+b` | no |
|
|
65
|
+
| `shortcut:shift+down` | `dock:shift+down` | yes |
|
|
66
|
+
| `renderer:fusion-result` | `feature:fusion` | yes |
|
|
67
|
+
| `workflow:investigate` | `feature:fusion` | yes |
|
|
68
|
+
| `workflow:reason` | `feature:fusion` | yes |
|
|
69
|
+
| `workflow:research` | `feature:fusion` | yes |
|
|
70
|
+
| `workflow:validate` | `feature:fusion` | yes |
|
|
71
|
+
|
|
50
72
|
Full owner map and generated contracts live in [docs/INDEX.md](docs/INDEX.md).
|
|
51
73
|
<!-- pi-docs:end name="readme-public-surfaces" -->
|
|
52
74
|
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
“Available by default” means after Pi has initialized extensions. Normal TUI/RPC/print/JSON modes do this; SDK embedders must provide a counted `bindExtensions()` binding and ensure post-reload binding. Bare `createAgentSession()` and empty/mode-only reload are blocked by the current public host lifecycle API. See [Getting started](docs/getting-started.md#sdk-embedding-requirement).
|
|
55
76
|
|
|
56
77
|
## Why use it?
|
|
57
78
|
|
|
58
79
|
| You want to... | Use this package because... |
|
|
59
80
|
|---|---|
|
|
60
|
-
| Start a dev server, watch build, migration dry run, or long check | `bg_run` and `/bg` return immediately, write durable output files, show a footer dock, and notify on terminal state. |
|
|
81
|
+
| Start a dev server, watch build, migration dry run, or long check | `bg_run` and `/bg` return immediately, write durable output files, show a footer dock, and notify on terminal state. Ordinary `isAgent:false` jobs can explicitly keep the same live execution across a real same-process reload. |
|
|
61
82
|
| Let Pi keep working instead of sleeping or polling | Default `bg_run` completion delivery sends a durable terminal notification and can wake a follow-up turn. |
|
|
62
83
|
| Ask a second agent to inspect the repo with the current conversation as context | `bg_delegate` starts one read/search/list child, isolated from ambient extensions by default; `bg_result` verifies the committed result before returning it. |
|
|
63
84
|
| Compare model perspectives without exposing arbitrary parent context | Fusion children receive only the workflow input and fixed tool policy; no silent route substitution or fallback is used on delegate/Fusion paths. |
|
|
64
85
|
| Produce local evidence for a direct Pi run | `bg_run_pi_attested` records local same-user-writable artifacts and hashes after a successful structured child Pi task. |
|
|
65
|
-
| Use Anthropic subscription OAuth consistently | The
|
|
86
|
+
| Use Anthropic subscription OAuth consistently | The default ambient provider applies attribution and exact-match sanitization; `/claude-cache` shows or changes session cache retention. Isolated package-owned Anthropic children always use the mandatory child entrypoint. |
|
|
66
87
|
|
|
67
88
|
## Install
|
|
68
89
|
|
|
@@ -97,9 +118,9 @@ Local paths are loaded from disk without copying; use the path to this package f
|
|
|
97
118
|
/bg --name "Typecheck watch" npm run typecheck -- --watch
|
|
98
119
|
```
|
|
99
120
|
|
|
100
|
-
`/bg` starts a tracked shell task and returns the task id plus output path. User-launched `/bg` tasks notify in the UI but do not automatically wake a follow-up model turn.
|
|
121
|
+
`/bg` starts a tracked shell task and returns the task id plus output path. User-launched `/bg` tasks notify in the UI but do not automatically wake a follow-up model turn. Add one leading `--survive-reload` only when this ordinary shell process must keep running across a real same-process `/reload`; default tasks are still killed.
|
|
101
122
|
|
|
102
|
-
3. Open the footer dock with **Shift↓** or list tasks:
|
|
123
|
+
3. Open the footer dock with the default **Shift↓** binding or list tasks:
|
|
103
124
|
|
|
104
125
|
```text
|
|
105
126
|
/jobs
|
|
@@ -135,13 +156,34 @@ Local paths are loaded from disk without copying; use the path to this package f
|
|
|
135
156
|
|
|
136
157
|
More walkthrough detail: [Getting started](docs/getting-started.md).
|
|
137
158
|
|
|
159
|
+
## Select capabilities and avoid shortcut conflicts
|
|
160
|
+
|
|
161
|
+
The default remains the complete historical surface. To run only process tasks, set:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
PI_BG_FEATURES=process pi
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`PI_BG_FEATURES` accepts a strict unique comma-separated set from `process,delegate,fusion,attested,attribution`; `process` is mandatory. `bg_result` is derived and appears exactly once when delegate or Fusion is enabled. Disabled tools, commands, renderers, and ambient attribution are absent from registration and remain absent after reload.
|
|
168
|
+
|
|
169
|
+
Choose the dock key independently:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
PI_BG_DOCK_SHORTCUT=ctrl+alt+b pi # avoid a Shift+Down owner
|
|
173
|
+
PI_BG_DOCK_SHORTCUT=off pi # use /tasks or /bg-tasks
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Accepted values are exactly `shift+down` (default), `ctrl+alt+b`, and `off`. Invalid settings fail startup with `pi_bg_config_invalid`; they do not silently restore defaults. `/tasks`, `/bg-tasks`, and the separate `Ctrl+Alt+C` clear fallback remain available. These flags select functionality; they do not claim a cold-start performance improvement.
|
|
177
|
+
|
|
178
|
+
Full contract: [Configuration](docs/operations/configuration.md) and [Shortcuts and dock](docs/reference/shortcuts-and-dock.md).
|
|
179
|
+
|
|
138
180
|
## Pick the right workflow
|
|
139
181
|
|
|
140
182
|
| Workflow | Blocking? | Context | Tools/network/write boundary | Best for | Expected behavior |
|
|
141
183
|
|---|---:|---|---|---|---|
|
|
142
184
|
| Ordinary foreground Pi work | Yes | Full current session | Whatever tools the active session has | Short reads/edits/commands where you want live back-and-forth | Pi waits for the work before responding. |
|
|
143
|
-
| `/bg` | No | No model child unless your command starts one | Runs your shell command; **not sandboxed** | User-started local commands, servers, watches | UI notification and footer tracking; `/bg` uses notification-only by default. |
|
|
144
|
-
| `bg_run` | No | No model child unless command starts one | Runs your shell command; **not sandboxed** | Agent-started long commands | Returns task id/output path; defaults to notification plus automatic follow-up wake. For an Anthropic child `pi`, do not pass `--no-extensions` unless you also explicitly load this package's attribution extension. |
|
|
185
|
+
| `/bg` | No | No model child unless your command starts one | Runs your shell command; **not sandboxed** | User-started local commands, servers, watches | UI notification and footer tracking; `/bg` uses notification-only by default. Optional `--survive-reload` is ordinary-shell-only. |
|
|
186
|
+
| `bg_run` | No | No model child unless command starts one | Runs your shell command; **not sandboxed** | Agent-started long commands | Returns task id/output path; defaults to notification plus automatic follow-up wake. Optional `surviveReload:true` requires `isAgent:false`. For an Anthropic child `pi`, do not pass `--no-extensions` unless you also explicitly load this package's attribution extension. |
|
|
145
187
|
| `bg_delegate` + `bg_result` | No launch; retrieval is point-in-time | Frozen visible conversation projection | Inspect-only child: read, grep, find, ls, artifact read; no shell, writes, network, recursion | Context-aware read-only investigation while parent continues | Launch returns immediately; result is committed by child and hash-verified by retrieval. |
|
|
146
188
|
| `bg_run_pi_attested` | No | Prompt passed to one direct child Pi run | Direct `pi --mode json`; no shell command; writes requested report path | Evidence-oriented direct Pi task | Emits local attestation sidecar only after successful completion. |
|
|
147
189
|
| `/fusion` / `fusion_reason` | Background launch; point-in-time `bg_result` retrieval | Versioned conversation projection plus prompt | Candidates/evaluator/repair/merger run with no tools | Self-contained reasoning and synthesis | Returns after durable preflight; three candidates → blind evaluator → optional bounded repair → merger. |
|
|
@@ -160,13 +202,14 @@ See [Choose a workflow](docs/choose-a-workflow.md) for a decision tree and trade
|
|
|
160
202
|
"name": "Docs preview",
|
|
161
203
|
"command": "npm run docs:dev",
|
|
162
204
|
"isAgent": false,
|
|
163
|
-
"timeoutSeconds": 3600
|
|
205
|
+
"timeoutSeconds": 3600,
|
|
206
|
+
"surviveReload": true
|
|
164
207
|
}
|
|
165
208
|
```
|
|
166
209
|
|
|
167
|
-
Expected: returns immediately with a task id, PID when available, and `.pi/tasks/...output`. The command runs as an ordinary local shell command with your user permissions; it can invoke networked tools or paid services if the command itself does so.
|
|
210
|
+
Expected: returns immediately with a task id, PID when available, and `.pi/tasks/...output`. Here the explicit flag retains that exact child/id/PID/nonce/path, launch policy, absolute timeout, and cumulative cap across a supported real reload. Omit it for compatible kill-on-reload behavior. New/resume/fork/clone/quit, hard crash, process restart, empty/mode-only SDK reload, and direct `AgentSession.dispose()` are not supported survival paths. The command runs as an ordinary local shell command with your user permissions; it can invoke networked tools or paid services if the command itself does so.
|
|
168
211
|
|
|
169
|
-
If `bg_run` starts an Anthropic child `pi`, keep normal extension discovery enabled. Do not add `--no-extensions` unless the command also supplies this package's `extensions/anthropic-attribution.ts` via `-e`/`--extension`; `bg_run` does not rewrite arbitrary shell argv.
|
|
212
|
+
If `bg_run` starts an Anthropic child `pi`, keep normal extension discovery enabled when ambient attribution is enabled. Do not add `--no-extensions` unless the command also supplies this package's always-on `extensions/anthropic-attribution-child.ts` via `-e`/`--extension`; `bg_run` does not rewrite arbitrary shell argv.
|
|
170
213
|
|
|
171
214
|
### `bg_delegate`: context-seeded read-only investigation
|
|
172
215
|
|
|
@@ -263,11 +306,11 @@ Use with `fusion_validate` for advisory read-only review.
|
|
|
263
306
|
<img src="docs/assets/footer-dock.svg" alt="Illustration of the pi-background-tasks footer dock with running and completed tasks" width="760">
|
|
264
307
|
</p>
|
|
265
308
|
|
|
266
|
-
When tasks are running or unseen completions exist, the footer shows a compact `bg ...` segment. Press **Shift↓** to open the focused bottom dock. Use `/bg-clear` to acknowledge finished-task footer notices in any terminal.
|
|
309
|
+
When tasks are running or unseen completions exist, the footer shows a compact `bg ...` segment. Press the configured **Shift↓** (default) or **Ctrl+Alt+B** binding to open the focused bottom dock, or use `/tasks` when the key is off. Use `/bg-clear` to acknowledge finished-task footer notices in any terminal.
|
|
267
310
|
|
|
268
311
|
| Control | Action |
|
|
269
312
|
|---|---|
|
|
270
|
-
| `Shift↓` | Open the dock |
|
|
313
|
+
| configured `Shift↓` / `Ctrl+Alt+B`, or `/tasks` | Open the dock |
|
|
271
314
|
| `/bg-clear` | Clear finished-task notices |
|
|
272
315
|
| `↑` / `↓`, `PageUp` / `PageDown` | Move through list or scroll output tail |
|
|
273
316
|
| `Enter` / `→` | Inspect details |
|
|
@@ -299,7 +342,7 @@ Agent tasks launched through `pi -p ...` or `pi --mode json ...` and marked `isA
|
|
|
299
342
|
- Shell jobs are tracked by the package, but they are not sandboxed. Treat commands as local processes with your permissions and credentials.
|
|
300
343
|
- Delegate and Fusion child Pi processes are route-pinned where applicable; delegate/Fusion paths do not silently substitute routes.
|
|
301
344
|
- Fusion uses direct child `pi --mode text` processes, not direct completion APIs. Frontier Fusion routes are admitted only through Pi Anthropic or Codex subscription OAuth; metered frontier API credentials are rejected before child creation.
|
|
302
|
-
- Normal installations
|
|
345
|
+
- Normal installations load a feature-aware ambient Claude Code OAuth attribution/sanitization entrypoint for parent Anthropic sessions; non-Anthropic sessions are unchanged. The default enables it, while omitting `attribution` removes its provider/hooks and `/claude-cache`. Isolated Fusion, delegate, and attested Anthropic children load the separate always-on child entrypoint explicitly before their guard/governor, regardless of that ambient flag. It requests `ttl: "1h"` on system/tool/conversation cache breakpoints before serialization and preserves provider-reported `cacheWrite1h` evidence. Set `PI_CACHE_RETENTION=short|none|long` or use `/claude-cache` when ambient attribution is enabled; malformed attribution, policy, cache evidence, or non-OAuth credentials fail before transport. Provider usage is preserved verbatim, but subscription OAuth can report `cacheWrite1h = 0` even when a unique cache remains readable beyond five minutes; treat positive `cacheWrite1h` as definitive and zero as inconclusive on that channel. Anthropic budgeting follows the provider's 200K subscription policy.
|
|
303
346
|
- Fusion research fetches only caller-supplied public `http(s)` URLs with bounded retrieval. It is not web search and not a secret-exfiltration boundary.
|
|
304
347
|
- Attestation sidecars are local, unsigned, same-user-writable evidence. They are useful for downstream local gates, but not cryptographic proof against local compromise, a compromised Pi binary, or a compromised provider.
|
|
305
348
|
- Metadata, attestations, delegate/Fusion artifacts, and configuration replacements use write/fsync/rename durability patterns. Failed/cancelled stored Fusion runs also have a manifest-bound `failure-summary.json` containing bounded no-answer evidence metadata and artifact refs only; `bg_result` returns it as an answer-free typed terminal view after integrity checks. Ordinary task output is closed and drained before terminal publication but is not explicitly fsynced. POSIX directory entries are fsynced after atomic replacement; Windows lacks the same portable directory-entry crash-durability guarantee.
|
|
@@ -332,3 +375,5 @@ Operations are `capabilities`, `run`, `status`, `logs`, and `kill`. This is the
|
|
|
332
375
|
## Contributing
|
|
333
376
|
|
|
334
377
|
Keep user-facing claims tied to source. If you change public schemas, command behavior, durability, model routing, or environment variables, update these package-local docs in the same change and run focused checks appropriate to the edit.
|
|
378
|
+
|
|
379
|
+
For startup work, use `scripts/benchmark-cold-load.mjs` with an owned output/scratch root, explicit `--runtime source|compiled`, and at least 30 fresh-process samples; the exact command and interpretation rules are in [Testing operations](docs/operations/testing.md#cold-load-measurement-discipline). Its “cold” result means an empty JavaScript/Jiti module cache, not a flushed filesystem cache. It is distribution evidence, not a flaky CI threshold. Published Pi entrypoints use precompiled JavaScript; disabled delegate/Fusion/attribution lanes, the dock UI, and attested execution stay outside process-only static startup. Native-Windows and vendor compiled-Bun timing still require their own evidence.
|
package/TESTING.md
CHANGED
|
@@ -10,6 +10,14 @@ The historical monorepo extension standards are contextual background only; do n
|
|
|
10
10
|
|
|
11
11
|
## Current commands
|
|
12
12
|
|
|
13
|
+
Compiled runtime build:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm run build:runtime
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This produces the published `dist/` JavaScript entrypoints and their complete deferred runtime closure from authoritative TypeScript. SDK/package/smoke/release scripts build it before exercising compiled distribution paths.
|
|
20
|
+
|
|
13
21
|
Default gate:
|
|
14
22
|
|
|
15
23
|
```bash
|
|
@@ -82,7 +90,7 @@ npm run pack:dry-run
|
|
|
82
90
|
npm run test:compat
|
|
83
91
|
```
|
|
84
92
|
|
|
85
|
-
Current smoke
|
|
93
|
+
Current smoke builds the runtime and then runs `tsx scripts/smoke.ts`. It creates a temporary Pi agent/session directory, sets offline/telemetry-suppression environment variables, and runs the compiled package entrypoints with `/jobs`.
|
|
86
94
|
|
|
87
95
|
`npm run smoke:large-context` is the Fusion context-policy evidence harness. It rebuilds the byte composition of the production failure (696,929 B tool results, 251,508 B tool arguments, 34,959 B user text, 24,733 B assistant text, 10,303 B thinking) as a real `SessionManager` branch, then prints:
|
|
88
96
|
|
|
@@ -183,6 +191,13 @@ CI=1
|
|
|
183
191
|
|
|
184
192
|
Tests must not use the user's real `~/.pi/agent`.
|
|
185
193
|
|
|
194
|
+
### Trustworthy package verification
|
|
195
|
+
|
|
196
|
+
`test:type-safety` uses the TypeScript parser, not line regexes. Its direct adversarial corpus separates executable type escapes from comment prose and string/template/regex contents, while retaining compiler-recognized suppression comments and multiline/parenthesized assertion coverage. Parentheses and type-transparent `satisfies` wrappers cannot hide a directly nested assertion. Assertions split through an identifier are intentionally outside this syntactic rule so a separately validated `unknown` boundary is not banned; the gate does not claim whole-program type soundness.
|
|
197
|
+
|
|
198
|
+
The package URL guard separates syntax-wide discovery of self-contained file-URL `pathname` hazards from finite flow-sensitive provenance at each use. Sequential overwrites replace prior provenance; feasible branches and distinct normal/break/continue/return/explicit-throw completions merge without discarding live states; and `finally` is inspected for every incoming completion. Static strings and static-head templates use the WHATWG URL parser's preprocessing and absolute-first behavior, including leading C0/space and TAB/CR/LF handling; proven absolute first values ignore bases, while `./` and `../` template heads use the explicit base. Direct/computed access and declaration, assignment, and statically known `for...of` destructuring are covered. Exact static keys distinguish lowercase `import.meta['url']` from uppercase intrinsic `globalThis['URL']`, while compiler-resolved parameter/local `globalThis` bindings stay controls. Any explicitly traced may-file branch is rejected. An unknown dynamic first value remains unknown but, when a base is explicit, also joins that base's possible provenance because it may be relative; known absolute HTTP(S) values remain non-file controls. Calls, implicit exceptions, interprocedural/later-closure effects, and heap/proxy mutation are not executed or inferred; this is a bounded source-policy guard, not complete JavaScript dataflow analysis.
|
|
199
|
+
|
|
200
|
+
The local-tarball consumer test does not borrow a warm user npm cache or npm configuration. It first proves a truly empty isolated cache fails with `ENOTCACHED`, then archives the already-published files in the installed `turndown` production closure through the public portable `tar` API bundled with the active npm CLI, without invoking npm's directory-pack lifecycle. A task-owned package with executable `prepack`, `prepare`, and `postpack` hooks proves plain packing attempts lifecycle code, characterizes npm 10.9.3's unsafe `--ignore-scripts` prepare behavior versus npm 11.13.0, and then proves the fixture archive path runs no hook on either line. The installed source hashes and original script-bearing manifest bytes must remain unchanged. The resulting real closure is exposed through a temporary loopback-only registry to seed a separate empty task-owned cache. Every npm subprocess uses explicit task-owned user/global config, a task-owned project cwd with empty project config, `GIT_ALLOW_PROTOCOL=file`, and absolute filesystem operands. A no-request config probe demonstrates that a hostile scoped global/project registry would win without isolation but is absent from the effective preparation environment. After the registry is closed, consumer installation runs with `--offline`; the test checks exact Turndown/Domino versions and performs a real HTML-to-Markdown conversion to prove the transitive dependency loads. Missing registry inputs, failed cache preparation, lifecycle execution, source mutation, or missing runtime dependencies are hard failures.
|
|
186
201
|
|
|
187
202
|
Fusion-specific targeted gates:
|
|
188
203
|
|
|
@@ -198,6 +213,31 @@ npm run test:agent-loop
|
|
|
198
213
|
|
|
199
214
|
The Fusion SDK/RPC/scripted-provider tests install a deterministic fake child `pi` in a temp `PATH` from `tests/helpers/fusion-fake-pi.ts`. Parent Pi remains the real SDK/RPC runtime; only direct child `pi --mode text` calls with the package-owned private compact metadata extension are intercepted. SDK coverage proves the Fusion tools return after durable no-child preflight without waiting for delayed children, transfer cancellation ownership away from the completed tool call, remain tracked through status/dock/kill/notification, verify manifest-bound `result.json` plus `merged.md`, and attach complete usage on the first `bg_result` retrieval exactly once. The scripted provider proves launch → no-poll parent response → terminal wake → `bg_result` → verified answer. `PI_CODING_AGENT_DIR` is pointed at the temp agent directory so `fusion-models.json` is never read from the user's real global Pi directory. Fusion v1 public-surface coverage asserts exactly four tools (`fusion_reason`, `fusion_investigate`, `fusion_research`, `fusion_validate`), no public capability argument, retired-tool active-tool removal, `/fusion` mapping to reason/no-tool candidates, closed schemas with Google-compatible enum status, targeted URL fetch not search, URL exfiltration warnings, strict validation verification rules, historical v4 rendering without old-tool activation, and actionable migration errors for `fusion_validate({prompt})`. Fusion context coverage covers both reason conversation projection and clean-task non-interference invariants, including parent-sentinel absence from every clean downstream prompt/artifact and byte-identical clean inputs across unrelated parent sessions. `tests/unit/fusion-context-prompts.test.ts` verifies that a synthetic session carrying more than 1 MB of tool arguments/results still yields a small canonical input, that user and assistant text survive verbatim, that thinking and tool payloads never appear (including no head/tail/preview sentinel), that omission counts, byte totals, and hashes are exact and stable, that repeated construction is byte-identical, that the active Fusion tool leaf and sibling calls stay scope-excluded, that images remain marker-only or ledger-only with no raw base64 in child prompts, and that every retained source block receives exactly one disposition. `tests/unit/fusion-high-cardinality.test.ts` covers the receipt-cardinality regression: a session of many short interleaved tool events (rather than a few enormous results) pins per-receipt cost, proves the compact `omitted_activity` fields are the only model-facing ones, reconciles every receipt against the ledger through `projection_map`, and proves the whole workflow fits a real route budget. `tests/unit/fusion-budget.test.ts` covers stage budgets and stage-local refusal wording; orchestrator coverage derives terminal run progress from durable attempts after usage persistence so late evaluator/merge refusal reports completed, failed, cancelled, and not-started truth instead of claiming zero children. Each route reserves the larger of Fusion's output contract and the model's declared maximum output, the limiting model is selected by conservative byte capacity (including when it is the evaluator rather than a candidate), unknown or too-small capacities fail before spawn, boundary prompts pass at exactly the limit and fail one byte past it, the child system prompt counts as input, dense multi-byte UTF-8 cannot bypass byte accounting, and candidate, evaluator, evaluation-repair, and merger expansions are each rejected before their child is spawned with zero partial launches. `tests/unit/fusion-pi-child.test.ts` covers the post-launch `fusion-runtime-guard.v2` protocol, BUG-185 removal of live token/output-reservation admission, stable payload normalization, 550-request/600-tool limits enforced by child guards and parent-sealed evidence, the 32 MiB aggregate tool-result ceiling enforced at both boundaries, malformed or duplicate evidence rejection, typed parent errors, and failed audit sealing. `tests/unit/fusion-claude-cache.test.ts` pins native pre-serialization `ttl: "1h"` requests, explicit short/none/long policy, call-level compaction opt-out, model compatibility fallback, non-mutation, the four-breakpoint ceiling, subscription prompt-caching-scope beta idempotence, malformed-control refusal, and distinct `child_cache_policy_invalid` parent errors. Child argv tests pin the package attribution/sanitization → runtime-governor order; root attribution tests pin linked OAuth account/device/session metadata, all exact-match sanitizer variants, beta-resource request shape, cache surfaces, one-hour provider usage pricing, duplicate-owner suppression, and the 200K subscription policy. Compact/result usage tests preserve Anthropic `cacheWrite1h` and provider `reasoning` subsets, and child metadata binds each requested/effective payload observation plus JSON-rendered output accounting in `fusion-child-result.v4`. Live subscription-OAuth acceptance on 2026-08-04 proved cold writes plus exact-repeat reads on Sonnet 4.5 and Opus 5 while both reported `cacheWrite1h = 0`. Separate normal-spawn and exact Fusion-child Opus 5 controls—with unique prompts—still read their caches after 370 idle seconds; the Fusion control wrote and reread 9,922 tokens through the attribution/sanitization → governor path. Documentation therefore treats positive `cacheWrite1h` as definitive but zero as inconclusive on subscription OAuth; payload intent, provider itemization, and behavioral lifetime remain separate evidence. Terminal `fusion-child-settlement.v3` is published only at `agent_settled`; recovered non-final provider errors must be zero-content/zero-usage retry markers named by that settlement, and one non-final candidate `stop` is accepted only when it is a hash-bound oversized original immediately followed by a same-session replacement. Failed/cancelled Fusion coverage also verifies canonical manifest-bound `failure-summary.json` evidence with no stage-output bodies, truthful classifications/omission receipts, subordinate one-shot summary persistence after `writeError`, and typed answer-free `bg_result` terminal views that never claim usage or expose partial output. `tests/scripted-provider/fusion-output-recovery.test.ts` drives a real Pi print-mode process and proves one PID/session context, one queued continuation before settlement, tool removal on turn two, original-artifact preservation, and replacement-only stdout. Missing/duplicate/tampered/failed settlements or substantive error records remain fatal. `tests/scripted-provider/fusion-runtime-guard.test.ts` drives a real Pi agent loop through Pi's `openai-codex-responses` adapter against local HTTP and proves provider-payload transforms chain in load order and `ctx.abort()` prevents transport for the execution/cache-policy refusals that remain. `tests/package/typebox-compat.test.ts` pins the TypeBox posture and compiles nullable-array schemas. The release-only `npm run test:compat` packs the package, installs exact supported Pi versions, runs `/jobs`, runs `/fusion` through the installed package entrypoint with the fake child Pi, verifies five child invocations, verifies `/fusion-models` rejects non-TUI mode, requires each supported Pi line to declare terminal `agent_settled` and `before_provider_request`, verifies the installed Anthropic adapter exposes cache breakpoints plus long/tool compatibility controls, asserts the resolved `typebox` is Pi's bundled peer rather than a private or nested copy, and scans the installed package bytes for TypeBox APIs removed in the 1.3.x line. Pi 0.75.5 is intentionally unsupported because it lacks the terminal event required to seal a Fusion audit after retries and compaction. It then drives the current host Pi through a real RPC `fusion_reason` parent-agent loop, checks the persisted tool result carries the complete Pi `Usage.cost` object, invokes `get_session_stats` (the same aggregation boundary used by the TUI footer), reopens the durable session, and verifies identical token/cost totals. All parent and child inference remains deterministic and local.
|
|
200
215
|
|
|
216
|
+
### D1 reload-survival gates
|
|
217
|
+
|
|
218
|
+
D1 is opt-in ordinary-shell behavior. Run it with isolated roots and serial real lifecycle tests:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
node node_modules/tsx/dist/cli.mjs --test --test-concurrency=1 \
|
|
222
|
+
tests/unit/reload-shell-owner.test.ts \
|
|
223
|
+
tests/unit/registry.test.ts \
|
|
224
|
+
tests/unit/extension-api.test.ts \
|
|
225
|
+
tests/unit/windows-taskkill.test.ts \
|
|
226
|
+
tests/unit/posix-invariance.test.ts
|
|
227
|
+
node node_modules/tsx/dist/cli.mjs --test --test-concurrency=1 \
|
|
228
|
+
tests/sdk/reload-survival-sdk.test.ts \
|
|
229
|
+
tests/sdk/sdk.test.ts \
|
|
230
|
+
tests/sdk/lifecycle-sdk.test.ts \
|
|
231
|
+
tests/sdk/fusion-sdk.test.ts
|
|
232
|
+
npm run test:component
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The real SDK suite uses counted `AgentSession.reload()` and actual POSIX processes for same-child continuity, a deterministic nonzero loader gap, fresh status/log/kill and slash commands, repeated handoff, original timeout, shell-policy refresh, leader-exit plus TERM-ignoring descendant proof, default kill control, copied-JSON nonadoption, and actual runtime new/switch/fork/clone/dispose refusal. Owner/registry units pin structural global reuse, exact identity, two-phase claims, stale/conflict errors, fixed no-claim cleanup, admission commit, cumulative cap, publication attempts, retention/reentrancy inheritance, and injected Windows taskkill ownership. Permanent race cases additionally prove that failed post-spawn admission retains its real child authority until natural terminal settlement, a reentrant reload-detach emitter throw remains pending for fresh cumulative attempt two, and a child closing after the deadline stop wait releases the orphaned slot so a later activation can bind.
|
|
236
|
+
|
|
237
|
+
`tests/windows/windows-integration.test.ts` remains the native qualification gate and deliberately fails off Windows. Mocked Windows branches do not count as native evidence. D1 remains blocked for Windows until native continuity, post-reload descendant kill, original timeout/cap, gap completion, helper/handle uniqueness, and no-claim tree cleanup all pass. Pi 0.84/0.86 empty or mode-only binding after reload and direct `AgentSession.dispose()` are permanent host-characterization cases, not passes; normal modes/counted binding and `AgentSessionRuntime.dispose()` are supported.
|
|
238
|
+
|
|
239
|
+
Passing real-process cases contain no success-path rescue. A failure-only `finally` rescue must be disclosed if an assertion fails, and every fixture PID/group must be gone before its temporary root is removed.
|
|
240
|
+
|
|
201
241
|
## Coverage summary
|
|
202
242
|
|
|
203
243
|
Implemented coverage includes:
|
|
@@ -213,7 +253,7 @@ Implemented coverage includes:
|
|
|
213
253
|
- durability: `tests/unit/durable-fs.test.ts` covers the shared `src/core/durable-fs.ts` primitive used for metadata, event/stderr buffers, attestations, delegate/Fusion artifacts, and configuration — single-open write/sync/close ordering, exclusive `wx` temp creation at `0o600`, direct `w` writes with inherited mode, never reopening a pathname merely to flush it, temp ownership, primary-versus-cleanup error precedence, `renameCompleted` after a post-rename directory failure, the Windows directory-sync skip, atomic replacement, and fatal `fsync` failures. Ordinary task `.output` is a streaming file ended/drained before terminal metadata and is not explicitly fsynced. `tests/package/package.test.ts` mutation guards pin the durable-helper invariants
|
|
214
254
|
- safety: kill, already-finished kill failure, timeout failure, spawn failure, low output-cap failure, multi-task shutdown cleanup, POSIX process-group kill fallback, Windows `taskkill /T` then `/T /F` tree termination with shared soft attempts, soft-abort-on-force, exit-128 race tolerance, loud force failures, no root-only fallback, SIGKILL escalation that terminates instead of re-arming (a SIGKILL never schedules a further escalation, and concurrent stop requests share exactly one escalation timer that is cleared on finalize), duplicate finalization/notification races, metadata/notification failure handling, and pruning
|
|
215
255
|
- agent loop: deterministic scripted-provider coverage against `extensions/background-tasks.ts` for actual event-driven `bg_run` behavior. The provider observes the effective system prompt, public tool descriptions, and real launch receipt and deliberately emits the pre-fix `bg_status` poll if any contract layer is absent; the passing path proves one launch, no sleep/status/log polling, one durable terminal notification, and exactly one follow-up turn. It also covers notification-only `triggerOnCompletion:false`, `/bg` display-only behavior, `notifyOnCompletion:false`, failed-task notification error fields, and parent-model `fusion_reason` tool use followed by normal parent response
|
|
216
|
-
- package: manifest, docs, ordered `pi.extensions`, exported `src/core/extension-api.ts`, registry-only production dependencies, peer dependency/import parity, packed runtime files/notices, tarball-install smoke, direct-completion import bans, test/helper/script/artifact exclusion, isolated offline npm installation, exact-version compatibility, and current-host persisted/replayed tool-usage safety
|
|
256
|
+
- package: manifest, docs, ordered `pi.extensions`, exported `src/core/extension-api.ts`, registry-only production dependencies, peer dependency/import parity, packed runtime files/notices, tarball-install smoke, direct-completion import bans, test/helper/script/artifact exclusion, compiler-tree type-escape and file-URL provenance guards, explicitly prepared isolated offline npm installation with an empty-cache negative control and real transitive load, exact-version compatibility, and current-host persisted/replayed tool-usage safety
|
|
217
257
|
|
|
218
258
|
## PTY notes
|
|
219
259
|
|
package/TEST_PLAN.md
CHANGED
|
@@ -13,7 +13,7 @@ The table below remains the exhaustive acceptance source. Do not replace it with
|
|
|
13
13
|
| Field | Value |
|
|
14
14
|
|---|---|
|
|
15
15
|
| Package | `pi-background-tasks` |
|
|
16
|
-
| Extension entrypoints | `extensions/anthropic-attribution.
|
|
16
|
+
| Extension entrypoints | `dist/extensions/anthropic-attribution.js`, `dist/extensions/background-tasks.js` (compiled from authoritative `extensions/*.ts`) |
|
|
17
17
|
| Public commands | `/bg`, `/jobs`, `/logs`, `/kill`, `/tasks`, `/bg-tasks`, `/bg-clear`, `/bg-update`, `/claude-cache`, `/fusion`, `/fusion-models` |
|
|
18
18
|
| Public tools | `bg_run`, `bg_delegate`, `bg_result`, `bg_run_pi_attested`, `bg_status`, `bg_logs`, `bg_kill`, `fusion_reason`, `fusion_investigate`, `fusion_research`, `fusion_validate` |
|
|
19
19
|
| Extension EventBus API | `pi-background-tasks:request:v1`, `pi-background-tasks:response:v1`, `pi-background-tasks:terminal:v1`; schemas exported from `src/core/extension-api.ts` |
|
|
@@ -28,6 +28,7 @@ The table below remains the exhaustive acceptance source. Do not replace it with
|
|
|
28
28
|
| Gate | Command | Required in default `npm run test`? | Status |
|
|
29
29
|
|---|---|---:|---|
|
|
30
30
|
| Typecheck | `npm run typecheck` | yes | implemented |
|
|
31
|
+
| Type safety | `npm run test:type-safety` | yes | implemented; compiler syntax-tree checks distinguish real top types, directly nested assertions through parentheses/`satisfies`, production non-null expressions, and compiler-recognized suppression comments from inert text; alias-separated assertions remain outside this syntactic rule |
|
|
31
32
|
| Unit | `npm run test:unit` | yes | implemented |
|
|
32
33
|
| SDK | `npm run test:sdk` | yes | implemented |
|
|
33
34
|
| RPC | `npm run test:rpc` | yes | implemented |
|
|
@@ -65,7 +66,8 @@ SDK/RPC/scripted-provider/package/compatibility coverage asserts exactly four to
|
|
|
65
66
|
|
|
66
67
|
| Feature | Public surface | Unit | SDK | RPC | Component | PTY | Package | Scripted provider | Notes |
|
|
67
68
|
|---|---|---:|---:|---:|---:|---:|---:|---:|---|
|
|
68
|
-
| Start background command from UI command | `/bg` | yes |
|
|
69
|
+
| Start background command from UI command | `/bg` | yes | D1 real lifecycle | yes | | yes | | | Unit covers `--name`, `--agent`, one bare `--survive-reload`, `--` literal handling, duplicate/assignment refusal, and agent conflict; RPC/PTY start default real processes. D1 SDK invokes the real command across reload and fresh `/jobs`, `/logs`, `/kill`. |
|
|
70
|
+
| Opt-in same-process ordinary shell reload survival (D1/#6) | `bg_run.surviveReload`, `/bg --survive-reload`, `reload-shell-owner.v1` | yes | yes | EventBus terminal only | rerun flag | default dock controls | source/docs payload | | Default remains kill-on-reload. Unit pins structural process-global identity `(pid, exact session, real cwd)`, random nonces/generations, two-phase claim, stale/conflict/incompatible errors, admission commit, fixed deadline plus late-close slot release, failed-admission authority retention, copied-JSON/PID nonadoption, original timeout/cumulative cap/policy, reentrant-handoff cumulative R1 publication/notification state, POSIX proof, and injected Windows taskkill. Real counted SDK covers same child/id/PID/nonce/path/output over repeated `AgentSession.reload()`, nonzero gap completion, commands/tools, original timeout, policy refresh, leader-exit/TERM-ignoring descendant, and actual runtime new/switch/fork/clone/dispose kill. V1 request remains closed and cannot request survival; delegate/Fusion/managed/attested inputs refuse. Empty/mode-only SDK reload and direct `AgentSession.dispose()` are blocked upstream; crash/restart is unsupported. Native Windows closure requires six real cases and is not certified by mocks. |
|
|
69
71
|
| List tasks | `/jobs` | | | yes | | | | | RPC asserts running and killed task rows. |
|
|
70
72
|
| Show bounded logs | `/logs <id> [maxBytes]` | yes | | yes | | | | | Unit covers bounded reads; RPC verifies output/path. |
|
|
71
73
|
| Kill running task | `/kill <id>` | | yes | yes | | | | | SDK tool and RPC slash command. |
|
|
@@ -77,7 +79,7 @@ SDK/RPC/scripted-provider/package/compatibility coverage asserts exactly four to
|
|
|
77
79
|
| Stop task from LLM tool | `bg_kill` | | yes | | | | | | Covers running kill and already-finished loud failure. |
|
|
78
80
|
| Fusion command background launch | `/fusion`, managed task, terminal notification, `bg_result` | yes | yes | yes | | yes | yes | | Core unit covers deterministic projection, child argv/stdin/metadata parsing, artifacts, pre-abort handling, and orchestration. SDK verifies `/fusion` returns after durable preflight, all five child invocations continue in the managed task, terminal notification is emitted without a parent rewrite, editor/cancel behavior remains correct, and malformed config launches zero children. RPC verifies command discovery, Unicode request preservation, background terminal delivery, no parent `agent_start`, editor protocol, malformed-config admission failure, child failure notification, and child isolation flags. |
|
|
79
81
|
| Fusion v1 background result | `fusion_reason`, `fusion_investigate`, `fusion_research`, `fusion_validate`, `bg_result` | yes | yes | | | current-host stats/replay | yes | yes | Exactly four workflow tools remain registered; each returns a managed-task receipt after durable no-child preflight. `bg_result` verifies manifest-bound `result.json`/`merged.md`, never truncates, and attaches complete `Usage` exactly once. Failed/cancelled retrieval returns `delivery:"none"` and a closed no-answer view backed only by verified, bounded `failure-summary.json` metadata/refs; it cannot claim usage or expose partial text. SDK pins non-blocking launch under delayed children, tool-signal ownership handoff, clean-context isolation, failure coordinates, shutdown cancellation, and repeated-retrieval no-double-counting. Scripted-provider coverage proves no polling between launch and terminal wake. |
|
|
80
|
-
| Global Anthropic attribution and sanitization | package-owned
|
|
82
|
+
| Global Anthropic attribution and sanitization | package-owned compiled attribution entrypoint, `/claude-cache`, isolated child `--extension` argv | yes | | | | | yes | local SSE + subscription handoff | Package discovery loads attribution before background tasks for every installation. Unit pins all three exact SPS-derived sanitization variants, non-Anthropic non-mutation, provider/session/account/device metadata, beta-resource transport, cache surfaces, one-hour pricing, the 200K subscription contract, and EventBus duplicate-owner suppression. BUG-192 coverage pins provider/api/model-aware Codex/ZAI signature removal, foreign-redacted omission, lineage-proven empty/redacted/non-BMP Claude replay, directional older-Claude→Fable 5.1 compatibility, long→short→long signature-epoch isolation plus stale-prefix/reverse denial, canonical collision-checked tool IDs, stable text-tool-result prefixes, deterministic repeated payloads, official Fable binding/cache-diagnostics betas, fail-before-network profile drift, one-shot hash-bound compaction epochs, one-hour controls, away/back model lanes, exact official HTTPS endpoint/no-redirect policy, strict error/EOF/`message_stop` SSE completion, and official $0.25/M cache-read accounting. BUG-193 coverage proves hookless compaction/branch-summary requests derive mandatory attribution from Pi's fresh request-scoped `options.sessionId`, retain zero cache markers, isolate concurrent IDs, expose an already-attributed payload to optional middleware exactly once, preserve a frozen ordinary-turn payload hash, and reject route, metadata, system-identity, cache-topology, and excess-breakpoint tampering before fetch. The stubbed-fetch no-network loop exercises the real custom transport without weakening its production endpoint policy; release evidence uses subscription OAuth only. Fusion Anthropic routes receive exactly two explicit extensions in order: global attribution/sanitization, then runtime governor. Delegate and attested Anthropic routes also load attribution explicitly because attribution cannot rely on ambient discovery; delegates do so in both isolated and ambient modes. Non-Anthropic argv does not resolve or add attribution. Missing bytes fail loudly; the package has no exotic/URL sanitizer dependency. The independent repo-local spawn extension is byte-parity tested but imports nothing from this package. |
|
|
81
83
|
| Fusion validation workflow | structured `fusion_validate`, workflow profiles, `fusion-manifest.v4`, `fusion-result.v5` | yes | yes | | | | yes | | Public validation rejects legacy `{prompt}` with a migration error, enforces non-empty `scope`/`acceptanceCriteria`, and loudly validates `verification` cross-fields (`provided` ↔ evidence, `not_run` ↔ reason). Core validate orchestration is clean/read-only/advisory, enforces source-finding accounting including singleton, duplicate, exclusion, and merger add/drop cases, and remains no build/test substitute claim. |
|
|
82
84
|
| Fusion context boundaries | canonical input `fusion-input.v5`, reason `context-omission-ledger.json`, clean-task inputs | yes | yes | yes | | | yes | | Unit covers reason/session projection for a >1 MB synthetic tool-heavy session, verbatim user/assistant text, thinking exclusion, zero tool-payload preview bytes, exact and stable omission counts/byte totals/hashes, compact tuple round-trip, receipt-to-ledger reconciliation, active-tool-call-leaf and sibling-call exclusion, and byte-identical repeated construction. Clean-task tests assert investigate/research/validate inputs omit parent system prompt, conversation projection, and omission ledger, stay byte-identical across unrelated parent sessions, and keep parent sentinels out of every clean downstream prompt and artifact. SDK/RPC verify clean validate stdin has no `conversation_projection` or transcript while `/fusion`/reason preserve the projected-conversation path. |
|
|
83
85
|
| Fusion stage budgets | `budget-plan.json` (v4 per-stage forecasts), typed `prompt_budget_exceeded_forecast` / `prompt_budget_exceeded_measured` | yes | | | | | | | Unit covers stage-local launch-refusal wording plus terminal progress derived from durable attempts and persisted usage, so late evaluator/repair/merger budget failures report completed, failed, cancelled, and not-started run truth. Unit also covers the per-family affine estimator, additive segment accounting, multibyte 1.0-token/byte charging, unknown-provider floor visibility, per-route reservation of `max(Fusion output contract, model maximum output)`, byte-capacity route selection, scope guards for small windows, input-only fatal preflight versus warning-only reservations, rejection of unknown/zero/negative/too-small context windows, boundary accept at exactly the limit and reject one byte past, the child system prompt counted as input, per-stage forecasts built from the real prompt builders against each stage's own route, reservation warnings, breach-detector artifacts, safe prompts completing all five calls, persisted route/plan snapshots including negative slack on fatal rejection, and the reproduced 1 MB failure shape now fitting the smallest configured budget. Errors carry stage, measured size, allowed size, limiting model, estimator source, and remediation in both structured detail and message text. |
|
|
@@ -124,8 +126,9 @@ SDK/RPC/scripted-provider/package/compatibility coverage asserts exactly four to
|
|
|
124
126
|
| Output cap kills task | `PI_BG_MAX_OUTPUT_BYTES` | | | yes | | | | | RPC runs with a low cap and asserts failed status/log notice. |
|
|
125
127
|
| Shutdown cleanup | `session_shutdown` | yes | yes | | | | | | SDK asserts multiple running tasks become killed; registry tests cover shared stop/wait behavior. |
|
|
126
128
|
| Process lifecycle/races | registry core | yes | yes | yes | | yes | | yes | Unit tests cover POSIX process-group fallback, Windows `taskkill /T` then `/T /F` tree termination, SIGKILL escalation, duplicate finalization/notification races, notification/metadata failures, pruning, malformed telemetry, split telemetry chunks, large telemetry records above the old 16KiB buffer, and wrapped-agent transcript/telemetry separation with split-chunk and trailing-partial flush; SDK/RPC cover runtime spawn/timeout/output-cap/shutdown; scripted provider covers wakeup integration. |
|
|
129
|
+
| Compiler/source safety guards | package TypeScript and file-URL path handling | | | | | | yes | | TypeScript compiler trees reject actual explicit `any` types, directly nested assertions through multiline/parenthesized/`satisfies` wrappers, production non-null expressions, and recognized `@ts-ignore`/`@ts-expect-error`/`@ts-nocheck` comments while allowing inert comment/string/template/regex text. Alias-separated assertions are intentionally outside the direct-syntax rule, preserving validated `unknown` boundaries without claiming whole-program soundness. The URL guard separates syntax-wide self-contained hazards from finite flow provenance, with distinct normal/break/continue/return/explicit-throw completions and `finally` processing. It uses WHATWG preprocessing/absolute-first classification for static strings and static-head templates; `./`/`../` heads use explicit bases, while proven absolute first values ignore them. It covers inline/variable/import-meta provenance, constructor/object/`pathToFileURL` aliases, exact lowercase `import.meta['url']` versus uppercase intrinsic `globalThis['URL']` keys, computed pathname keys, declaration/assignment/statically known `for...of` destructuring, and Windows drive/UNC fixtures; and retains any explicitly traced may-file branch. Unknown dynamic first values remain unknown but join an explicit base as a possible relative branch; known absolute HTTP(S), definite overwrites, and compiler-resolved shadowed `URL`/`globalThis` bindings are controls. Calls, implicit exceptions, interprocedural/later-closure effects, and heap/proxy mutation are documented limits; no source-file allowlist suppresses findings. |
|
|
127
130
|
| Package manifest | `package.json` | | | | | | yes | | Keywords, ordered `pi.extensions`, files/notices, registry-only production dependencies, local smoke/compat scripts, and direct-completion import bans. |
|
|
128
|
-
| Pack contents | `npm pack --dry-run`, `npm run test:pnpm-pack` | | | | | | yes | | Runtime files and third-party notice included; tests, fake child helpers, release-only scripts, artifacts, nested tarballs, and `node_modules` excluded. Default npm tarball
|
|
131
|
+
| Pack contents | `npm pack --dry-run`, `npm run test:pnpm-pack` | | | | | | yes | | Runtime files and third-party notice included; tests, fake child helpers, release-only scripts, artifacts, nested tarballs, and `node_modules` excluded. Default npm tarball coverage proves an empty cache fails loudly, then seeds a separate empty task-owned cache only from a loopback registry built from lifecycle-free portable archives of the real installed Turndown/Domino production closure. A task-owned executable `prepack`/`prepare`/`postpack` sentinel pins npm 10.9.3's unsafe `--ignore-scripts` prepare behavior and npm 11.13.0's denial, proves the fixture path executes zero hooks, preserves script-bearing manifest bytes, and leaves source hashes unchanged. Every npm subprocess uses explicit task-owned user/global config, an isolated project cwd/config, `GIT_ALLOW_PROTOCOL=file`, and absolute archive/pack/install operands; a config-only hostile scoped-registry control performs no request. The registry is closed before `--offline` install, and Turndown executes with its Domino transitive dependency. Linux CI separately installs the tarball with pnpm 11.18.0 and `blockExoticSubdeps: true`. |
|
|
129
132
|
| Maintainer docs gateway | `BACKGROUND-TASKS-INSTRUCTIONS.md` | docs focused | | | | | | | Gateway requires INDEX → read-before-edit → owning-doc navigation, records current docs generate/verify/attestation workflow, preserves subscription-only frontier routing, parent/child tool separation, durability/integrity, and no self-certification. Logo reference is package-local (`docs/assets/logo.svg`) when the asset-owner file is present. |
|
|
130
133
|
| Shared architecture/API/runtime/operations docs | `docs/api`, `docs/concepts`, `docs/subsystems`, `docs/reference`, `docs/operations` | docs focused | | | | | | | The active docs engine validates frontmatter, package-local links/reference links, INDEX reachability, exact behavioral source ownership, generated runtime contracts, deterministic generated regions, and manifest freshness. Semantic receipt state is reported by default and enforced only by the optional strict attestation command. Unsupported extraction and legacy placeholders fail closed. |
|
|
131
134
|
| Package maintenance version/tag wording | `PUBLISHING.md`, `docs/operations/releasing.md` | docs focused | | | | | | | Release commands derive `VERSION` from `package.json`; observed git tags stop at `v0.6.0`, so git install/tag certification is separate from npm publishing and no nonexistent v1 tag is advertised. |
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Always-on safety entrypoint for package-owned isolated Anthropic children.
|
|
2
|
+
// Ambient parent capability selection must never disable this extension.
|
|
3
|
+
export { default } from '../src/core/anthropic-attribution.js';
|
|
4
|
+
//# sourceMappingURL=anthropic-attribution-child.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic-attribution-child.js","sourceRoot":"","sources":["../../extensions/anthropic-attribution-child.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,yEAAyE;AACzE,OAAO,EAAE,OAAO,EAAE,MAAM,sCAAsC,CAAC","sourcesContent":["// Always-on safety entrypoint for package-owned isolated Anthropic children.\n// Ambient parent capability selection must never disable this extension.\nexport { default } from '../src/core/anthropic-attribution.js';\n"]}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { parseBackgroundTasksConfig } from '../src/core/config.js';
|
|
2
|
+
const ANTHROPIC_PROVIDER = 'anthropic';
|
|
3
|
+
function captureProviderSnapshot(registry) {
|
|
4
|
+
const effective = registry.getProvider(ANTHROPIC_PROVIDER);
|
|
5
|
+
if (effective === undefined) {
|
|
6
|
+
throw new Error('pi_anthropic_attribution_unsupported: the host exposes no effective anthropic provider to preserve');
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
effective,
|
|
10
|
+
legacy: registry.getRegisteredProviderConfig(ANTHROPIC_PROVIDER),
|
|
11
|
+
native: registry.getRegisteredNativeProvider(ANTHROPIC_PROVIDER),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function confirmProviderInstallation(registry, before) {
|
|
15
|
+
const token = registry.getRegisteredProviderConfig(ANTHROPIC_PROVIDER);
|
|
16
|
+
const native = registry.getRegisteredNativeProvider(ANTHROPIC_PROVIDER);
|
|
17
|
+
const effective = registry.getProvider(ANTHROPIC_PROVIDER);
|
|
18
|
+
// The accepted duplicate-owner protocol returned without registering anything.
|
|
19
|
+
if (token === before.legacy && native === before.native && effective === before.effective) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
if (token === undefined ||
|
|
23
|
+
token === before.legacy ||
|
|
24
|
+
native !== undefined ||
|
|
25
|
+
effective === undefined ||
|
|
26
|
+
effective === before.effective ||
|
|
27
|
+
token.streamSimple === before.legacy?.streamSimple) {
|
|
28
|
+
throw new Error('pi_anthropic_attribution_install_failed: host provider registration did not install the package transport atomically');
|
|
29
|
+
}
|
|
30
|
+
return { registry, before, token };
|
|
31
|
+
}
|
|
32
|
+
function sameConfigValues(left, right) {
|
|
33
|
+
const leftKeys = Object.keys(left).sort();
|
|
34
|
+
const rightKeys = Object.keys(right).sort();
|
|
35
|
+
return (leftKeys.length === rightKeys.length &&
|
|
36
|
+
leftKeys.every((key, index) => key === rightKeys[index] && Object.is(Reflect.get(left, key), Reflect.get(right, key))));
|
|
37
|
+
}
|
|
38
|
+
function restoreProviderInstallation(installation) {
|
|
39
|
+
const { registry, before, token } = installation;
|
|
40
|
+
if (registry.getRegisteredProviderConfig(ANTHROPIC_PROVIDER) !== token ||
|
|
41
|
+
registry.getRegisteredNativeProvider(ANTHROPIC_PROVIDER) !== undefined) {
|
|
42
|
+
// A later owner replaced this exact installation. It owns teardown now.
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (before.native !== undefined) {
|
|
46
|
+
registry.registerProvider(before.native);
|
|
47
|
+
}
|
|
48
|
+
else if (before.legacy !== undefined) {
|
|
49
|
+
// Native replacement removes only the still-current package legacy layer. Reapplying
|
|
50
|
+
// the prior legacy snapshot then starts from an empty legacy layer, so package-only
|
|
51
|
+
// fields cannot leak through ModelRuntime's documented merge semantics.
|
|
52
|
+
registry.registerProvider(before.effective);
|
|
53
|
+
registry.registerProvider(ANTHROPIC_PROVIDER, before.legacy);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// The exact package token and absence of a later native owner were proven above.
|
|
57
|
+
// Public unregister is therefore owner-conditional here: it removes only this
|
|
58
|
+
// still-current package layer and restores the captured built-in source absence.
|
|
59
|
+
registry.unregisterProvider(ANTHROPIC_PROVIDER);
|
|
60
|
+
}
|
|
61
|
+
const restoredConfig = registry.getRegisteredProviderConfig(ANTHROPIC_PROVIDER);
|
|
62
|
+
const restoredNative = registry.getRegisteredNativeProvider(ANTHROPIC_PROVIDER);
|
|
63
|
+
const restoredEffective = registry.getProvider(ANTHROPIC_PROVIDER);
|
|
64
|
+
const restoredIds = registry.getRegisteredProviderIds();
|
|
65
|
+
const registrationRestored = before.legacy !== undefined
|
|
66
|
+
? restoredNative === undefined &&
|
|
67
|
+
restoredConfig !== undefined &&
|
|
68
|
+
sameConfigValues(restoredConfig, before.legacy)
|
|
69
|
+
: restoredConfig === undefined &&
|
|
70
|
+
(before.native !== undefined
|
|
71
|
+
? restoredNative === before.native
|
|
72
|
+
: restoredNative === undefined);
|
|
73
|
+
const streamIdentityRestored = before.legacy !== undefined
|
|
74
|
+
? restoredConfig?.streamSimple === before.legacy.streamSimple
|
|
75
|
+
: restoredEffective?.streamSimple === before.effective.streamSimple;
|
|
76
|
+
const effectiveIdentityRestored = before.legacy !== undefined || restoredEffective === before.effective;
|
|
77
|
+
const registeredIdRestored = before.legacy !== undefined || before.native !== undefined
|
|
78
|
+
? restoredIds.includes(ANTHROPIC_PROVIDER)
|
|
79
|
+
: !restoredIds.includes(ANTHROPIC_PROVIDER);
|
|
80
|
+
if (!registrationRestored ||
|
|
81
|
+
restoredEffective === undefined ||
|
|
82
|
+
!streamIdentityRestored ||
|
|
83
|
+
!effectiveIdentityRestored ||
|
|
84
|
+
!registeredIdRestored) {
|
|
85
|
+
throw new Error('pi_anthropic_attribution_restore_failed: the preexisting host provider was not restored by identity');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export default async function ambientAnthropicAttribution(pi) {
|
|
89
|
+
const config = parseBackgroundTasksConfig();
|
|
90
|
+
if (config.features.attribution) {
|
|
91
|
+
const { default: spawnAnthropicAttribution } = await import('../src/core/anthropic-attribution.js');
|
|
92
|
+
let installation;
|
|
93
|
+
pi.on('session_start', (_event, context) => {
|
|
94
|
+
const registry = context.modelRegistry;
|
|
95
|
+
const before = captureProviderSnapshot(registry);
|
|
96
|
+
spawnAnthropicAttribution(pi);
|
|
97
|
+
installation = confirmProviderInstallation(registry, before) ?? installation;
|
|
98
|
+
});
|
|
99
|
+
pi.on('session_shutdown', () => {
|
|
100
|
+
const current = installation;
|
|
101
|
+
installation = undefined;
|
|
102
|
+
if (current !== undefined)
|
|
103
|
+
restoreProviderInstallation(current);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=anthropic-attribution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic-attribution.js","sourceRoot":"","sources":["../../extensions/anthropic-attribution.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Provider } from '@earendil-works/pi-ai';\nimport type { ModelRegistry } from '@earendil-works/pi-coding-agent';\nimport type { PiContextLike, PiExtensionHost } from '../src/core/anthropic-attribution.js';\nimport { parseBackgroundTasksConfig } from '../src/core/config.js';\n\nconst ANTHROPIC_PROVIDER = 'anthropic';\n\ntype RegisteredProviderConfig = NonNullable<\n ReturnType<ModelRegistry['getRegisteredProviderConfig']>\n>;\n\ninterface AmbientAttributionContext extends PiContextLike {\n readonly modelRegistry: ModelRegistry;\n}\n\ninterface ProviderSnapshot {\n readonly effective: Provider;\n readonly legacy: RegisteredProviderConfig | undefined;\n readonly native: Provider | undefined;\n}\n\ninterface ProviderInstallation {\n readonly registry: ModelRegistry;\n readonly before: ProviderSnapshot;\n readonly token: RegisteredProviderConfig;\n}\n\nfunction captureProviderSnapshot(registry: ModelRegistry): ProviderSnapshot {\n const effective = registry.getProvider(ANTHROPIC_PROVIDER);\n if (effective === undefined) {\n throw new Error(\n 'pi_anthropic_attribution_unsupported: the host exposes no effective anthropic provider to preserve',\n );\n }\n return {\n effective,\n legacy: registry.getRegisteredProviderConfig(ANTHROPIC_PROVIDER),\n native: registry.getRegisteredNativeProvider(ANTHROPIC_PROVIDER),\n };\n}\n\nfunction confirmProviderInstallation(\n registry: ModelRegistry,\n before: ProviderSnapshot,\n): ProviderInstallation | undefined {\n const token = registry.getRegisteredProviderConfig(ANTHROPIC_PROVIDER);\n const native = registry.getRegisteredNativeProvider(ANTHROPIC_PROVIDER);\n const effective = registry.getProvider(ANTHROPIC_PROVIDER);\n\n // The accepted duplicate-owner protocol returned without registering anything.\n if (token === before.legacy && native === before.native && effective === before.effective) {\n return undefined;\n }\n\n if (\n token === undefined ||\n token === before.legacy ||\n native !== undefined ||\n effective === undefined ||\n effective === before.effective ||\n token.streamSimple === before.legacy?.streamSimple\n ) {\n throw new Error(\n 'pi_anthropic_attribution_install_failed: host provider registration did not install the package transport atomically',\n );\n }\n\n return { registry, before, token };\n}\n\nfunction sameConfigValues(\n left: RegisteredProviderConfig,\n right: RegisteredProviderConfig,\n): boolean {\n const leftKeys = Object.keys(left).sort();\n const rightKeys = Object.keys(right).sort();\n return (\n leftKeys.length === rightKeys.length &&\n leftKeys.every(\n (key, index) =>\n key === rightKeys[index] && Object.is(Reflect.get(left, key), Reflect.get(right, key)),\n )\n );\n}\n\nfunction restoreProviderInstallation(installation: ProviderInstallation): void {\n const { registry, before, token } = installation;\n if (\n registry.getRegisteredProviderConfig(ANTHROPIC_PROVIDER) !== token ||\n registry.getRegisteredNativeProvider(ANTHROPIC_PROVIDER) !== undefined\n ) {\n // A later owner replaced this exact installation. It owns teardown now.\n return;\n }\n\n if (before.native !== undefined) {\n registry.registerProvider(before.native);\n } else if (before.legacy !== undefined) {\n // Native replacement removes only the still-current package legacy layer. Reapplying\n // the prior legacy snapshot then starts from an empty legacy layer, so package-only\n // fields cannot leak through ModelRuntime's documented merge semantics.\n registry.registerProvider(before.effective);\n registry.registerProvider(ANTHROPIC_PROVIDER, before.legacy);\n } else {\n // The exact package token and absence of a later native owner were proven above.\n // Public unregister is therefore owner-conditional here: it removes only this\n // still-current package layer and restores the captured built-in source absence.\n registry.unregisterProvider(ANTHROPIC_PROVIDER);\n }\n\n const restoredConfig = registry.getRegisteredProviderConfig(ANTHROPIC_PROVIDER);\n const restoredNative = registry.getRegisteredNativeProvider(ANTHROPIC_PROVIDER);\n const restoredEffective = registry.getProvider(ANTHROPIC_PROVIDER);\n const restoredIds = registry.getRegisteredProviderIds();\n const registrationRestored =\n before.legacy !== undefined\n ? restoredNative === undefined &&\n restoredConfig !== undefined &&\n sameConfigValues(restoredConfig, before.legacy)\n : restoredConfig === undefined &&\n (before.native !== undefined\n ? restoredNative === before.native\n : restoredNative === undefined);\n const streamIdentityRestored =\n before.legacy !== undefined\n ? restoredConfig?.streamSimple === before.legacy.streamSimple\n : restoredEffective?.streamSimple === before.effective.streamSimple;\n const effectiveIdentityRestored =\n before.legacy !== undefined || restoredEffective === before.effective;\n const registeredIdRestored =\n before.legacy !== undefined || before.native !== undefined\n ? restoredIds.includes(ANTHROPIC_PROVIDER)\n : !restoredIds.includes(ANTHROPIC_PROVIDER);\n if (\n !registrationRestored ||\n restoredEffective === undefined ||\n !streamIdentityRestored ||\n !effectiveIdentityRestored ||\n !registeredIdRestored\n ) {\n throw new Error(\n 'pi_anthropic_attribution_restore_failed: the preexisting host provider was not restored by identity',\n );\n }\n}\n\nexport default async function ambientAnthropicAttribution(pi: PiExtensionHost): Promise<void> {\n const config = parseBackgroundTasksConfig();\n if (config.features.attribution) {\n const { default: spawnAnthropicAttribution } = await import(\n '../src/core/anthropic-attribution.js'\n );\n let installation: ProviderInstallation | undefined;\n pi.on('session_start', (_event, context) => {\n const registry = (context as AmbientAttributionContext).modelRegistry;\n const before = captureProviderSnapshot(registry);\n spawnAnthropicAttribution(pi);\n installation = confirmProviderInstallation(registry, before) ?? installation;\n });\n pi.on('session_shutdown', () => {\n const current = installation;\n installation = undefined;\n if (current !== undefined) restoreProviderInstallation(current);\n });\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"background-tasks.js","sourceRoot":"","sources":["../../extensions/background-tasks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["export { default } from '../src/extension.js';\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegate-child.js","sourceRoot":"","sources":["../../extensions/delegate-child.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oCAAoC,CAAC","sourcesContent":["export { default } from '../src/delegate-child-extension.js';\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fusion-child.js","sourceRoot":"","sources":["../../extensions/fusion-child.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC","sourcesContent":["export { default } from '../src/fusion-child-extension.js';\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the package-owned always-on Anthropic child attribution extension.
|
|
6
|
+
*
|
|
7
|
+
* Package-owned child Pi processes disable ambient extension discovery, so they
|
|
8
|
+
* must explicitly load this safety entrypoint. It deliberately bypasses the
|
|
9
|
+
* independently selectable ambient parent capability. Keeping path resolution
|
|
10
|
+
* in one module prevents Fusion, delegation, and attested runs from deriving
|
|
11
|
+
* different package paths.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveAnthropicAttributionExtensionPath(moduleUrl = import.meta.url, pathExists = existsSync) {
|
|
14
|
+
const modulePath = fileURLToPath(moduleUrl);
|
|
15
|
+
const extension = modulePath.endsWith('.ts')
|
|
16
|
+
? 'anthropic-attribution-child.ts'
|
|
17
|
+
: 'anthropic-attribution-child.js';
|
|
18
|
+
const candidate = resolve(dirname(modulePath), '../../extensions', extension);
|
|
19
|
+
if (!pathExists(candidate)) {
|
|
20
|
+
throw new Error(`Anthropic attribution extension is missing: ${candidate}`);
|
|
21
|
+
}
|
|
22
|
+
return candidate;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=anthropic-attribution-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic-attribution-path.js","sourceRoot":"","sources":["../../../src/core/anthropic-attribution-path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC;;;;;;;;GAQG;AACH,MAAM,UAAU,wCAAwC,CACtD,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAC3B,aAAwC,UAAU;IAElD,MAAM,UAAU,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC1C,CAAC,CAAC,gCAAgC;QAClC,CAAC,CAAC,gCAAgC,CAAC;IACrC,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,kBAAkB,EAAE,SAAS,CAAC,CAAC;IAC9E,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,+CAA+C,SAAS,EAAE,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["import { existsSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * Resolve the package-owned always-on Anthropic child attribution extension.\n *\n * Package-owned child Pi processes disable ambient extension discovery, so they\n * must explicitly load this safety entrypoint. It deliberately bypasses the\n * independently selectable ambient parent capability. Keeping path resolution\n * in one module prevents Fusion, delegation, and attested runs from deriving\n * different package paths.\n */\nexport function resolveAnthropicAttributionExtensionPath(\n moduleUrl = import.meta.url,\n pathExists: (path: string) => boolean = existsSync,\n): string {\n const modulePath = fileURLToPath(moduleUrl);\n const extension = modulePath.endsWith('.ts')\n ? 'anthropic-attribution-child.ts'\n : 'anthropic-attribution-child.js';\n const candidate = resolve(dirname(modulePath), '../../extensions', extension);\n if (!pathExists(candidate)) {\n throw new Error(`Anthropic attribution extension is missing: ${candidate}`);\n }\n return candidate;\n}\n"]}
|