specpi 0.19.0 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.19.1 - 2026-09-08
4
+
5
+ - Allow Pi's exact public Copilot catalog identification headers during delegation, fixing Copilot → Anthropic → Copilot switches that were incorrectly rejected as runtime provider overrides. Reconstruct headers independently in the child; retain runtime-auth, extension-provider and other header restrictions. Restart Pi after updating the harness.
6
+
3
7
  ## 0.19.0 - 2026-09-07
4
8
 
5
9
  - Add session-owned background commands with exact interactive approvals, shared Command Guard admission, bounded logs, and best-effort process cleanup.
package/README.md CHANGED
@@ -49,20 +49,12 @@ specpi doctor
49
49
 
50
50
  Preview the changes, confirm the install, then check the setup. Restart Pi when finished.
51
51
 
52
- To pin this release, use `npm install --global specpi@0.19.0`.
52
+ To pin this release, use `npm install --global specpi@0.19.1`.
53
53
 
54
54
  Delegation is enabled at startup. Use `/delegate off` to turn it off.
55
55
 
56
56
  [Setup, updates & removal](https://tannermidd.github.io/SpecPi/wiki/#getting-started) · [Delegation settings](docs/delegation/README.md)
57
57
 
58
- ## Background tasks
59
-
60
- Ask Pi to start a long-running command with `background_start`, then use `background_list`, `background_logs`, and `background_stop` to observe and clean up tasks. `background_start` accepts `command`, optional `cwd`/`label`, and `timeoutSeconds` (1–28,800; default 1,800). It requires interactive approval even with Guard off; Guard denials and locks still apply. Headless starts are denied.
61
-
62
- Commands use `/bin/sh` on POSIX or system `cmd.exe` on Windows, not Pi's configured Bash. They inherit the process environment except `NODE_OPTIONS` and `NODE_PATH`, which are cleared to keep the supervisor's startup predictable. No PTY or interactive stdin is provided. Four active tasks are allowed; output is capped at 256 KiB per task, reads at 64 KiB, and completed records at 32. Log offsets are absolute bytes in the UTF-8 stream including stdout/stderr markers, before terminal-control escaping; responses report the next cursor and lost bytes.
63
-
64
- Stop tasks when finished. Session replacement, reload, tree navigation, and shutdown attempt bounded cleanup. `cleanup: confirmed` means the owned root/group termination was observed, not that escaped descendants are contained; unconfirmed cleanup retains its slot and may need manual process inspection. Spawn success is not service readiness. Output is memory-only in the extension, but returned text may enter Pi conversation/provider retention. See the [security boundary](SECURITY_MODEL.md#background-task-execution).
65
-
66
58
  ## SpecPi Chat for VS Code
67
59
 
68
60
  Chat beside your code, attach files, and switch conversations.
@@ -71,7 +63,7 @@ Chat beside your code, attach files, and switch conversations.
71
63
 
72
64
  In VS Code, find **SpecPi Chat** by **tannermidd** in Extensions and install it. Open **SpecPi** in the Activity Bar, then **Connect Pi**.
73
65
 
74
- Chat 0.4.1 shows a compact delegation strip only while workers run or settle, and opens workspace image links in the image viewer. SpecPi 0.19.0 includes the delegation fix for switching models in the same chat. Update both packages, reload VS Code for the Chat update, and use **Restart Pi** to load the harness update. Later model switches do not require a restart. New Chat sessions start with Guard off; use `/guard guard` or `/guard strict` to enable it.
66
+ Chat 0.4.1 shows a compact delegation strip only while workers run or settle, and opens workspace image links in the image viewer. SpecPi 0.19.1 fixes delegation when switching GitHub Copilot models in the same chat, including Luna Opus Luna. Update the harness and use **Restart Pi** to load the fix; this patch does not require a Chat update. Later model switches do not require a restart. New Chat sessions start with Guard off; use `/guard guard` or `/guard strict` to enable it.
75
67
 
76
68
  ## Go further
77
69
 
package/SECURITY_MODEL.md CHANGED
@@ -35,7 +35,7 @@ not a new provider-disclosure boundary.
35
35
 
36
36
  Delegation is a native extension discovered through the ordinary Pi package and SpecPi lifecycle. Start `pi` normally; delegation is enabled by default at the first session start of each Pi process, including TUI, RPC, print and JSON modes, after settings, host and Guard checks. Startup launches no workers or model inference, but Pi-owned authentication/OAuth preparation may occur. The model-facing schema is present only while dispatch is enabled. It adds no launcher, separate host process, service or trust override. Parent Pi retains normal resource discovery, trust decisions and proxy policy. Delegation checks required public SDK capabilities, not exact version identifiers. Missing session/runtime/settings/thinking APIs prevent activation; session construction and every request still enforce the tool, model and resource policy. API presence is not proof of every future SDK behavior or a passing integration receipt. Normal installation's minimum Pi version and 0.84.4 bootstrap pin are separate contracts.
37
37
 
38
- Each worker is an SDK `createAgentSession` instance with in-memory session storage. Pi owns its model/tool loop. A fresh Pi `ModelRuntime` uses standard authentication, environment and `models.json` resolution, restoring Pi's persisted model catalogs with catalog network refresh disabled. Child transport and thinking budgets come from configured global settings; project settings are not loaded. Parent model/thinking are explicit with Pi's supported-level clamping. SpecPi does not extract credentials, copy authentication state or inspect private runtime fields. Preflight rejects runtime-only authentication, selected extension-registered provider overrides, model-specific headers, startup proxy configuration and mismatched safe model descriptors because those routes cannot be faithfully reconstructed. These rejections leave parent configuration unchanged.
38
+ Each worker is an SDK `createAgentSession` instance with in-memory session storage. Pi owns its model/tool loop. A fresh Pi `ModelRuntime` uses standard authentication, environment and `models.json` resolution, restoring Pi's persisted model catalogs with catalog network refresh disabled. Child transport and thinking budgets come from configured global settings; project settings are not loaded. Parent model/thinking are explicit with Pi's supported-level clamping. SpecPi does not extract credentials, copy authentication state or inspect private runtime fields. Preflight rejects runtime-only authentication, selected extension-registered provider overrides, unsupported model-specific headers, startup proxy configuration and mismatched safe model descriptors because those routes cannot be faithfully reconstructed. The sole model-header exception is the exact public Copilot catalog set: `User-Agent: GitHubCopilotChat/0.35.0`, `Editor-Version: vscode/1.107.0`, `Editor-Plugin-Version: copilot-chat/0.35.0`, and `Copilot-Integration-Id: vscode-chat`. Both parent and independently reconstructed child must have that entire set, with no extra keys, inherited headers or accessors; header presence must also match. No parent header object is copied or serialized. Changed catalog values require review before admission. These rejections leave parent configuration unchanged.
39
39
 
40
40
  This is not full parent inference parity: parent request hooks, ephemeral runtime settings and session affinity are not automatically inherited. Keep delegation disabled if those inherited controls are required for every request. Children load no ambient extensions, skills, AGENTS files or parent transcript. Their only tools are selected-source list/read/literal-search; shell, write, arbitrary plugin, live-web and nested delegation tools are absent. Trusted parent extensions remain privileged in the same process. This is an application boundary, not an OS sandbox against malicious extensions.
41
41
 
@@ -74,6 +74,6 @@ Cancellation revokes access and requests SDK abort. Slots remain held until SDK-
74
74
 
75
75
  Activation checks required public SDK capabilities, not an exact version allowlist. The installer retains its separate Pi 0.84.4 bootstrap pin. Restart after updating the runtime.
76
76
 
77
- Children use fresh Pi `ModelRuntime` instances with standard authentication/environment/models resolution and global transport/thinking settings. Project settings are not loaded. Parent model/thinking are explicit and checked through Pi's public APIs. Runtime-only authentication, selected extension-provider overrides, model headers, startup proxy configuration and mismatched model descriptors are rejected rather than silently approximated.
77
+ Children use fresh Pi `ModelRuntime` instances with standard authentication/environment/models resolution and global transport/thinking settings. Project settings are not loaded. Parent model/thinking are explicit and checked through Pi's public APIs. Runtime-only authentication, selected extension-provider overrides, unsupported model headers, startup proxy configuration and mismatched model descriptors are rejected rather than silently approximated. Pi's exact public Copilot client-identification headers are supported when independently reconstructed in the child; all other model headers remain unsupported. The reviewed values and matching rules are listed in [the security model](../../SECURITY_MODEL.md).
78
78
 
79
79
  Parent hooks, ephemeral settings and session affinity are **not inherited**. Keep delegation off if they are required on every request. Stable provider identity cannot certify an unchanged remote service. Trusted extensions still share Pi's privileged process; this is a trusted-local-filesystem contract, not an OS sandbox. See [SECURITY_MODEL.md](../../SECURITY_MODEL.md) for the authoritative boundaries and [THIRD_PARTY.md](../../THIRD_PARTY.md) for dependency compatibility.
@@ -17,8 +17,10 @@ fresh Pi `ModelRuntime`. The parent model and thinking level are passed explicit
17
17
  subject to Pi's clamping. Standard Pi authentication, environment and `models.json`
18
18
  resolution apply. Child transport/thinking budgets come from configured global settings;
19
19
  project settings are not loaded. Runtime-only authentication, selected extension-provider
20
- overrides, model-specific headers, startup proxy configuration and safe model-descriptor
21
- mismatches fail preflight. Parent request hooks,
20
+ overrides, unsupported model-specific headers, startup proxy configuration and safe model-descriptor
21
+ mismatches fail preflight. The exact public Copilot client-identification header set is
22
+ permitted only when independently reconstructed in the child, as specified in the
23
+ [security model](../../SECURITY_MODEL.md). Parent request hooks,
22
24
  ephemeral runtime settings, session affinity and ambient resources are not inherited.
23
25
 
24
26
  Command Guard is optional. Absent and Off states permit activation; an installed
@@ -7,7 +7,7 @@ import { DelegationError } from "./errors.mjs";
7
7
  import { createTimeoutStore } from "./settings.mjs";
8
8
 
9
9
  const stateKey = Symbol.for("specpi.delegation.native.v1");
10
- const revision = 10;
10
+ const revision = 11;
11
11
 
12
12
  export async function withPiCompatibility(sdk, loadCompatibility) {
13
13
  if (typeof sdk.clampThinkingLevel === "function") {
@@ -5,6 +5,14 @@ const MAX_CONTEXT_BYTES = 256 * 1024;
5
5
  const MAX_RUN_CONTEXT_BYTES = budgetLimits(MAX_BUDGET_MULTIPLIER).contextBytes;
6
6
  const MAX_RETAINED_RESPONSE_BYTES = budgetLimits(MAX_BUDGET_MULTIPLIER).retainedResponseBytes;
7
7
  const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
8
+ // Public client identification from https://pi.dev/api/models/providers/github-copilot.
9
+ // Keep this exception exact: other headers may contain credentials or routing overrides.
10
+ const COPILOT_CATALOG_HEADERS = Object.freeze({
11
+ "User-Agent": "GitHubCopilotChat/0.35.0",
12
+ "Editor-Version": "vscode/1.107.0",
13
+ "Editor-Plugin-Version": "copilot-chat/0.35.0",
14
+ "Copilot-Integration-Id": "vscode-chat",
15
+ });
8
16
  const MODEL_FIELDS = [
9
17
  "id",
10
18
  "provider",
@@ -62,7 +70,41 @@ function closed(value, keys) {
62
70
 
63
71
  function descriptor(model) {
64
72
  // Only public model behavior fields; never serialize a provider, headers, or credentials.
65
- return JSON.parse(bounded(Object.fromEntries(MODEL_FIELDS.map((key) => [key, model[key]])), 16 * 1024));
73
+ return JSON.parse(
74
+ bounded(
75
+ {
76
+ ...Object.fromEntries(MODEL_FIELDS.map((key) => [key, model[key]])),
77
+ hasModelHeaders: model.headers !== undefined,
78
+ },
79
+ 16 * 1024,
80
+ ),
81
+ );
82
+ }
83
+
84
+ function supportedModelHeaders(model) {
85
+ const headers = model.headers;
86
+ if (headers === undefined) {
87
+ return true;
88
+ }
89
+
90
+ if (model.provider !== "github-copilot" || !headers || typeof headers !== "object") {
91
+ return false;
92
+ }
93
+
94
+ try {
95
+ // Check names before touching values; never evaluate header getters or
96
+ // inspect arbitrary authentication headers. Retain no parent header data.
97
+ const keys = Reflect.ownKeys(headers);
98
+
99
+ return (
100
+ Object.getPrototypeOf(headers) === Object.prototype &&
101
+ keys.length === Object.keys(COPILOT_CATALOG_HEADERS).length &&
102
+ keys.every((key) => Object.hasOwn(COPILOT_CATALOG_HEADERS, key)) &&
103
+ keys.every((key) => Object.getOwnPropertyDescriptor(headers, key)?.value === COPILOT_CATALOG_HEADERS[key])
104
+ );
105
+ } catch {
106
+ return false;
107
+ }
66
108
  }
67
109
 
68
110
  function sameValue(left, right) {
@@ -314,7 +356,7 @@ export function createNativePiHost(ctx, { id, isCurrent, sdk, thinkingLevel } =
314
356
  !streaming &&
315
357
  (registry.getProviderAuthStatus(model.provider)?.source === "runtime" ||
316
358
  registry.getRegisteredProviderIds().includes(model.provider) ||
317
- model.headers !== undefined)
359
+ !supportedModelHeaders(model))
318
360
  ) {
319
361
  throw new DelegationError("Delegation cannot inherit this parent runtime provider override");
320
362
  }
@@ -351,6 +393,7 @@ export function createNativePiHost(ctx, { id, isCurrent, sdk, thinkingLevel } =
351
393
  runtime.getError() ||
352
394
  !runtime.getProvider(model.provider) ||
353
395
  !childModel ||
396
+ !supportedModelHeaders(childModel) ||
354
397
  !sameValue(descriptor(childModel), selected)
355
398
  ) {
356
399
  throw new DelegationError(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specpi",
3
- "version": "0.19.0",
3
+ "version": "0.19.1",
4
4
  "description": "An explicit, privacy-conscious, self-improving harness for the Pi coding agent",
5
5
  "author": "Tanner Middleton",
6
6
  "repository": {