pi-background-tasks 2.1.4 → 2.3.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/PUBLISHING.md +2 -0
- package/README.md +15 -8
- package/TESTING.md +15 -10
- package/TEST_PLAN.md +10 -9
- package/THIRD_PARTY_NOTICES.md +30 -0
- package/docs/INDEX.md +8 -4
- package/docs/choose-a-workflow.md +5 -2
- package/docs/commands/claude-cache.md +50 -0
- package/docs/getting-started.md +3 -0
- package/docs/manifest.json +84 -19
- package/docs/operations/configuration.md +15 -1
- package/docs/operations/releasing.md +6 -3
- package/docs/read-before-edit.md +4 -1
- package/docs/reference/runtime-contracts.md +52 -52
- package/docs/subsystems/anthropic-attribution.md +63 -0
- package/docs/subsystems/attested-pi-runs.md +2 -2
- package/docs/subsystems/child-launch-durability-and-safety.md +3 -3
- package/docs/subsystems/delegation.md +12 -4
- package/docs/subsystems/docs-freshness-gate.md +5 -5
- package/docs/subsystems/fusion.md +2 -2
- package/docs/tools/bg_delegate.md +21 -9
- package/docs/tools/bg_result.md +1 -1
- package/docs/tools/bg_run.md +5 -0
- package/extensions/anthropic-attribution.ts +1 -0
- package/package.json +4 -2
- package/src/core/anthropic-attribution-path.ts +26 -0
- package/src/core/{fusion/anthropic-attribution.ts → anthropic-attribution.ts} +61 -8
- package/src/core/attested-pi-run.ts +10 -1
- package/src/core/common.ts +2 -1
- package/src/core/delegate/artifacts.ts +4 -0
- package/src/core/delegate/launch.ts +44 -14
- package/src/core/delegate/runner.ts +24 -0
- package/src/core/delegate/seed.ts +12 -0
- package/src/core/delegate/types.ts +10 -2
- package/src/core/fusion/config.ts +1 -1
- package/src/core/fusion/pi-child.ts +7 -124
- package/src/core/registry.ts +4 -1
- package/src/delegate-child-extension.ts +9 -2
- package/src/delegate-extension.ts +53 -4
|
@@ -32,7 +32,7 @@ The design deliberately separates:
|
|
|
32
32
|
|
|
33
33
|
## Seed and context policy
|
|
34
34
|
|
|
35
|
-
The seed schema is `pi-background-tasks.delegate-seed.
|
|
35
|
+
The seed schema is `pi-background-tasks.delegate-seed.v2`. It wraps the frozen `visible-conversation-ledger-v2` projection under delegate policy id `delegate-inspect-v1`; it never emits Fusion input schemas or claims Fusion provenance. The exact selected `extension_mode` is hash-bound into the seed, and is also recorded in launch details, task facts, and `manifest.json`.
|
|
36
36
|
|
|
37
37
|
Projection behavior:
|
|
38
38
|
|
|
@@ -62,16 +62,22 @@ The child launch:
|
|
|
62
62
|
- separate random `--session-id`;
|
|
63
63
|
- task-owned `--session-dir` under the artifact directory;
|
|
64
64
|
- parent session/provider/model/reasoning env keys stripped;
|
|
65
|
-
-
|
|
66
|
-
-
|
|
65
|
+
- skill/template/theme/context discovery always disabled;
|
|
66
|
+
- extension discovery disabled by default in `extensionMode:"isolated"`;
|
|
67
|
+
- extension discovery deliberately enabled only by `extensionMode:"ambient"`;
|
|
68
|
+
- non-Anthropic children explicitly load the package-owned child guard in both modes;
|
|
69
|
+
- Anthropic children explicitly load package attribution/sanitization first, then the child guard, in both modes.
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+
Ambient mode exists for providers registered by user/project Pi extensions. It omits only `--no-extensions`; it accepts no caller-supplied extension paths and performs no provider fallback or route substitution. Ambient discovery executes arbitrary trusted-location extension code in the child process. That code has Node process privileges and is not sandboxed by Pi's model-visible tool allowlist, so ambient mode deliberately weakens the inspect-only process-isolation guarantee. It must not be described as safe or equivalent to isolated mode.
|
|
72
|
+
|
|
73
|
+
The only v1 capability is `inspect`. Allowed model-visible tools are exactly `read`, `grep`, `find`, `ls`, and `delegate_read_artifact`; forbidden tools deny shell, writes, background task controls, recursive delegation, attested Pi launch, and Fusion. This tool boundary remains argv/tool-registry enforced in both extension modes, but it is not a sandbox for ambient extension initialization or handlers. Missing Anthropic attribution bytes are a pre-artifact `delegate_isolation_unsupported` refusal; no un-attributed child or alternate route is launched.
|
|
69
74
|
|
|
70
75
|
## Route and budget
|
|
71
76
|
|
|
72
77
|
Routes are pinned once:
|
|
73
78
|
|
|
74
79
|
- omitted route → parent current model;
|
|
80
|
+
- extension-only routes still must be visible in the parent registry and require explicit `extensionMode:"ambient"` so the fresh child can load their implementing extension;
|
|
75
81
|
- explicit route → exact registry entry;
|
|
76
82
|
- unavailable/unknown-capacity routes fail;
|
|
77
83
|
- no substitution, fallback, or retry on a different route.
|
|
@@ -130,6 +136,8 @@ Default delivery inlines answers up to `48 KiB`; larger answers return artifact
|
|
|
130
136
|
|
|
131
137
|
Current `autoDeliver` status: `bg_delegate` accepts and records `never | when_small | always` and includes it in launch facts/details. The registry's generic terminal notification currently does not evaluate delegate results or include answer text, so `bg_result` remains the retrieval path.
|
|
132
138
|
|
|
139
|
+
`extensionMode` accepts only `isolated | ambient`, defaults to `isolated`, and is surfaced in receipt text and durable metadata. Ambient receipts include an explicit arbitrary-code/isolation warning.
|
|
140
|
+
|
|
133
141
|
## User-oriented failure taxonomy
|
|
134
142
|
|
|
135
143
|
Admission / no child:
|
|
@@ -12,14 +12,14 @@ covers_sources: []
|
|
|
12
12
|
This authored section defines the boundary: documentation facts are extracted from package metadata and TypeScript ASTs, then generated into docs and the manifest. Unsupported syntax fails the gate rather than falling back to regex or stale hand-maintained inventories. Public registrations must remain unconditional top-level direct calls or use the one validated local tool-wrapper shape; host/method aliases, computed access, nested or conditional registration, wrapper chaining/passing, constructor helpers, ambiguous public metadata, destructured Pi parameters, and repeated imported registrars are rejected.
|
|
13
13
|
|
|
14
14
|
<!-- pi-docs:begin name="docs-freshness-gate" generator="scripts/docs/generate.mjs" -->
|
|
15
|
-
- Canonical package version: `2.
|
|
16
|
-
- Governed markdown docs:
|
|
17
|
-
- Public surfaces extracted:
|
|
18
|
-
- Governed production sources:
|
|
15
|
+
- Canonical package version: `2.3.0`
|
|
16
|
+
- Governed markdown docs: 42
|
|
17
|
+
- Public surfaces extracted: 31
|
|
18
|
+
- Governed production sources: 50
|
|
19
19
|
- Tool contracts extracted: 11
|
|
20
20
|
- Schema IDs extracted: 44
|
|
21
21
|
- Environment variable references extracted: 49
|
|
22
|
-
- Behavioral attestation receipts not passing:
|
|
22
|
+
- Behavioral attestation receipts not passing: 7
|
|
23
23
|
- Receipt store: `docs/attestations.json`
|
|
24
24
|
|
|
25
25
|
`npm run docs:verify` is read-only: it renders generated files twice in memory and compares them with committed bytes. `npm run docs:generate` is the only docs writer.
|
|
@@ -5,7 +5,7 @@ mode: mixed
|
|
|
5
5
|
review_policy: behavioral
|
|
6
6
|
stability: stable
|
|
7
7
|
covers_surfaces: [renderer:fusion-result, workflow:investigate, workflow:reason, workflow:research, workflow:validate]
|
|
8
|
-
covers_sources: [extensions/fusion-child.ts, src/core/fusion/
|
|
8
|
+
covers_sources: [extensions/fusion-child.ts, src/core/fusion/artifacts.ts, src/core/fusion/budget.ts, src/core/fusion/child-protocol.ts, src/core/fusion/claude-cache.ts, src/core/fusion/clean-context.ts, src/core/fusion/config.ts, src/core/fusion/context.ts, src/core/fusion/evaluation.ts, src/core/fusion/orchestrator.ts, src/core/fusion/output-contract.ts, src/core/fusion/pi-child.ts, src/core/fusion/prompts.ts, src/core/fusion/result-package.ts, src/core/fusion/source-policy.ts, src/core/fusion/types.ts, src/core/fusion/web-fetch.ts, src/core/fusion/workflows.ts, src/fusion-child-extension.ts, src/fusion-extension.ts, src/ui/fusion-model-selector.ts]
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# Fusion subsystem
|
|
@@ -88,7 +88,7 @@ Inspect/research candidates write sealed tool-call audit logs. The log contains
|
|
|
88
88
|
|
|
89
89
|
## Child process isolation
|
|
90
90
|
|
|
91
|
-
Fusion never calls direct completion APIs. It launches direct child `pi --mode text` processes and writes the prompt over stdin. Child argv includes `--no-session`, `--no-extensions`, `--no-skills`, `--no-prompt-templates`, `--no-themes`, and `--no-context-files`; explicit extensions still load. Non-Anthropic children receive only the package-owned compact metadata/runtime-governor extension. Anthropic children receive, in fixed order, the package-
|
|
91
|
+
Fusion never calls direct completion APIs. It launches direct child `pi --mode text` processes and writes the prompt over stdin. Child argv includes `--no-session`, `--no-extensions`, `--no-skills`, `--no-prompt-templates`, `--no-themes`, and `--no-context-files`; explicit extensions still load. Non-Anthropic children receive only the package-owned compact metadata/runtime-governor extension. Anthropic children receive, in fixed order, the package-wide attribution/sanitization extension and the runtime governor. The same attribution implementation is globally loaded for ordinary package sessions; Fusion supplies its public extension entrypoint explicitly because ambient discovery is disabled. Attribution adds the Claude Code OAuth session header, linked account/device/session metadata, model-policy beta headers, system identity, beta-resource transport, cache surfaces, and model-aware cache usage pricing. It reads `userID` and `oauthAccount.accountUuid` from `~/.claude.json` without writing the file and fails loudly when required attribution data is absent or malformed. Its internal sanitizer removes all reviewed exact-match rejected prompt lines while preserving unrelated text and cache controls; no external sanitizer package is resolved.
|
|
92
92
|
|
|
93
93
|
Child text mode writes the final full answer to stdout. The private child extension emits compact reasoning-free metadata frames to stderr for finalized assistant messages: provider/model, stop reason, text block byte counts and hashes, aggregate text hash, the complete Pi `Usage` object (including Anthropic `cacheWrite1h` and provider-reported reasoning subsets), and a closed cache-policy observation. It governs every final `before_provider_request` payload after attribution and sanitization. For Anthropic routes, the child environment defaults `PI_CACHE_RETENTION` to `long` before provider serialization, so the attribution/Pi adapter creates system, final-tool, and final-conversation breakpoints with `ttl: "1h"`; inherited `PI_CACHE_RETENTION=short|none|long` remains explicit, and call-level `cacheRetention="none"` still wins for compaction. The final governor validates and normalizes those upstream-selected breakpoints, falls back to short when model compatibility rejects long retention, preserves no-marker compaction payloads, enforces Anthropic's four-breakpoint ceiling, and appends the subscription prompt-caching-scope beta idempotently. Its `effective_retention` field describes the final payload, not provider acceptance. Provider usage is preserved verbatim: `cacheWrite1h > 0` proves a one-hour write, but zero is inconclusive on subscription OAuth. Live normal-spawn and exact Fusion-child controls each observed a unique cache read after 370 idle seconds despite `cacheWrite1h = 0`; therefore payload observations prove request intent and `cacheRead` proves reuse, while neither zero telemetry nor a six-minute hit alone proves the full one-hour lifetime. Malformed controls or policy values abort before transport. Non-Anthropic payloads and child environments remain unchanged apart from the governor's existing JSON normalization.
|
|
94
94
|
|
|
@@ -11,14 +11,15 @@ covers_sources: []
|
|
|
11
11
|
|
|
12
12
|
<!-- pi-docs:begin name="tool-contract-bg_delegate" generator="scripts/docs/generate.mjs" -->
|
|
13
13
|
- Label: **Background Delegate**
|
|
14
|
-
- Source: `src/delegate-extension.ts:
|
|
15
|
-
- Description: Launch one background Pi agent seeded with a frozen projection of the current conversation, then return a launch receipt immediately. The child has its own session, a route pinned at launch that is never substituted, and read-only tools. Retrieve its verified answer with bg_result.
|
|
14
|
+
- Source: `src/delegate-extension.ts:340`
|
|
15
|
+
- Description: Launch one background Pi agent seeded with a frozen projection of the current conversation, then return a launch receipt immediately. The child has its own session, a route pinned at launch that is never substituted, and read-only tools. Extension discovery is isolated by default; ambient mode supports extension-registered providers but executes arbitrary discovered extension code. Retrieve its verified answer with bg_result.
|
|
16
16
|
- Root schema: `object`; additionalProperties: `false`
|
|
17
17
|
|
|
18
18
|
| Field | Required | Type | Description | Constraints |
|
|
19
19
|
| --- | --- | --- | --- | --- |
|
|
20
20
|
| `autoDeliver` | no | `string` | Whether the completion notification carries the answer: never \| when_small \| always. Default never; retrieve with bg_result. | |
|
|
21
21
|
| `capability` | no | `string` | Capability profile. Only "inspect" (read/search/list, no shell, no writes, no network, no recursion) is supported. | |
|
|
22
|
+
| `extensionMode` | no | `string` | Extension discovery: isolated \| ambient. Default isolated. Ambient is for extension-registered providers and executes arbitrary discovered extension code, weakening process isolation. | |
|
|
22
23
|
| `maxToolCalls` | no | `number` | Maximum tool calls. Default 120. | |
|
|
23
24
|
| `maxTurns` | no | `number` | Maximum agent turns. Default 24. | |
|
|
24
25
|
| `name` | yes | `string` | Short human-readable task name shown in the bg footer dock. Use 2-6 words. | |
|
|
@@ -46,6 +47,10 @@ covers_sources: []
|
|
|
46
47
|
"description": "Capability profile. Only \"inspect\" (read/search/list, no shell, no writes, no network, no recursion) is supported.",
|
|
47
48
|
"type": "string"
|
|
48
49
|
},
|
|
50
|
+
"extensionMode": {
|
|
51
|
+
"description": "Extension discovery: isolated | ambient. Default isolated. Ambient is for extension-registered providers and executes arbitrary discovered extension code, weakening process isolation.",
|
|
52
|
+
"type": "string"
|
|
53
|
+
},
|
|
49
54
|
"maxToolCalls": {
|
|
50
55
|
"description": "Maximum tool calls. Default 120.",
|
|
51
56
|
"type": "number"
|
|
@@ -118,6 +123,7 @@ Optional:
|
|
|
118
123
|
|
|
119
124
|
- `route: {provider: string, model: string}` — exact route pin. If omitted, the parent session's current `ctx.model.provider` and `ctx.model.id` are used.
|
|
120
125
|
- `capability: "inspect"` — default `"inspect"`; this is the only v1 capability.
|
|
126
|
+
- `extensionMode: "isolated" | "ambient"` — default `"isolated"`. Use `"ambient"` only when the pinned provider is implemented by an auto-discovered user/project extension. Ambient mode executes arbitrary discovered extension code and weakens process isolation.
|
|
121
127
|
- `maxTurns: positive integer` — default `24`.
|
|
122
128
|
- `maxToolCalls: positive integer` — default `120`.
|
|
123
129
|
- `timeoutSeconds: positive integer` — default `1200`.
|
|
@@ -153,21 +159,27 @@ Route resolution is pin-only:
|
|
|
153
159
|
- routes with no declared context window are refused before child creation;
|
|
154
160
|
- the child records provider/model attestations for assistant messages, and a mismatch prevents a successful result commit.
|
|
155
161
|
|
|
156
|
-
## Inspect-only tool boundary
|
|
162
|
+
## Inspect-only tool boundary and extension modes
|
|
157
163
|
|
|
158
|
-
The v1 capability is enforced by child argv and Pi's tool registry, not merely by prompt text:
|
|
164
|
+
The v1 model-visible capability is enforced by child argv and Pi's tool registry, not merely by prompt text:
|
|
159
165
|
|
|
160
166
|
- enabled tools: `read`, `grep`, `find`, `ls`, `delegate_read_artifact`;
|
|
161
167
|
- `--no-builtin-tools` is used with the explicit allowlist;
|
|
162
168
|
- forbidden tools include shell/write/background/delegate/Fusion surfaces (`bash`, `edit`, `write`, `bg_run`, `bg_delegate`, `bg_result`, `bg_run_pi_attested`, Fusion tools, etc.);
|
|
163
|
-
-
|
|
164
|
-
-
|
|
169
|
+
- skills, prompt templates, themes, and context files remain disabled in both extension modes;
|
|
170
|
+
- the package-owned delegate guard is loaded explicitly in both modes; Anthropic routes first load the package attribution/sanitization extension.
|
|
171
|
+
|
|
172
|
+
`extensionMode:"isolated"` adds `--no-extensions` and is the default. Use it for built-in providers and whenever ambient provider code is unnecessary.
|
|
173
|
+
|
|
174
|
+
`extensionMode:"ambient"` omits only `--no-extensions`, allowing Pi to discover trusted-location user/project extensions so a fresh child can resolve an extension-registered provider. It does **not** accept extension paths from the tool call, alter the pinned route, or provide fallback/substitution.
|
|
175
|
+
|
|
176
|
+
Ambient extensions execute arbitrary code in the child process with Node privileges. The read-only tool allowlist constrains tools exposed to the model; it does not sandbox extension initialization or event handlers. Therefore ambient mode weakens the inspect-only process-isolation guarantee even though the model-visible tool registry remains inspect-only.
|
|
165
177
|
|
|
166
|
-
There is no shell, edit/write, network tool, recursive delegation,
|
|
178
|
+
There is no shell, edit/write, network tool, recursive delegation, or Fusion in the child tool set. That claim applies to registered model tools, not to arbitrary code loaded by ambient extensions.
|
|
167
179
|
|
|
168
180
|
## Admission, budgets, and artifacts
|
|
169
181
|
|
|
170
|
-
Public admission resolves the route
|
|
182
|
+
Public admission resolves the route, package-owned child guard, and—for Anthropic routes—the package attribution extension before entering `preflightDelegateLaunch()`. Within that preflight, the hook contract is checked before capability/limit/seed/budget admission. Every refusal still occurs before child process, child session directory, or artifact root creation, leaving zero child processes and zero delegate artifacts; callers should not depend on a single absolute error-precedence order across route, guard-extension, and hook checks.
|
|
171
183
|
|
|
172
184
|
Budgets and limits:
|
|
173
185
|
|
|
@@ -190,4 +202,4 @@ Inside the child, `delegate_read_artifact({artifact, offset, length})` reads an
|
|
|
190
202
|
|
|
191
203
|
## Completion
|
|
192
204
|
|
|
193
|
-
`bg_delegate` returns a receipt with task id, route, child session id, artifact dir, seed hash/size, budget source, limits, auto-deliver setting, and notification/wake settings. With default notification settings, the parent receives the generic durable `background-task-notification` after terminal state and may then call `bg_result`. Do not poll solely to wait.
|
|
205
|
+
`bg_delegate` returns a receipt with task id, route, child session id, artifact dir, seed hash/size, budget source, extension mode, limits, auto-deliver setting, and notification/wake settings. Ambient receipts include an explicit arbitrary-code/isolation warning. The mode is also hash-bound in `seed.json` and persisted in task facts and `manifest.json`. With default notification settings, the parent receives the generic durable `background-task-notification` after terminal state and may then call `bg_result`. Do not poll solely to wait.
|
package/docs/tools/bg_result.md
CHANGED
|
@@ -11,7 +11,7 @@ covers_sources: []
|
|
|
11
11
|
|
|
12
12
|
<!-- pi-docs:begin name="tool-contract-bg_result" generator="scripts/docs/generate.mjs" -->
|
|
13
13
|
- Label: **Background Result**
|
|
14
|
-
- Source: `src/delegate-extension.ts:
|
|
14
|
+
- Source: `src/delegate-extension.ts:515`
|
|
15
15
|
- Description: Retrieve a hash-verified result from a bg_delegate or background Fusion task. Never blocks: a running task returns a typed not-ready result. Oversized answers are never truncated.
|
|
16
16
|
- Root schema: `object`; additionalProperties: `false`
|
|
17
17
|
|
package/docs/tools/bg_run.md
CHANGED
|
@@ -97,6 +97,8 @@ Legacy argument preparation can derive a missing `name` from `description` or `c
|
|
|
97
97
|
|
|
98
98
|
Use for long-running tests, builds, servers, watchers, sleeps, and child agent work. Do not use normal foreground shell tools for commands expected to outlive the current turn.
|
|
99
99
|
|
|
100
|
+
For an Anthropic child `pi`, keep normal extension discovery enabled. Do not pass `--no-extensions` unless the command also explicitly loads this package's `extensions/anthropic-attribution.ts` with `-e`/`--extension`.
|
|
101
|
+
|
|
100
102
|
## Defaults
|
|
101
103
|
|
|
102
104
|
- `notifyOnCompletion`: `true`.
|
|
@@ -154,6 +156,8 @@ Creates `.pi/tasks/<session-id>-<pid>/<task-id>.output` and `.json`. If `isAgent
|
|
|
154
156
|
|
|
155
157
|
The command runs through the platform shell and is not sandboxed. Use `isAgent:true` only to request Pi-agent telemetry wrapping; setting it does not make execution safer. Model-visible logs are bounded and point to the full output path.
|
|
156
158
|
|
|
159
|
+
`bg_run` does not parse or repair arbitrary child `pi` argv. An Anthropic command that uses `--no-extensions` without explicitly loading the attribution extension bypasses the package's attribution/sanitization contract and is unsupported.
|
|
160
|
+
|
|
157
161
|
## Related docs
|
|
158
162
|
|
|
159
163
|
- [Completion delivery](../concepts/completion-delivery.md)
|
|
@@ -162,6 +166,7 @@ The command runs through the platform shell and is not sandboxed. Use `isAgent:t
|
|
|
162
166
|
- [`bg_kill`](bg_kill.md)
|
|
163
167
|
- [`/bg`](../commands/bg.md)
|
|
164
168
|
- [Background task runtime](../subsystems/background-task-runtime.md)
|
|
169
|
+
- [Anthropic attribution](../subsystems/anthropic-attribution.md)
|
|
165
170
|
|
|
166
171
|
## Source ownership/reference
|
|
167
172
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '../src/core/anthropic-attribution.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-background-tasks",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Pi extension for durable background shell tasks, read-only delegated agents, local attested Pi runs, and fixed-purpose Fusion workflows through child Pi processes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "ISC",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"TEST_PLAN.md",
|
|
43
43
|
"PUBLISHING.md",
|
|
44
44
|
"LICENSE",
|
|
45
|
+
"THIRD_PARTY_NOTICES.md",
|
|
45
46
|
"docs/",
|
|
46
47
|
"BACKGROUND-TASKS-INSTRUCTIONS.md",
|
|
47
48
|
"logo.png"
|
|
@@ -65,6 +66,7 @@
|
|
|
65
66
|
"lint": "cd ../.. && npm run quality:ts:lint",
|
|
66
67
|
"format:check": "cd ../.. && npm run quality:ts:format",
|
|
67
68
|
"test:compat": "tsx scripts/test-compat.ts",
|
|
69
|
+
"test:pnpm-pack": "node scripts/test-pnpm-pack-install.mjs",
|
|
68
70
|
"test:hook-contract": "tsx --test --test-concurrency=1 tests/scripted-provider/pi-hook-contract.test.ts",
|
|
69
71
|
"docs:generate": "node scripts/docs/generate.mjs",
|
|
70
72
|
"docs:verify": "node scripts/docs/verify.mjs",
|
|
@@ -79,6 +81,7 @@
|
|
|
79
81
|
"pi": {
|
|
80
82
|
"image": "https://raw.githubusercontent.com/ismailsaleekh/pi-background-tasks/main/logo.png",
|
|
81
83
|
"extensions": [
|
|
84
|
+
"./extensions/anthropic-attribution.ts",
|
|
82
85
|
"./extensions/background-tasks.ts"
|
|
83
86
|
]
|
|
84
87
|
},
|
|
@@ -101,7 +104,6 @@
|
|
|
101
104
|
"node": ">=22.19.0"
|
|
102
105
|
},
|
|
103
106
|
"dependencies": {
|
|
104
|
-
"@ravshansbox/pi-anthropic-sps": "https://codeload.github.com/ravshansbox/pi-anthropic-sps/tar.gz/17409b5615f0ec0625776bc5434f92f2c55e3fd0",
|
|
105
107
|
"turndown": "7.2.4"
|
|
106
108
|
}
|
|
107
109
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the package-owned global Anthropic attribution extension.
|
|
7
|
+
*
|
|
8
|
+
* Package-owned child Pi processes disable ambient extension discovery, so they
|
|
9
|
+
* must explicitly load this public extension entrypoint. Keeping path resolution
|
|
10
|
+
* in one module prevents Fusion, delegation, and attested runs from deriving
|
|
11
|
+
* different package paths.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveAnthropicAttributionExtensionPath(
|
|
14
|
+
moduleUrl = import.meta.url,
|
|
15
|
+
pathExists: (path: string) => boolean = existsSync,
|
|
16
|
+
): string {
|
|
17
|
+
const modulePath = fileURLToPath(moduleUrl);
|
|
18
|
+
const extension = modulePath.endsWith('.ts')
|
|
19
|
+
? 'anthropic-attribution.ts'
|
|
20
|
+
: 'anthropic-attribution.js';
|
|
21
|
+
const candidate = resolve(dirname(modulePath), '../../extensions', extension);
|
|
22
|
+
if (!pathExists(candidate)) {
|
|
23
|
+
throw new Error(`Anthropic attribution extension is missing: ${candidate}`);
|
|
24
|
+
}
|
|
25
|
+
return candidate;
|
|
26
|
+
}
|
|
@@ -64,13 +64,17 @@ const AUDIT_ENV = 'PIPELINE_ANTHROPIC_ATTRIBUTION_AUDIT_PATH';
|
|
|
64
64
|
const CACHE_RETENTION_ENV = 'PI_CACHE_RETENTION';
|
|
65
65
|
export const ANTHROPIC_CACHE_RETENTION_ENTRY = 'pipeline-anthropic-cache-retention';
|
|
66
66
|
const ANTHROPIC_CACHE_RETENTION_SCHEMA = 'pipeline.anthropic_cache_retention.v1';
|
|
67
|
+
export const ANTHROPIC_ATTRIBUTION_CLAIM_CHANNEL = 'pi-anthropic-attribution:claim:v1';
|
|
68
|
+
const ANTHROPIC_ATTRIBUTION_CLAIM_SCHEMA = 'pi-anthropic-attribution.claim.v1';
|
|
67
69
|
const NATIVE_ATTESTATION_PLACEHOLDER = '00000';
|
|
68
70
|
const ANTHROPIC_CACHE_CONTROL_BREAKPOINT_LIMIT = 4;
|
|
69
71
|
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
+
// Sanitization behavior derived from the MIT-licensed ravshansbox/pi-anthropic-sps
|
|
73
|
+
// extension at commit 17409b5615f0ec0625776bc5434f92f2c55e3fd0. Keep exact-match
|
|
74
|
+
// semantics and all known Pi prompt variants; unrelated system text is preserved.
|
|
72
75
|
const ANTHROPIC_SYSTEM_PROMPT_BAD_LINES = new Set([
|
|
73
76
|
'- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md)',
|
|
77
|
+
'- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md), environment variables (docs/environment-variables.md)',
|
|
74
78
|
'- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing',
|
|
75
79
|
]);
|
|
76
80
|
|
|
@@ -329,7 +333,13 @@ interface PiCommandConfigLike {
|
|
|
329
333
|
readonly handler: (args: string, ctx: PiContextLike) => Promise<void> | void;
|
|
330
334
|
}
|
|
331
335
|
|
|
336
|
+
interface PiEventBusLike {
|
|
337
|
+
emit(channel: string, data: unknown): void;
|
|
338
|
+
on(channel: string, handler: (data: unknown) => void): () => void;
|
|
339
|
+
}
|
|
340
|
+
|
|
332
341
|
export interface PiExtensionHost extends PiProviderRegistrationHost {
|
|
342
|
+
readonly events: PiEventBusLike;
|
|
333
343
|
on(
|
|
334
344
|
eventName: 'session_start' | 'session_shutdown' | 'session_tree' | 'before_agent_start',
|
|
335
345
|
handler: (event: unknown, ctx: PiContextLike) => void,
|
|
@@ -1867,11 +1877,54 @@ function cacheRetentionLabel(retention: CacheRetention): string {
|
|
|
1867
1877
|
}
|
|
1868
1878
|
}
|
|
1869
1879
|
|
|
1880
|
+
interface AnthropicAttributionClaimProbe {
|
|
1881
|
+
readonly schema_version: typeof ANTHROPIC_ATTRIBUTION_CLAIM_SCHEMA;
|
|
1882
|
+
readonly acknowledge: () => void;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
function isAnthropicAttributionClaimProbe(value: unknown): value is AnthropicAttributionClaimProbe {
|
|
1886
|
+
return (
|
|
1887
|
+
isPlainObject(value) &&
|
|
1888
|
+
value['schema_version'] === ANTHROPIC_ATTRIBUTION_CLAIM_SCHEMA &&
|
|
1889
|
+
typeof value['acknowledge'] === 'function'
|
|
1890
|
+
);
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
/**
|
|
1894
|
+
* Prevent two independently installed copies from registering duplicate provider
|
|
1895
|
+
* hooks and `/claude-cache` commands in one Pi runtime. Pi loads extension factories
|
|
1896
|
+
* sequentially and its EventBus dispatches listeners synchronously, so an existing
|
|
1897
|
+
* owner acknowledges this probe before emit() returns. The winning extension only
|
|
1898
|
+
* publishes ownership after every registration below succeeds; a factory that throws
|
|
1899
|
+
* cannot strand a false claim that suppresses a healthy later copy.
|
|
1900
|
+
*/
|
|
1870
1901
|
export default function spawnAnthropicAttribution(pi: PiExtensionHost): void {
|
|
1902
|
+
const acknowledgements: true[] = [];
|
|
1903
|
+
const probe: AnthropicAttributionClaimProbe = {
|
|
1904
|
+
schema_version: ANTHROPIC_ATTRIBUTION_CLAIM_SCHEMA,
|
|
1905
|
+
acknowledge: () => {
|
|
1906
|
+
acknowledgements.push(true);
|
|
1907
|
+
},
|
|
1908
|
+
};
|
|
1909
|
+
pi.events.emit(ANTHROPIC_ATTRIBUTION_CLAIM_CHANNEL, probe);
|
|
1910
|
+
if (acknowledgements.length > 0) return;
|
|
1911
|
+
|
|
1871
1912
|
let sessionCacheRetention: Exclude<CacheRetention, 'none'> | undefined;
|
|
1872
1913
|
const getSessionOverride = (): Exclude<CacheRetention, 'none'> | undefined =>
|
|
1873
1914
|
sessionCacheRetention;
|
|
1874
1915
|
|
|
1916
|
+
// Registration is global but route-scoped by provider name. Keeping it at
|
|
1917
|
+
// factory scope avoids lifecycle-dependent provider availability; the custom
|
|
1918
|
+
// transport derives session/model headers from the attributed payload.
|
|
1919
|
+
pi.registerProvider('anthropic', {
|
|
1920
|
+
api: 'anthropic-messages',
|
|
1921
|
+
streamSimple: (model, context, options) =>
|
|
1922
|
+
streamAnthropicViaBetaMessages(model, context, {
|
|
1923
|
+
...(options ?? {}),
|
|
1924
|
+
cacheRetention: resolveCacheRetentionPreference(options, getSessionOverride()),
|
|
1925
|
+
}),
|
|
1926
|
+
});
|
|
1927
|
+
|
|
1875
1928
|
pi.registerCommand('claude-cache', {
|
|
1876
1929
|
description: 'Show or set Claude cache retention for this session (short, long, default)',
|
|
1877
1930
|
handler: (args, ctx) => {
|
|
@@ -1902,7 +1955,6 @@ export default function spawnAnthropicAttribution(pi: PiExtensionHost): void {
|
|
|
1902
1955
|
|
|
1903
1956
|
pi.on('session_start', (_event, ctx) => {
|
|
1904
1957
|
sessionCacheRetention = restoreAnthropicSessionCacheRetention(ctx.sessionManager.getBranch());
|
|
1905
|
-
registerAnthropicAttributionProvider(pi, ctx, getSessionOverride);
|
|
1906
1958
|
});
|
|
1907
1959
|
|
|
1908
1960
|
pi.on('session_shutdown', () => {
|
|
@@ -1913,13 +1965,8 @@ export default function spawnAnthropicAttribution(pi: PiExtensionHost): void {
|
|
|
1913
1965
|
sessionCacheRetention = restoreAnthropicSessionCacheRetention(ctx.sessionManager.getBranch());
|
|
1914
1966
|
});
|
|
1915
1967
|
|
|
1916
|
-
pi.on('before_agent_start', (_event, ctx) => {
|
|
1917
|
-
registerAnthropicAttributionProvider(pi, ctx, getSessionOverride);
|
|
1918
|
-
});
|
|
1919
|
-
|
|
1920
1968
|
pi.on('before_provider_request', (event, ctx) => {
|
|
1921
1969
|
if (!isAnthropicContext(ctx)) return undefined;
|
|
1922
|
-
registerAnthropicAttributionProvider(pi, ctx, getSessionOverride);
|
|
1923
1970
|
return rewriteAnthropicRequestPayload({
|
|
1924
1971
|
payload: event.payload,
|
|
1925
1972
|
ctx,
|
|
@@ -1927,4 +1974,10 @@ export default function spawnAnthropicAttribution(pi: PiExtensionHost): void {
|
|
|
1927
1974
|
headerRegistered: true,
|
|
1928
1975
|
});
|
|
1929
1976
|
});
|
|
1977
|
+
|
|
1978
|
+
// Publish ownership last. Extension loading is sequential, so later independent
|
|
1979
|
+
// copies probe this responder and become inert instead of registering duplicates.
|
|
1980
|
+
pi.events.on(ANTHROPIC_ATTRIBUTION_CLAIM_CHANNEL, (value) => {
|
|
1981
|
+
if (isAnthropicAttributionClaimProbe(value)) value.acknowledge();
|
|
1982
|
+
});
|
|
1930
1983
|
}
|
|
@@ -150,9 +150,18 @@ export function attestedPiChildEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
|
|
150
150
|
return out;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
export function buildAttestedPiArgv(
|
|
153
|
+
export function buildAttestedPiArgv(
|
|
154
|
+
input: StructuredPiLaunchRequest,
|
|
155
|
+
attributionExtensionPath?: string,
|
|
156
|
+
): string[] {
|
|
154
157
|
validateStructuredPiLaunchRequest(input);
|
|
155
158
|
const args = ['pi', '--mode', 'json', '--provider', input.provider, '--model', input.model];
|
|
159
|
+
if (input.provider === 'anthropic') {
|
|
160
|
+
if (!attributionExtensionPath?.trim()) {
|
|
161
|
+
throw new Error('Anthropic attested Pi tasks require the package attribution extension');
|
|
162
|
+
}
|
|
163
|
+
args.push('--extension', attributionExtensionPath);
|
|
164
|
+
}
|
|
156
165
|
if (input.thinking?.trim()) args.push('--thinking', input.thinking.trim());
|
|
157
166
|
args.push(...(input.extraPiArgs ?? []), input.prompt);
|
|
158
167
|
return args;
|
package/src/core/common.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { open } from 'node:fs/promises';
|
|
|
3
3
|
import { extname, isAbsolute, join, win32 } from 'node:path';
|
|
4
4
|
import { DEFAULT_MAX_BYTES } from '@earendil-works/pi-coding-agent';
|
|
5
5
|
import type { BackgroundTaskChildProcess } from './registry.js';
|
|
6
|
-
import type { DelegateBudgetRouteSource } from './delegate/types.js';
|
|
6
|
+
import type { DelegateBudgetRouteSource, DelegateExtensionMode } from './delegate/types.js';
|
|
7
7
|
import type { FusionResultDetails, FusionUsage, FusionWorkflowId } from './fusion/types.js';
|
|
8
8
|
|
|
9
9
|
export const TASK_STATUS_VALUES = ['running', 'completed', 'failed', 'killed'] as const;
|
|
@@ -87,6 +87,7 @@ export interface DelegateTaskFacts {
|
|
|
87
87
|
childSessionId: string;
|
|
88
88
|
route: { provider: string; model: string; qualifiedId: string };
|
|
89
89
|
budget: DelegateBudgetRouteSource;
|
|
90
|
+
extensionMode: DelegateExtensionMode;
|
|
90
91
|
autoDeliver: 'never' | 'when_small' | 'always';
|
|
91
92
|
/** Set once the run reaches a terminal state and its result has been evaluated. */
|
|
92
93
|
outcome?: DelegateTaskOutcome | undefined;
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
DELEGATE_MANIFEST_SCHEMA_VERSION,
|
|
9
9
|
DELEGATE_RECEIPT_SCHEMA_VERSION,
|
|
10
10
|
DelegateError,
|
|
11
|
+
type DelegateExtensionMode,
|
|
11
12
|
type DelegateLimits,
|
|
12
13
|
type DelegatePinnedRoute,
|
|
13
14
|
type DelegateSpillReceipt,
|
|
@@ -67,6 +68,7 @@ export interface DelegateManifestV1 {
|
|
|
67
68
|
cwd: string;
|
|
68
69
|
child_session_id: string;
|
|
69
70
|
child_session_dir: string;
|
|
71
|
+
extension_mode: DelegateExtensionMode;
|
|
70
72
|
route: DelegatePinnedRoute;
|
|
71
73
|
limits: DelegateLimits;
|
|
72
74
|
seed_sha256: string;
|
|
@@ -111,6 +113,7 @@ export interface CreateDelegateArtifactStoreOptions {
|
|
|
111
113
|
sessionId?: string | undefined;
|
|
112
114
|
childSessionId: string;
|
|
113
115
|
childSessionDir: string;
|
|
116
|
+
extensionMode: DelegateExtensionMode;
|
|
114
117
|
route: DelegatePinnedRoute;
|
|
115
118
|
limits: DelegateLimits;
|
|
116
119
|
seedSha256: string;
|
|
@@ -175,6 +178,7 @@ export class DelegateArtifactStore {
|
|
|
175
178
|
cwd: options.cwd,
|
|
176
179
|
child_session_id: options.childSessionId,
|
|
177
180
|
child_session_dir: options.childSessionDir,
|
|
181
|
+
extension_mode: options.extensionMode,
|
|
178
182
|
route: options.route,
|
|
179
183
|
limits: options.limits,
|
|
180
184
|
seed_sha256: options.seedSha256,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
DELEGATE_TOOL_NAME,
|
|
18
18
|
DelegateError,
|
|
19
19
|
type DelegateCapability,
|
|
20
|
+
type DelegateExtensionMode,
|
|
20
21
|
type DelegateLimits,
|
|
21
22
|
type DelegatePinnedRoute,
|
|
22
23
|
type DelegateRoute,
|
|
@@ -87,7 +88,7 @@ export function resolveDelegateRoute(input: DelegateRouteResolutionInput): Deleg
|
|
|
87
88
|
childCreated: false,
|
|
88
89
|
remediation: [
|
|
89
90
|
'Name a provider/model pair that appears in the current model registry.',
|
|
90
|
-
|
|
91
|
+
"Omit the route argument to use the parent session's current model.",
|
|
91
92
|
],
|
|
92
93
|
},
|
|
93
94
|
);
|
|
@@ -239,12 +240,23 @@ export function delegateToolsFor(capability: DelegateCapability): readonly strin
|
|
|
239
240
|
});
|
|
240
241
|
}
|
|
241
242
|
|
|
243
|
+
function assertDelegateExtensionMode(mode: DelegateExtensionMode): void {
|
|
244
|
+
if (mode === 'isolated' || mode === 'ambient') return;
|
|
245
|
+
throw new DelegateError(`bg_delegate extension mode ${String(mode)} is not supported`, {
|
|
246
|
+
code: 'invalid_arguments',
|
|
247
|
+
childCreated: false,
|
|
248
|
+
remediation: ['Use extensionMode "isolated" or "ambient".'],
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
242
252
|
export interface DelegateChildArgvInput {
|
|
243
253
|
route: DelegatePinnedRoute;
|
|
244
254
|
capability: DelegateCapability;
|
|
255
|
+
extensionMode: DelegateExtensionMode;
|
|
245
256
|
childSessionId: string;
|
|
246
257
|
childSessionDir: string;
|
|
247
258
|
childExtensionPath: string;
|
|
259
|
+
attributionExtensionPath?: string | undefined;
|
|
248
260
|
systemPrompt: string;
|
|
249
261
|
}
|
|
250
262
|
|
|
@@ -252,11 +264,15 @@ export interface DelegateChildArgvInput {
|
|
|
252
264
|
* Build the child argv.
|
|
253
265
|
*
|
|
254
266
|
* The child gets its own `--session-id` and a task-owned `--session-dir`, so it
|
|
255
|
-
* is structurally incapable of opening or mutating the parent session.
|
|
256
|
-
*
|
|
257
|
-
* disabled
|
|
267
|
+
* is structurally incapable of opening or mutating the parent session. Skills,
|
|
268
|
+
* prompt templates, themes, and context files are always disabled. Extension
|
|
269
|
+
* discovery is disabled in isolated mode and deliberately enabled in ambient
|
|
270
|
+
* mode; ambient extensions execute arbitrary code and are not sandboxed by the
|
|
271
|
+
* model-visible tool allowlist. The package guard is always explicit; Anthropic
|
|
272
|
+
* routes first load the package attribution/sanitization extension.
|
|
258
273
|
*/
|
|
259
274
|
export function buildDelegateChildArgv(input: DelegateChildArgvInput): string[] {
|
|
275
|
+
assertDelegateExtensionMode(input.extensionMode);
|
|
260
276
|
const tools = delegateToolsFor(input.capability);
|
|
261
277
|
for (const forbidden of DELEGATE_FORBIDDEN_TOOLS) {
|
|
262
278
|
if (tools.includes(forbidden)) {
|
|
@@ -266,6 +282,20 @@ export function buildDelegateChildArgv(input: DelegateChildArgvInput): string[]
|
|
|
266
282
|
);
|
|
267
283
|
}
|
|
268
284
|
}
|
|
285
|
+
const extensionPaths =
|
|
286
|
+
input.route.provider === 'anthropic'
|
|
287
|
+
? [
|
|
288
|
+
input.attributionExtensionPath ??
|
|
289
|
+
(() => {
|
|
290
|
+
throw new DelegateError(
|
|
291
|
+
'Anthropic delegate launch requires the package attribution extension',
|
|
292
|
+
{ code: 'delegate_isolation_unsupported', childCreated: false },
|
|
293
|
+
);
|
|
294
|
+
})(),
|
|
295
|
+
input.childExtensionPath,
|
|
296
|
+
]
|
|
297
|
+
: [input.childExtensionPath];
|
|
298
|
+
|
|
269
299
|
return [
|
|
270
300
|
'--mode',
|
|
271
301
|
'text',
|
|
@@ -279,13 +309,12 @@ export function buildDelegateChildArgv(input: DelegateChildArgvInput): string[]
|
|
|
279
309
|
tools.join(','),
|
|
280
310
|
'--exclude-tools',
|
|
281
311
|
DELEGATE_FORBIDDEN_TOOLS.join(','),
|
|
282
|
-
'--no-extensions',
|
|
312
|
+
...(input.extensionMode === 'isolated' ? ['--no-extensions'] : []),
|
|
283
313
|
'--no-skills',
|
|
284
314
|
'--no-prompt-templates',
|
|
285
315
|
'--no-themes',
|
|
286
316
|
'--no-context-files',
|
|
287
|
-
'--extension',
|
|
288
|
-
input.childExtensionPath,
|
|
317
|
+
...extensionPaths.flatMap((path) => ['--extension', path]),
|
|
289
318
|
'--provider',
|
|
290
319
|
input.route.provider,
|
|
291
320
|
'--model',
|
|
@@ -362,6 +391,7 @@ export interface DelegatePreflightInput {
|
|
|
362
391
|
toolCallId: string | undefined;
|
|
363
392
|
prompt: string;
|
|
364
393
|
capability: DelegateCapability;
|
|
394
|
+
extensionMode: DelegateExtensionMode;
|
|
365
395
|
route: DelegatePinnedRoute;
|
|
366
396
|
limitOverrides: DelegateLimitOverrides;
|
|
367
397
|
hookEvidence: DelegateHookContractEvidence;
|
|
@@ -411,10 +441,9 @@ export function buildDelegateChildPrompt(seedSerialized: string, directive: stri
|
|
|
411
441
|
* one of these can refuse, and none of them has created a process, a session, or
|
|
412
442
|
* an artifact by the time it does.
|
|
413
443
|
*/
|
|
414
|
-
export function preflightDelegateLaunch(
|
|
415
|
-
input: DelegatePreflightInput,
|
|
416
|
-
): DelegatePreflightResult {
|
|
444
|
+
export function preflightDelegateLaunch(input: DelegatePreflightInput): DelegatePreflightResult {
|
|
417
445
|
assertDelegateHookContract(input.hookEvidence);
|
|
446
|
+
assertDelegateExtensionMode(input.extensionMode);
|
|
418
447
|
// Validates the capability and proves the tool set contains nothing forbidden.
|
|
419
448
|
delegateToolsFor(input.capability);
|
|
420
449
|
const limits = resolveDelegateLimits(input.route, input.limitOverrides);
|
|
@@ -427,10 +456,13 @@ export function preflightDelegateLaunch(
|
|
|
427
456
|
toolCallId: input.toolCallId,
|
|
428
457
|
directive: input.prompt,
|
|
429
458
|
capability: input.capability,
|
|
459
|
+
extensionMode: input.extensionMode,
|
|
430
460
|
route: input.route,
|
|
431
461
|
limits,
|
|
432
462
|
});
|
|
433
|
-
const childSystemPrompt = buildDelegateChildSystemPrompt(
|
|
463
|
+
const childSystemPrompt = buildDelegateChildSystemPrompt(
|
|
464
|
+
'the task seed in your first user message',
|
|
465
|
+
);
|
|
434
466
|
const childPrompt = buildDelegateChildPrompt(seed.serialized, seed.seed.directive.text);
|
|
435
467
|
const plan = planDelegateAdmission({
|
|
436
468
|
route: input.route,
|
|
@@ -454,9 +486,7 @@ export function preflightDelegateLaunch(
|
|
|
454
486
|
}
|
|
455
487
|
|
|
456
488
|
/** Task-owned child session directory. Never the parent's session directory. */
|
|
457
|
-
export async function ensureDelegateChildSessionDir(
|
|
458
|
-
artifactDirAbs: string,
|
|
459
|
-
): Promise<string> {
|
|
489
|
+
export async function ensureDelegateChildSessionDir(artifactDirAbs: string): Promise<string> {
|
|
460
490
|
const dir = join(artifactDirAbs, 'child-session');
|
|
461
491
|
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
462
492
|
return dir;
|