@arbiterforge/ca-pi 0.15.7 → 0.15.10
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/README.md +2 -2
- package/package.json +1 -1
- package/plugins/ca-pi/CHANGELOG.md +32 -0
- package/plugins/ca-pi/extensions/codearbiter-child.js +2 -2
- package/plugins/ca-pi/extensions/codearbiter.js +33 -23
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-amd64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-arm64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-amd64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-arm64 +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-amd64.exe +0 -0
- package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-arm64.exe +0 -0
- package/plugins/ca-pi/helpers/artifacts/release.json +6 -6
- package/plugins/ca-pi/hooks/_artifactauthoritylib.py +160 -20
- package/plugins/ca-pi/hooks/_gitlib.py +8 -2
- package/plugins/ca-pi/hooks/_host.py +1 -1
- package/plugins/ca-pi/hooks/hostapi.py +1 -1
- package/plugins/ca-pi/includes/artifacts.md +15 -1
- package/plugins/ca-pi/includes/pi-host-notes.md +1 -1
- package/plugins/ca-pi/package.json +1 -1
- package/plugins/ca-pi/skills/ca-doctor/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ project context. You decide. codeArbiter enforces.
|
|
|
10
10
|
<img alt="Claude Code plugin" src="https://img.shields.io/badge/Claude_Code-plugin-d97757">
|
|
11
11
|
<img alt="Codex plugin" src="https://img.shields.io/badge/OpenAI_Codex-plugin-10a37f">
|
|
12
12
|
<img alt="Pi Feature Forge preview" src="https://img.shields.io/badge/ca--pi-Feature_Forge_preview-d97757">
|
|
13
|
-
<img alt="version 2.23.
|
|
13
|
+
<img alt="version 2.23.2" src="https://img.shields.io/badge/version-2.23.2-2b7489">
|
|
14
14
|
<img alt="core lanes" src="https://img.shields.io/badge/core_lanes-18-555">
|
|
15
15
|
<img alt="skills" src="https://img.shields.io/badge/skills-22-555">
|
|
16
16
|
<img alt="agents" src="https://img.shields.io/badge/agents-20-555">
|
|
@@ -183,7 +183,7 @@ Replace `<version>` with the numeric suffix from the chosen tag while retaining
|
|
|
183
183
|
it is a partial install that cannot author typed HTML specs and plans; use the npm channel for
|
|
184
184
|
that.
|
|
185
185
|
|
|
186
|
-
Pi 0.
|
|
186
|
+
Pi 1.0.0 is the supported host for this release line. Generated aliases use `/ca-*`;
|
|
187
187
|
`/skill:ca-*` is the host-native fallback. Every `ca-pi-v*` tag is also published to npm as
|
|
188
188
|
`npm:@arbiterforge/ca-pi` with provenance (ADR-0029); the pinned Git tag remains the reproducible
|
|
189
189
|
install. The [Pi runbook](./docs/pi-parity-testing.md) covers isolated install, trust, verification,
|
package/package.json
CHANGED
|
@@ -4,6 +4,38 @@ All notable changes to `ca-pi` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.15.10] - 2026-10-03
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Promote the verified Pi host window through exact Pi 1.0.0. Pi 1.0.0 closes the
|
|
12
|
+
undici advisories present in 0.84.1; the brace-expansion advisories it still pins
|
|
13
|
+
were already present in 0.84.1 and are recorded as accepted under ADR-0041.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Child dispatch no longer degrades on Pi 1.0.0: the runner accepts the 1.0.0 JSON
|
|
18
|
+
wire (`message_update` usage, named `toolcall_start`, `deferred` completion).
|
|
19
|
+
- `/ca-doctor` validates the Pi 1.0.0 CLI at its declared `dist/bundle/cli.js` bin.
|
|
20
|
+
|
|
21
|
+
## [0.15.9] - 2026-10-03
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Named verification now supports a root npm script delegating once to a uniquely
|
|
26
|
+
selected workspace runner. The declared root command remains intact, both npm
|
|
27
|
+
argument boundaries are honored, and every inspected manifest is retained in
|
|
28
|
+
launch provenance. Unsupported or ambiguous delegation fails before execution.
|
|
29
|
+
|
|
30
|
+
## [0.15.8] - 2026-09-27
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- The statusline's git dirty-state probe no longer takes `.git/index.lock`. It is killed at a
|
|
35
|
+
100 ms budget, and a plain `git status` killed while rewriting a stale index could leave a
|
|
36
|
+
stale lock that blocked the next commit (reproduced: 5 of 200 renders at that timing, 0 of 200
|
|
37
|
+
with the fix).
|
|
38
|
+
|
|
7
39
|
## [0.15.7] - 2026-09-27
|
|
8
40
|
|
|
9
41
|
### Changed
|
|
@@ -629,7 +629,7 @@ function resolvePythonCommand(platform = process.platform, probe = systemPythonP
|
|
|
629
629
|
}
|
|
630
630
|
|
|
631
631
|
// src/compatibility.ts
|
|
632
|
-
var SUPPORTED_PI_VERSIONS = /* @__PURE__ */ new Set(["0.
|
|
632
|
+
var SUPPORTED_PI_VERSIONS = /* @__PURE__ */ new Set(["1.0.0"]);
|
|
633
633
|
var MINIMUM_NODE = [22, 19, 0];
|
|
634
634
|
var SEMVER_PREFIX = /^(\d+)\.(\d+)\.(\d+)(?:$|[-+])/u;
|
|
635
635
|
function atLeast(version, minimum) {
|
|
@@ -644,7 +644,7 @@ function atLeast(version, minimum) {
|
|
|
644
644
|
}
|
|
645
645
|
function compatibilityDirection(input) {
|
|
646
646
|
if (!SUPPORTED_PI_VERSIONS.has(input.piVersion)) {
|
|
647
|
-
return "codeArbiter requires Pi 0.
|
|
647
|
+
return "codeArbiter requires Pi 1.0.0; install a supported Pi version and run /ca-doctor.";
|
|
648
648
|
}
|
|
649
649
|
if (!atLeast(input.nodeVersion, MINIMUM_NODE)) {
|
|
650
650
|
return "codeArbiter requires Node >=22.19.0 for Pi; upgrade Node and run /ca-doctor.";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
var define_CODEARBITER_PI_PERMISSION_POLICY_SURFACES_default = { "ca-plan": "planning-write", codearbiter_background_bash: "background-launch" };
|
|
3
3
|
|
|
4
4
|
// <define:__CODEARBITER_PI_SKILL_EXPANSION_FINGERPRINTS__>
|
|
5
|
-
var define_CODEARBITER_PI_SKILL_EXPANSION_FINGERPRINTS_default = { "0.
|
|
5
|
+
var define_CODEARBITER_PI_SKILL_EXPANSION_FINGERPRINTS_default = { "1.0.0": "12632f365440b07d5183cff871d889b796a3c711b6b49df20f95d9bc198d6c51" };
|
|
6
6
|
|
|
7
7
|
// <define:__CODEARBITER_PI_TOOL_CLASSES__>
|
|
8
8
|
var define_CODEARBITER_PI_TOOL_CLASSES_default = { bash: "EXEC", codearbiter_background_bash: "EXEC", codearbiter_dispatch: "EXEC", codearbiter_farm_preview: "EXEC", write: "WRITE", edit: "EDIT", read: "READ" };
|
|
@@ -16,7 +16,7 @@ import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL2 } from
|
|
|
16
16
|
import { types as utilTypes9 } from "node:util";
|
|
17
17
|
|
|
18
18
|
// src/compatibility.ts
|
|
19
|
-
var SUPPORTED_PI_VERSIONS = /* @__PURE__ */ new Set(["0.
|
|
19
|
+
var SUPPORTED_PI_VERSIONS = /* @__PURE__ */ new Set(["1.0.0"]);
|
|
20
20
|
var MINIMUM_NODE = [22, 19, 0];
|
|
21
21
|
var SEMVER_PREFIX = /^(\d+)\.(\d+)\.(\d+)(?:$|[-+])/u;
|
|
22
22
|
function atLeast(version, minimum) {
|
|
@@ -31,7 +31,7 @@ function atLeast(version, minimum) {
|
|
|
31
31
|
}
|
|
32
32
|
function compatibilityDirection(input) {
|
|
33
33
|
if (!SUPPORTED_PI_VERSIONS.has(input.piVersion)) {
|
|
34
|
-
return "codeArbiter requires Pi 0.
|
|
34
|
+
return "codeArbiter requires Pi 1.0.0; install a supported Pi version and run /ca-doctor.";
|
|
35
35
|
}
|
|
36
36
|
if (!atLeast(input.nodeVersion, MINIMUM_NODE)) {
|
|
37
37
|
return "codeArbiter requires Node >=22.19.0 for Pi; upgrade Node and run /ca-doctor.";
|
|
@@ -6881,10 +6881,10 @@ async function collectPiDoctorInput(dependencies) {
|
|
|
6881
6881
|
var REMEDIATION = {
|
|
6882
6882
|
package: "Reinstall ca-pi from the approved pinned Git tag, then restart Pi.",
|
|
6883
6883
|
trust: "Run /trust in Pi, inspect the project, grant trust only if you accept it, then start a new session.",
|
|
6884
|
-
version: "Upgrade Pi to 0.
|
|
6884
|
+
version: "Upgrade Pi to 1.0.0 and Node to >=22.19.0, then restart Pi.",
|
|
6885
6885
|
python: "Upgrade or install Python 3, then run /ca-doctor again.",
|
|
6886
6886
|
core: "Reinstall ca-pi to restore the generated shared core, then run /ca-doctor again.",
|
|
6887
|
-
commands: "Remove conflicting command owners or run Pi 0.
|
|
6887
|
+
commands: "Remove conflicting command owners or run Pi 1.0.0, then restart Pi and run /ca-doctor.",
|
|
6888
6888
|
bridge: "Reinstall ca-pi and Python 3, then run /ca-doctor again.",
|
|
6889
6889
|
child: "Reinstall ca-pi if the hardened child artifact is missing or tampered, then run /ca-doctor again.",
|
|
6890
6890
|
"ambient-marker": "Remove CODEARBITER_SUBAGENT from the parent environment and restart Pi.",
|
|
@@ -6913,7 +6913,7 @@ function diagnosePi(input) {
|
|
|
6913
6913
|
const packageHealthy = input.package.declared && input.package.name === "@arbiterforge/ca-pi" && existsSync(input.package.root) && existsSync(input.package.extensionPath) && samePath2(input.package.extensionPath, expectedExtension) && canonicallyInside(input.package.extensionPath, input.package.root);
|
|
6914
6914
|
const trustHealthy = input.trust.inspected && (!input.trust.required || input.trust.projectTrusted);
|
|
6915
6915
|
const waitingForTrust = input.trust.required && !input.trust.projectTrusted;
|
|
6916
|
-
const versionHealthy = input.runtime.piVersion === "0.
|
|
6916
|
+
const versionHealthy = input.runtime.piVersion === "1.0.0" && atLeast(input.runtime.nodeVersion, [22, 19, 0]);
|
|
6917
6917
|
const piBelowMinimum = !atLeast(input.runtime.piVersion, [0, 80, 5]);
|
|
6918
6918
|
const supportedExpansion = input.commands.expansionVerifiedVersions.includes(input.runtime.piVersion);
|
|
6919
6919
|
const expectedDoctorSkill = resolve9(input.package.root, "skills", "ca-doctor", "SKILL.md");
|
|
@@ -6924,7 +6924,7 @@ function diagnosePi(input) {
|
|
|
6924
6924
|
resolve9(input.package.root, "extensions", "codearbiter-child.js")
|
|
6925
6925
|
) && canonicallyInside(input.child.path, input.package.root) && existsSync(input.child.path);
|
|
6926
6926
|
const coreHealthy = input.core.present && existsSync(input.core.bridgeScript) && samePath2(input.core.bridgeScript, resolve9(input.package.root, "hooks", "pi-bridge.py")) && canonicallyInside(input.core.bridgeScript, input.package.root);
|
|
6927
|
-
const runtimeIdentityHealthy = existsSync(input.runtime.cliEntry) && existsSync(input.runtime.moduleEntry) && canonicallyInside(input.runtime.cliEntry, input.runtime.packageRoot) && canonicallyInside(input.runtime.moduleEntry, input.runtime.packageRoot) && samePath2(input.runtime.cliEntry, resolve9(input.runtime.packageRoot, "dist", "cli.js")) && samePath2(input.runtime.moduleEntry, resolve9(input.runtime.packageRoot, "dist", "index.js"));
|
|
6927
|
+
const runtimeIdentityHealthy = existsSync(input.runtime.cliEntry) && existsSync(input.runtime.moduleEntry) && canonicallyInside(input.runtime.cliEntry, input.runtime.packageRoot) && canonicallyInside(input.runtime.moduleEntry, input.runtime.packageRoot) && samePath2(input.runtime.cliEntry, resolve9(input.runtime.packageRoot, "dist", "bundle", "cli.js")) && samePath2(input.runtime.moduleEntry, resolve9(input.runtime.packageRoot, "dist", "index.js"));
|
|
6928
6928
|
const mutators = ["bash", "write", "edit"];
|
|
6929
6929
|
const wrapperHealthy = input.finalArguments.wrapperSourcePath !== void 0 && existsSync(input.finalArguments.wrapperSourcePath) && samePath2(input.finalArguments.wrapperSourcePath, expectedExtension) && mutators.every((name) => input.finalArguments.activeTools?.includes(name) === true) && mutators.every((name) => {
|
|
6930
6930
|
const path = input.finalArguments.toolSources?.[name];
|
|
@@ -7034,7 +7034,7 @@ function diagnosePi(input) {
|
|
|
7034
7034
|
{
|
|
7035
7035
|
id: "active-dispatch",
|
|
7036
7036
|
state: "degraded",
|
|
7037
|
-
message: "Supported Pi 0.
|
|
7037
|
+
message: "Supported Pi 1.0.0 public extension APIs cannot submit this deterministic self-test through the active dispatcher; the wrapper self-test does not exercise active dispatch.",
|
|
7038
7038
|
remediation: REMEDIATION["active-dispatch"]
|
|
7039
7039
|
}
|
|
7040
7040
|
];
|
|
@@ -8296,17 +8296,17 @@ function validContentBlock(value, kind) {
|
|
|
8296
8296
|
case "text":
|
|
8297
8297
|
return exactKeys4(value, ["type", "text", "textSignature"], ["type", "text"]) && boundedString2(value.text) && (value.textSignature === void 0 || boundedString2(value.textSignature));
|
|
8298
8298
|
case "image":
|
|
8299
|
-
return kind
|
|
8299
|
+
return (kind === "user" || kind === "toolResult") && exactKeys4(value, ["type", "data", "mimeType"]) && boundedString2(value.data) && boundedString2(value.mimeType);
|
|
8300
8300
|
case "thinking":
|
|
8301
8301
|
return kind === "assistant" && exactKeys4(value, ["type", "thinking", "thinkingSignature", "redacted"], ["type", "thinking"]) && boundedString2(value.thinking) && (value.thinkingSignature === void 0 || boundedString2(value.thinkingSignature)) && (value.redacted === void 0 || typeof value.redacted === "boolean");
|
|
8302
8302
|
case "toolCall":
|
|
8303
|
-
return kind === "assistant" && exactKeys4(value, ["type", "id", "name", "arguments", "thoughtSignature"], ["type", "id", "name", "arguments"]) && boundedString2(value.id) && boundedString2(value.name) && validOpaqueJson(value.arguments) && (value.thoughtSignature === void 0 || boundedString2(value.thoughtSignature));
|
|
8303
|
+
return kind === "assistant" && exactKeys4(value, ["type", "id", "name", "arguments", "thoughtSignature", "namespace"], ["type", "id", "name", "arguments"]) && boundedString2(value.id) && boundedString2(value.name) && validOpaqueJson(value.arguments) && (value.thoughtSignature === void 0 || boundedString2(value.thoughtSignature)) && (value.namespace === void 0 || boundedString2(value.namespace));
|
|
8304
8304
|
default:
|
|
8305
8305
|
return false;
|
|
8306
8306
|
}
|
|
8307
8307
|
}
|
|
8308
8308
|
function validContent(value, kind) {
|
|
8309
|
-
if (kind === "user" && typeof value === "string") return boundedString2(value);
|
|
8309
|
+
if ((kind === "user" || kind === "system") && typeof value === "string") return boundedString2(value);
|
|
8310
8310
|
return Array.isArray(value) && value.length <= MAX_JSON_ARRAY && value.every((block) => validContentBlock(block, kind));
|
|
8311
8311
|
}
|
|
8312
8312
|
function validUsage(value) {
|
|
@@ -8335,24 +8335,32 @@ function validDeferredHandle(value) {
|
|
|
8335
8335
|
["provider", "modelId", "api", "id"]
|
|
8336
8336
|
) && ["provider", "modelId", "api", "id"].every((key) => boundedString2(value[key])) && (value.expiresAt === void 0 || typeof value.expiresAt === "number" && Number.isFinite(value.expiresAt)) && (value.pollAfterMs === void 0 || typeof value.pollAfterMs === "number" && Number.isFinite(value.pollAfterMs)) && (value.data === void 0 || validOpaqueJson(value.data));
|
|
8337
8337
|
}
|
|
8338
|
+
function validNestedCalls(value) {
|
|
8339
|
+
return isRecord(value) && exactKeys4(value, ["calls", "complete"]) && typeof value.complete === "boolean" && Array.isArray(value.calls) && value.calls.length <= MAX_JSON_ARRAY && value.calls.every((call) => isRecord(call) && exactKeys4(call, ["id", "name", "arguments", "argumentsBytes", "status", "durationMs", "error"], ["id", "name", "status"]) && boundedString2(call.id) && boundedString2(call.name) && (call.status === "ok" || call.status === "error" || call.status === "unfinished") && (call.arguments === void 0 || isRecord(call.arguments) && validOpaqueJson(call.arguments)) && (call.argumentsBytes === void 0 || typeof call.argumentsBytes === "number" && Number.isFinite(call.argumentsBytes)) && (call.durationMs === void 0 || typeof call.durationMs === "number" && Number.isFinite(call.durationMs)) && (call.error === void 0 || boundedString2(call.error)));
|
|
8340
|
+
}
|
|
8341
|
+
function validSystemMessage(value) {
|
|
8342
|
+
const sections = value.sections;
|
|
8343
|
+
return exactKeys4(value, ["role", "content", "sections", "toolsAdded", "toolsRemoved", "timestamp"], ["role", "content", "timestamp"]) && validContent(value.content, "system") && (sections === void 0 || isRecord(sections) && Object.keys(sections).length <= MAX_JSON_KEYS && Object.keys(sections).every((key) => boundedString2(key) && (sections[key] === null || boundedString2(sections[key])))) && (value.toolsAdded === void 0 || Array.isArray(value.toolsAdded) && value.toolsAdded.length <= MAX_JSON_ARRAY && value.toolsAdded.every((tool) => isRecord(tool) && boundedString2(tool.name) && validOpaqueJson(tool))) && (value.toolsRemoved === void 0 || Array.isArray(value.toolsRemoved) && value.toolsRemoved.length <= MAX_JSON_ARRAY && value.toolsRemoved.every((tool) => isRecord(tool) && exactKeys4(tool, ["name"]) && boundedString2(tool.name))) && typeof value.timestamp === "number" && Number.isFinite(value.timestamp);
|
|
8344
|
+
}
|
|
8338
8345
|
function validMessage(value) {
|
|
8339
8346
|
if (!isRecord(value) || typeof value.role !== "string") return false;
|
|
8347
|
+
if (value.role === "system") return validSystemMessage(value);
|
|
8340
8348
|
if (value.role === "user") {
|
|
8341
8349
|
return exactKeys4(value, ["role", "content", "timestamp"]) && validContent(value.content, "user") && typeof value.timestamp === "number" && Number.isFinite(value.timestamp);
|
|
8342
8350
|
}
|
|
8343
8351
|
if (value.role === "assistant") {
|
|
8344
8352
|
return exactKeys4(
|
|
8345
8353
|
value,
|
|
8346
|
-
["role", "content", "api", "provider", "model", "responseModel", "responseId", "diagnostics", "usage", "stopReason", "errorMessage", "rawStopReason", "deferred", "timestamp"],
|
|
8354
|
+
["role", "content", "api", "provider", "model", "responseModel", "responseId", "providerThinkingLevel", "thinkingLevel", "diagnostics", "usage", "stopReason", "errorMessage", "rawStopReason", "deferred", "endTurn", "timestamp"],
|
|
8347
8355
|
["role", "content", "api", "provider", "model", "usage", "stopReason", "timestamp"]
|
|
8348
|
-
) && validContent(value.content, "assistant") && ["api", "provider", "model", "stopReason"].every((key) => typeof value[key] === "string") && (value.responseModel === void 0 || boundedString2(value.responseModel)) && (value.responseId === void 0 || boundedString2(value.responseId)) && (value.errorMessage === void 0 || boundedString2(value.errorMessage)) && (value.rawStopReason === void 0 || boundedString2(value.rawStopReason)) && (value.deferred === void 0 || validDeferredHandle(value.deferred)) && (value.diagnostics === void 0 || Array.isArray(value.diagnostics) && value.diagnostics.length <= MAX_JSON_ARRAY && value.diagnostics.every(validDiagnostic)) && validUsage(value.usage) && typeof value.timestamp === "number" && Number.isFinite(value.timestamp);
|
|
8356
|
+
) && validContent(value.content, "assistant") && ["api", "provider", "model", "stopReason"].every((key) => typeof value[key] === "string") && (value.responseModel === void 0 || boundedString2(value.responseModel)) && (value.responseId === void 0 || boundedString2(value.responseId)) && (value.errorMessage === void 0 || boundedString2(value.errorMessage)) && (value.rawStopReason === void 0 || boundedString2(value.rawStopReason)) && (value.providerThinkingLevel === void 0 || boundedString2(value.providerThinkingLevel)) && (value.thinkingLevel === void 0 || boundedString2(value.thinkingLevel)) && (value.endTurn === void 0 || typeof value.endTurn === "boolean") && (value.deferred === void 0 || validDeferredHandle(value.deferred)) && (value.diagnostics === void 0 || Array.isArray(value.diagnostics) && value.diagnostics.length <= MAX_JSON_ARRAY && value.diagnostics.every(validDiagnostic)) && validUsage(value.usage) && typeof value.timestamp === "number" && Number.isFinite(value.timestamp);
|
|
8349
8357
|
}
|
|
8350
8358
|
if (value.role === "toolResult") {
|
|
8351
8359
|
return exactKeys4(
|
|
8352
8360
|
value,
|
|
8353
|
-
["role", "toolCallId", "toolName", "content", "details", "isError", "usage", "timestamp"],
|
|
8361
|
+
["role", "toolCallId", "toolName", "content", "details", "isError", "usage", "nestedCalls", "timestamp"],
|
|
8354
8362
|
["role", "toolCallId", "toolName", "content", "isError", "timestamp"]
|
|
8355
|
-
) && typeof value.toolCallId === "string" && typeof value.toolName === "string" && validContent(value.content, "toolResult") && (value.details === void 0 || validOpaqueJson(value.details)) && (value.usage === void 0 || validUsage(value.usage)) && typeof value.isError === "boolean" && typeof value.timestamp === "number" && Number.isFinite(value.timestamp);
|
|
8363
|
+
) && typeof value.toolCallId === "string" && typeof value.toolName === "string" && validContent(value.content, "toolResult") && (value.details === void 0 || validOpaqueJson(value.details)) && (value.usage === void 0 || validUsage(value.usage)) && (value.nestedCalls === void 0 || validNestedCalls(value.nestedCalls)) && typeof value.isError === "boolean" && typeof value.timestamp === "number" && Number.isFinite(value.timestamp);
|
|
8356
8364
|
}
|
|
8357
8365
|
return false;
|
|
8358
8366
|
}
|
|
@@ -8401,8 +8409,9 @@ function validAssistantEvent(value) {
|
|
|
8401
8409
|
return exactWithOptionalPartial(["type"]);
|
|
8402
8410
|
case "text_start":
|
|
8403
8411
|
case "thinking_start":
|
|
8404
|
-
case "toolcall_start":
|
|
8405
8412
|
return exactWithOptionalPartial(["type", "contentIndex"]) && contentIndex();
|
|
8413
|
+
case "toolcall_start":
|
|
8414
|
+
return ("id" in value || "toolName" in value ? exactKeys4(value, ["type", "contentIndex", "id", "toolName"]) && boundedString2(value.id) && boundedString2(value.toolName) : exactWithOptionalPartial(["type", "contentIndex"])) && contentIndex();
|
|
8406
8415
|
case "text_delta":
|
|
8407
8416
|
case "thinking_delta":
|
|
8408
8417
|
case "toolcall_delta":
|
|
@@ -8413,7 +8422,7 @@ function validAssistantEvent(value) {
|
|
|
8413
8422
|
case "toolcall_end":
|
|
8414
8423
|
return exactWithOptionalPartial(["type", "contentIndex", "toolCall"]) && contentIndex() && validContentBlock(value.toolCall, "assistant");
|
|
8415
8424
|
case "done":
|
|
8416
|
-
return exactKeys4(value, ["type", "reason", "message"]) && ["stop", "length", "toolUse"].includes(value.reason) && validMessage(value.message) && value.message.role === "assistant";
|
|
8425
|
+
return exactKeys4(value, ["type", "reason", "message"]) && ["stop", "length", "toolUse", "deferred"].includes(value.reason) && validMessage(value.message) && value.message.role === "assistant";
|
|
8417
8426
|
case "error":
|
|
8418
8427
|
return exactKeys4(value, ["type", "reason", "error"]) && ["aborted", "error"].includes(value.reason) && validMessage(value.error) && value.error.role === "assistant";
|
|
8419
8428
|
default:
|
|
@@ -8435,7 +8444,8 @@ function parseChildJsonLine(line) {
|
|
|
8435
8444
|
case "response":
|
|
8436
8445
|
if (typeof record2.id !== "string" || record2.command !== "prompt" || typeof record2.success !== "boolean") invalidProtocol();
|
|
8437
8446
|
if (record2.success === true) {
|
|
8438
|
-
|
|
8447
|
+
const data = record2.data;
|
|
8448
|
+
if (!exactKeys4(record2, ["type", "id", "command", "success", "data"], ["type", "id", "command", "success"]) || data !== void 0 && !(isRecord(data) && exactKeys4(data, ["disposition"]) && (data.disposition === "started" || data.disposition === "handled" || data.disposition === "queued"))) invalidProtocol();
|
|
8439
8449
|
} else if (!exactKeys4(record2, ["type", "id", "command", "success", "error"]) || typeof record2.error !== "string") invalidProtocol();
|
|
8440
8450
|
break;
|
|
8441
8451
|
case "agent_start":
|
|
@@ -8454,16 +8464,16 @@ function parseChildJsonLine(line) {
|
|
|
8454
8464
|
if (!exactKeys4(record2, ["type", "message"]) || !validMessage(record2.message) && !validPartialAssistantMessage(record2.message)) invalidProtocol();
|
|
8455
8465
|
break;
|
|
8456
8466
|
case "message_update":
|
|
8457
|
-
if ("message" in record2 ? !exactKeys4(record2, ["type", "message", "assistantMessageEvent"]) || !validPartialAssistantMessage(record2.message) || !validAssistantEvent(record2.assistantMessageEvent) : !exactKeys4(record2, ["type", "assistantMessageEvent"]) || !validAssistantEvent(record2.assistantMessageEvent)) invalidProtocol();
|
|
8467
|
+
if ("message" in record2 ? !exactKeys4(record2, ["type", "message", "assistantMessageEvent"]) || !validPartialAssistantMessage(record2.message) || !validAssistantEvent(record2.assistantMessageEvent) : "usage" in record2 ? !exactKeys4(record2, ["type", "usage", "assistantMessageEvent"]) || !validUsage(record2.usage) || !validAssistantEvent(record2.assistantMessageEvent) : !exactKeys4(record2, ["type", "assistantMessageEvent"]) || !validAssistantEvent(record2.assistantMessageEvent)) invalidProtocol();
|
|
8458
8468
|
break;
|
|
8459
8469
|
case "tool_execution_start":
|
|
8460
|
-
if (!exactKeys4(record2, ["type", "toolCallId", "toolName", "args"]) || typeof record2.toolCallId !== "string" || typeof record2.toolName !== "string" || !validOpaqueJson(record2.args)) invalidProtocol();
|
|
8470
|
+
if (!exactKeys4(record2, ["type", "toolCallId", "toolName", "args", "parentToolCallId"], ["type", "toolCallId", "toolName", "args"]) || record2.parentToolCallId !== void 0 && !boundedString2(record2.parentToolCallId) || typeof record2.toolCallId !== "string" || typeof record2.toolName !== "string" || !validOpaqueJson(record2.args)) invalidProtocol();
|
|
8461
8471
|
break;
|
|
8462
8472
|
case "tool_execution_update":
|
|
8463
|
-
if (!exactKeys4(record2, ["type", "toolCallId", "toolName", "args", "partialResult"]) || typeof record2.toolCallId !== "string" || typeof record2.toolName !== "string" || !validOpaqueJson(record2.args) || !validOpaqueJson(record2.partialResult)) invalidProtocol();
|
|
8473
|
+
if (!exactKeys4(record2, ["type", "toolCallId", "toolName", "args", "partialResult", "parentToolCallId"], ["type", "toolCallId", "toolName", "args", "partialResult"]) || record2.parentToolCallId !== void 0 && !boundedString2(record2.parentToolCallId) || typeof record2.toolCallId !== "string" || typeof record2.toolName !== "string" || !validOpaqueJson(record2.args) || !validOpaqueJson(record2.partialResult)) invalidProtocol();
|
|
8464
8474
|
break;
|
|
8465
8475
|
case "tool_execution_end":
|
|
8466
|
-
if (!exactKeys4(record2, ["type", "toolCallId", "toolName", "result", "isError"]) || typeof record2.toolCallId !== "string" || typeof record2.toolName !== "string" || !validOpaqueJson(record2.result) || typeof record2.isError !== "boolean") invalidProtocol();
|
|
8476
|
+
if (!exactKeys4(record2, ["type", "toolCallId", "toolName", "result", "isError", "parentToolCallId"], ["type", "toolCallId", "toolName", "result", "isError"]) || record2.parentToolCallId !== void 0 && !boundedString2(record2.parentToolCallId) || typeof record2.toolCallId !== "string" || typeof record2.toolName !== "string" || !validOpaqueJson(record2.result) || typeof record2.isError !== "boolean") invalidProtocol();
|
|
8467
8477
|
break;
|
|
8468
8478
|
case "extension_error":
|
|
8469
8479
|
if (!exactKeys4(record2, ["type", "extensionPath", "event", "error"]) || typeof record2.extensionPath !== "string" || typeof record2.event !== "string" || typeof record2.error !== "string") invalidProtocol();
|
|
@@ -10588,7 +10598,7 @@ async function codeArbiterPi(pi) {
|
|
|
10588
10598
|
activeTools: pi.getActiveTools(),
|
|
10589
10599
|
allTools: pi.getAllTools(),
|
|
10590
10600
|
expansionFingerprints,
|
|
10591
|
-
childFingerprint: "
|
|
10601
|
+
childFingerprint: "86a08a0d5c29aa6693a07065f4ab2d6ad1d43293c65277cee944389886363d67"
|
|
10592
10602
|
});
|
|
10593
10603
|
const wrapperSelfTest = await runPiWrapperSelfTest({
|
|
10594
10604
|
enabled: enabledForDoctor,
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -6,32 +6,32 @@
|
|
|
6
6
|
"binaries": {
|
|
7
7
|
"darwin/amd64": {
|
|
8
8
|
"file": "ca-artifact-darwin-amd64",
|
|
9
|
-
"sha256": "
|
|
9
|
+
"sha256": "f25d64dc4fae4874f97c6a982382fcd0685552b62ecc29714d2f8f41280c20b1",
|
|
10
10
|
"native_tested": true
|
|
11
11
|
},
|
|
12
12
|
"darwin/arm64": {
|
|
13
13
|
"file": "ca-artifact-darwin-arm64",
|
|
14
|
-
"sha256": "
|
|
14
|
+
"sha256": "92eb0c6394176508ed569d4969f2d5058b45c0b650cf538e6ea43f011ade5e1c",
|
|
15
15
|
"native_tested": true
|
|
16
16
|
},
|
|
17
17
|
"linux/amd64": {
|
|
18
18
|
"file": "ca-artifact-linux-amd64",
|
|
19
|
-
"sha256": "
|
|
19
|
+
"sha256": "57135caf8e274ea0929a0ddae8d3f520473b9342398ab7db25c5688ebcb77347",
|
|
20
20
|
"native_tested": true
|
|
21
21
|
},
|
|
22
22
|
"linux/arm64": {
|
|
23
23
|
"file": "ca-artifact-linux-arm64",
|
|
24
|
-
"sha256": "
|
|
24
|
+
"sha256": "265c2d77b074017b30a2a979fd8926add4f0ede19a73bc83b77eb8a1147fecdc",
|
|
25
25
|
"native_tested": true
|
|
26
26
|
},
|
|
27
27
|
"windows/amd64": {
|
|
28
28
|
"file": "ca-artifact-windows-amd64.exe",
|
|
29
|
-
"sha256": "
|
|
29
|
+
"sha256": "a4ec0eeccca1c840a19ad8a0da57a660f365c20d565e72edfae8f1bf18498e7e",
|
|
30
30
|
"native_tested": true
|
|
31
31
|
},
|
|
32
32
|
"windows/arm64": {
|
|
33
33
|
"file": "ca-artifact-windows-arm64.exe",
|
|
34
|
-
"sha256": "
|
|
34
|
+
"sha256": "3ac1d16a8030ffb8f12565f39b4a70245aa263048efa91e2f6fb5f613b3361f7",
|
|
35
35
|
"native_tested": true
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -933,8 +933,11 @@ def _bind_commands(
|
|
|
933
933
|
raise AuthorityError("UNSUPPORTED_EXECUTABLE", "declared Node runner entrypoint is unavailable") from exc
|
|
934
934
|
launch_files.append(_launch_file(script, "runner-entrypoint"))
|
|
935
935
|
if definition["required_tests"] and _command_name(definition["argv"][0]) in {"npm", "npm.cmd"}:
|
|
936
|
-
_runner,
|
|
937
|
-
|
|
936
|
+
_runner, manifests = _npm_runner(definition["argv"], cwd)
|
|
937
|
+
if len(manifests) > 1:
|
|
938
|
+
launch_files.extend(_validate_nested_npm_launch(executable, manifests[0].parent))
|
|
939
|
+
launch_files.extend(_launch_file(manifest, "npm-manifest" if index == 0 else "npm-workspace-manifest")
|
|
940
|
+
for index, manifest in enumerate(manifests))
|
|
938
941
|
bindings.append({
|
|
939
942
|
"definition_sha256": command["definition_sha256"],
|
|
940
943
|
"argv": argv,
|
|
@@ -1433,7 +1436,147 @@ def _command_tokens(command: str) -> list[str]:
|
|
|
1433
1436
|
return list(lexer)
|
|
1434
1437
|
|
|
1435
1438
|
|
|
1436
|
-
def
|
|
1439
|
+
def _npm_package(manifest: Path) -> dict[str, Any]:
|
|
1440
|
+
_path_identity(manifest)
|
|
1441
|
+
with manifest.open("rb") as stream:
|
|
1442
|
+
raw = stream.read(MAX_STATE + 1)
|
|
1443
|
+
if len(raw) > MAX_STATE:
|
|
1444
|
+
raise ValueError("oversized manifest")
|
|
1445
|
+
package = json.loads(raw.decode("utf-8"), object_pairs_hook=_unique_json_pairs,
|
|
1446
|
+
parse_constant=_invalid_json_constant)
|
|
1447
|
+
if not isinstance(package, dict):
|
|
1448
|
+
raise ValueError("manifest is not an object")
|
|
1449
|
+
return package
|
|
1450
|
+
|
|
1451
|
+
|
|
1452
|
+
def _npm_script(package: dict[str, Any], name: str) -> list[str]:
|
|
1453
|
+
scripts = package["scripts"]
|
|
1454
|
+
script = scripts[name]
|
|
1455
|
+
if not isinstance(script, str) or any(marker in script for marker in ("\n", "\r", ";", "&", "|", ">", "<", "`", "$")):
|
|
1456
|
+
raise ValueError("compound script")
|
|
1457
|
+
if os.name == "nt" and any(marker in script for marker in ("^", "%", "!", "'")):
|
|
1458
|
+
raise ValueError("unsupported cmd.exe expansion or quoting")
|
|
1459
|
+
if scripts.get("pre" + name) or scripts.get("post" + name):
|
|
1460
|
+
raise ValueError("lifecycle scripts")
|
|
1461
|
+
tokens = _command_tokens(script)
|
|
1462
|
+
if not tokens:
|
|
1463
|
+
raise ValueError("empty script")
|
|
1464
|
+
return tokens
|
|
1465
|
+
|
|
1466
|
+
|
|
1467
|
+
def _npm_workspace_runner(tokens: list[str], package: dict[str, Any], manifest: Path) -> tuple[list[str], list[Path]]:
|
|
1468
|
+
# Exactly one explicit workspace selector and one delegation level. These
|
|
1469
|
+
# tokens select a collector; they never replace the declared root invocation.
|
|
1470
|
+
if (tokens[0] not in {"npm", "npm.cmd"} or (tokens[0] == "npm.cmd" and os.name != "nt")
|
|
1471
|
+
or len(tokens) < 5 or tokens[1] not in {"-w", "--workspace"}
|
|
1472
|
+
or tokens[3] not in {"run", "run-script"}
|
|
1473
|
+
or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9:._-]*", tokens[4])):
|
|
1474
|
+
raise ValueError("unsupported workspace delegation")
|
|
1475
|
+
selector = tokens[2]
|
|
1476
|
+
name_pattern = r"(?:@[a-z0-9._-]+/)?[a-z0-9][a-z0-9._-]*"
|
|
1477
|
+
if not re.fullmatch(name_pattern, selector):
|
|
1478
|
+
raise ValueError("workspace selector must be an exact package name")
|
|
1479
|
+
forwarded = tokens[5:]
|
|
1480
|
+
if forwarded:
|
|
1481
|
+
if forwarded[0] != "--":
|
|
1482
|
+
raise ValueError("workspace runner arguments must follow --")
|
|
1483
|
+
forwarded = forwarded[1:]
|
|
1484
|
+
workspaces = package.get("workspaces")
|
|
1485
|
+
if not isinstance(workspaces, list) or not 1 <= len(workspaces) <= 32:
|
|
1486
|
+
raise ValueError("workspaces must be a bounded array of explicit directories")
|
|
1487
|
+
inspected, names, paths, selected = [], set(), set(), None
|
|
1488
|
+
root = manifest.parent
|
|
1489
|
+
selector_path = root / selector
|
|
1490
|
+
for value in workspaces:
|
|
1491
|
+
if (not isinstance(value, str) or not value or any(c in value for c in "\\!*?[]{}()")
|
|
1492
|
+
or Path(value).anchor or PureWindowsPath(value).anchor
|
|
1493
|
+
or any(part in {"", ".", ".."} for part in value.split("/"))):
|
|
1494
|
+
raise ValueError("workspace path is not an explicit contained directory")
|
|
1495
|
+
directory = root
|
|
1496
|
+
for part in value.split("/"):
|
|
1497
|
+
directory /= part
|
|
1498
|
+
_path_identity(directory)
|
|
1499
|
+
directory.resolve(strict=True).relative_to(root.resolve(strict=True))
|
|
1500
|
+
workspace_manifest = directory / "package.json"
|
|
1501
|
+
identity = _path_identity(workspace_manifest)
|
|
1502
|
+
if identity in paths:
|
|
1503
|
+
raise ValueError("duplicate workspace directory")
|
|
1504
|
+
paths.add(identity)
|
|
1505
|
+
workspace = _npm_package(workspace_manifest)
|
|
1506
|
+
name = workspace.get("name")
|
|
1507
|
+
if not isinstance(name, str) or not re.fullmatch(name_pattern, name) or name in names:
|
|
1508
|
+
raise ValueError("missing or duplicate workspace name")
|
|
1509
|
+
names.add(name)
|
|
1510
|
+
inspected.append(workspace_manifest)
|
|
1511
|
+
# npm also treats selectors as directory filters. Refuse a name that
|
|
1512
|
+
# could select an additional workspace by its path or parent directory.
|
|
1513
|
+
if name != selector and (directory == selector_path or selector_path in directory.parents):
|
|
1514
|
+
raise ValueError("ambiguous workspace name and directory selector")
|
|
1515
|
+
if name == selector:
|
|
1516
|
+
selected = workspace
|
|
1517
|
+
if selected is None:
|
|
1518
|
+
raise ValueError("workspace name was not found")
|
|
1519
|
+
runner = _npm_script(selected, tokens[4])
|
|
1520
|
+
if _command_name(runner[0]) in {"npm", "npm.cmd"}:
|
|
1521
|
+
raise ValueError("recursive npm delegation")
|
|
1522
|
+
return [*runner, *forwarded], inspected
|
|
1523
|
+
|
|
1524
|
+
|
|
1525
|
+
def _validate_nested_npm_launch(executable: Path, cwd: Path) -> list[dict[str, str]]:
|
|
1526
|
+
# npm prepends every ancestor's node_modules/.bin to its script PATH.
|
|
1527
|
+
# A nested npm must resolve to the same qualified executable as its parent.
|
|
1528
|
+
names = ("npm", "npm.cmd", "npm.bat", "npm.exe", "npm.com") if os.name == "nt" else ("npm",)
|
|
1529
|
+
directories = [parent / "node_modules" / ".bin" for parent in (cwd, *cwd.parents)]
|
|
1530
|
+
if os.name == "nt":
|
|
1531
|
+
directories.insert(0, cwd) # cmd.exe searches its working directory first.
|
|
1532
|
+
if any(os.path.lexists(directory / name) for directory in directories for name in names):
|
|
1533
|
+
raise AuthorityError("UNSUPPORTED_EXECUTABLE", "nested npm is shadowed by a local executable")
|
|
1534
|
+
if _resolve_executable("npm", cwd) != executable:
|
|
1535
|
+
raise AuthorityError("UNSUPPORTED_EXECUTABLE", "nested npm resolves to a different executable")
|
|
1536
|
+
if os.name != "nt":
|
|
1537
|
+
return []
|
|
1538
|
+
# The outer launch pins npm-cli.js directly, but a script's npm.cmd consults
|
|
1539
|
+
# npm-prefix.js and may redirect to another installation. Qualify that same
|
|
1540
|
+
# helper in the manifest directory, and retain its identity with the CLI.
|
|
1541
|
+
node = executable.parent / "node.exe"
|
|
1542
|
+
cli = executable.parent / "node_modules" / "npm" / "bin" / "npm-cli.js"
|
|
1543
|
+
helper = cli.with_name("npm-prefix.js")
|
|
1544
|
+
try:
|
|
1545
|
+
helper_binding = _launch_file(helper, "npm-prefix")
|
|
1546
|
+
except (OSError, AuthorityError) as exc:
|
|
1547
|
+
raise AuthorityError("UNSUPPORTED_EXECUTABLE", "nested npm prefix helper is unavailable") from exc
|
|
1548
|
+
environment, _environment_digest = _minimal_environment()
|
|
1549
|
+
|
|
1550
|
+
def probe(arguments: list[str]) -> list[str]:
|
|
1551
|
+
try:
|
|
1552
|
+
result = _run_contained([str(node), *arguments], cwd=str(cwd), env=environment,
|
|
1553
|
+
timeout_seconds=15)
|
|
1554
|
+
if result.returncode or result.stderr or len(result.stdout) > 32768:
|
|
1555
|
+
raise ValueError("unsuccessful or oversized qualification output")
|
|
1556
|
+
return result.stdout.decode("utf-8", errors="strict").splitlines()
|
|
1557
|
+
except (OSError, ValueError, subprocess.SubprocessError, AuthorityError) as exc:
|
|
1558
|
+
raise AuthorityError("UNSUPPORTED_EXECUTABLE", "nested npm launch qualification failed") from exc
|
|
1559
|
+
|
|
1560
|
+
config = probe([str(cli), "config", "get", "script-shell", "node-options"])
|
|
1561
|
+
if config != ["script-shell=null", "node-options=null"]:
|
|
1562
|
+
raise AuthorityError("UNSUPPORTED_EXECUTABLE", "nested npm requires default script-shell and node-options")
|
|
1563
|
+
prefixes = probe([str(helper)])
|
|
1564
|
+
if (len(prefixes) != 1 or not Path(prefixes[0]).is_absolute()
|
|
1565
|
+
or any(ord(character) < 32 or character in '\"<>|?*' for character in prefixes[0])):
|
|
1566
|
+
raise AuthorityError("UNSUPPORTED_EXECUTABLE", "nested npm prefix qualification returned an invalid path")
|
|
1567
|
+
redirected_cli = Path(prefixes[0]) / "node_modules" / "npm" / "bin" / "npm-cli.js"
|
|
1568
|
+
if (os.path.lexists(redirected_cli)
|
|
1569
|
+
and os.path.normcase(os.path.normpath(redirected_cli)) != os.path.normcase(os.path.normpath(cli))):
|
|
1570
|
+
raise AuthorityError("UNSUPPORTED_EXECUTABLE", "nested npm prefix would redirect to a different CLI")
|
|
1571
|
+
try:
|
|
1572
|
+
if _launch_file(helper, "npm-prefix") != helper_binding:
|
|
1573
|
+
raise OSError("prefix helper changed during qualification")
|
|
1574
|
+
except (OSError, AuthorityError) as exc:
|
|
1575
|
+
raise AuthorityError("UNSUPPORTED_EXECUTABLE", "nested npm prefix helper changed during qualification") from exc
|
|
1576
|
+
return [helper_binding]
|
|
1577
|
+
|
|
1578
|
+
|
|
1579
|
+
def _npm_runner(argv: list[str], cwd: Path | None) -> tuple[list[str], list[Path]]:
|
|
1437
1580
|
message = "declare a direct test runner with an explicit supported reporter; npm scripts must be a single runner command"
|
|
1438
1581
|
if cwd is None:
|
|
1439
1582
|
raise AuthorityError("UNSUPPORTED_COLLECTOR", "npm runner requires an inspectable package.json; " + message)
|
|
@@ -1468,25 +1611,22 @@ def _npm_runner(argv: list[str], cwd: Path | None) -> tuple[list[str], Path]:
|
|
|
1468
1611
|
directory /= part
|
|
1469
1612
|
_path_identity(directory)
|
|
1470
1613
|
manifest.parent.resolve(strict=True).relative_to(cwd.resolve(strict=True))
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
if len(raw) > MAX_STATE:
|
|
1475
|
-
raise ValueError("oversized manifest")
|
|
1476
|
-
package = json.loads(raw.decode("utf-8"), object_pairs_hook=_unique_json_pairs,
|
|
1477
|
-
parse_constant=_invalid_json_constant)
|
|
1478
|
-
scripts = package["scripts"]
|
|
1479
|
-
script = scripts[script_name]
|
|
1480
|
-
if not isinstance(script, str) or any(marker in script for marker in ("\n", "\r", ";", "&", "|", ">", "<", "`", "$")):
|
|
1481
|
-
raise ValueError("compound script")
|
|
1482
|
-
if scripts.get("pre" + script_name) or scripts.get("post" + script_name):
|
|
1483
|
-
raise ValueError("lifecycle scripts")
|
|
1484
|
-
tokens = _command_tokens(script)
|
|
1485
|
-
if not tokens or _command_name(tokens[0]) in {"npm", "npm.cmd"}:
|
|
1486
|
-
raise ValueError("nested npm script")
|
|
1614
|
+
package = _npm_package(manifest)
|
|
1615
|
+
tokens = _npm_script(package, script_name)
|
|
1616
|
+
manifests = [manifest]
|
|
1487
1617
|
except (OSError, ValueError, KeyError, TypeError, RecursionError, AuthorityError) as exc:
|
|
1488
1618
|
raise AuthorityError("UNSUPPORTED_COLLECTOR", "npm package script is unavailable or compound; " + message) from exc
|
|
1489
|
-
|
|
1619
|
+
if _command_name(tokens[0]) in {"npm", "npm.cmd"}:
|
|
1620
|
+
# --prefix changes npm's config lookup and the environment inherited by
|
|
1621
|
+
# its script. Until those contexts are bound, qualify root delegation only.
|
|
1622
|
+
if relative != Path("."):
|
|
1623
|
+
raise AuthorityError("UNSUPPORTED_COLLECTOR", "nested npm workspace delegation requires --prefix .")
|
|
1624
|
+
try:
|
|
1625
|
+
runner, inspected = _npm_workspace_runner([*tokens, *forwarded], package, manifest)
|
|
1626
|
+
return runner, [manifest, *inspected]
|
|
1627
|
+
except (OSError, ValueError, KeyError, TypeError, RecursionError, AuthorityError) as exc:
|
|
1628
|
+
raise AuthorityError("UNSUPPORTED_COLLECTOR", "npm package script is unavailable or compound; " + message) from exc
|
|
1629
|
+
return [*tokens, *forwarded], manifests
|
|
1490
1630
|
|
|
1491
1631
|
|
|
1492
1632
|
def validate_command_definition(definition: dict[str, Any], *, cwd: Path | None = None) -> str:
|
|
@@ -99,9 +99,15 @@ def head_branch(root):
|
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
def git_dirty(root):
|
|
102
|
-
"""Return dirty state, suppressing the marker when Git fails or times out.
|
|
102
|
+
"""Return dirty state, suppressing the marker when Git fails or times out.
|
|
103
|
+
|
|
104
|
+
--no-optional-locks: the probe is killed at DIRTY_CHECK_TIMEOUT_SECONDS, and a
|
|
105
|
+
plain `git status` opportunistically rewrites a stale index under
|
|
106
|
+
.git/index.lock; a kill inside that write strands the lock and blocks the
|
|
107
|
+
user's next commit. A read-only probe must never take it."""
|
|
103
108
|
try:
|
|
104
|
-
out = subprocess.run([git_executable(), "-
|
|
109
|
+
out = subprocess.run([git_executable(), "--no-optional-locks", "-C", root,
|
|
110
|
+
"status", "--porcelain"],
|
|
105
111
|
capture_output=True, text=True,
|
|
106
112
|
timeout=DIRTY_CHECK_TIMEOUT_SECONDS,
|
|
107
113
|
encoding="utf-8", errors="replace")
|
|
@@ -11,7 +11,7 @@ import hostapi # noqa: E402
|
|
|
11
11
|
class PiHost(hostapi.Host):
|
|
12
12
|
name = "pi"
|
|
13
13
|
adapter_name = "@arbiterforge/ca-pi"
|
|
14
|
-
adapter_version = "0.15.
|
|
14
|
+
adapter_version = "0.15.10"
|
|
15
15
|
update_target = "ca-pi"
|
|
16
16
|
update_tag_prefix = "ca-pi-v"
|
|
17
17
|
update_command = "pi update npm:@arbiterforge/ca-pi"
|
|
@@ -313,7 +313,7 @@ class Host:
|
|
|
313
313
|
|
|
314
314
|
name = "claude"
|
|
315
315
|
adapter_name = "ca"
|
|
316
|
-
adapter_version = "2.23.
|
|
316
|
+
adapter_version = "2.23.2"
|
|
317
317
|
|
|
318
318
|
# Update-notifier descriptor. Each independently versioned host overrides
|
|
319
319
|
# these three values in its per-plugin _host.py. Keeping the target,
|
|
@@ -262,13 +262,27 @@ the filename, not a leaf-name alias. Named unittest commands require `-v` or
|
|
|
262
262
|
Plain exit checks cannot attest named tests.
|
|
263
263
|
|
|
264
264
|
Named npm commands support inspectable single-runner scripts, a contained
|
|
265
|
-
repository-relative `--prefix`, and arguments forwarded after `--`.
|
|
265
|
+
repository-relative `--prefix`, and arguments forwarded after `--`. A root script
|
|
266
|
+
may delegate once through `npm -w <name> run <script>` or
|
|
267
|
+
`npm --workspace <name> run <script>` when the name resolves uniquely in an array
|
|
268
|
+
of at most 32 explicit, contained workspace directories. The runner binds the root
|
|
269
|
+
manifest and every inspected workspace manifest. Globs, linked directories,
|
|
270
|
+
multiple workspace selectors and recursive npm delegation are unsupported.
|
|
271
|
+
Workspace delegation uses the declared cwd; a non-dot `--prefix` is unsupported
|
|
272
|
+
for this shape.
|
|
273
|
+
Preserve both npm argument boundaries when adding a reporter: for a root
|
|
274
|
+
`test:client` script containing `npm -w @example/client run test`, declare
|
|
275
|
+
`npm run test:client -- -- --reporter=verbose` to forward the reporter to Vitest.
|
|
276
|
+
The original root command still executes the full script. Compound
|
|
266
277
|
scripts and pre/post lifecycle scripts require separately declared direct-runner
|
|
267
278
|
commands; retain every check from the original suite when splitting commands.
|
|
268
279
|
On Windows, only the qualified standard `npm.cmd` layout is adapted: the runner
|
|
269
280
|
pins the wrapper, sibling `node.exe`, sibling `node_modules/npm/bin/npm-cli.js`,
|
|
270
281
|
and script manifest. It deliberately selects that sibling CLI rather than the
|
|
271
282
|
shim's optional global-prefix redirection. Unknown batch wrappers fail closed.
|
|
283
|
+
For workspace delegation, it also pins `npm-prefix.js` and checks that the nested
|
|
284
|
+
wrapper uses the same CLI, the default script shell, and no configured Node
|
|
285
|
+
options. Local executables shadowing nested npm are rejected.
|
|
272
286
|
Direct Node script entrypoints are also pinned. These launch-file bindings do
|
|
273
287
|
not establish transitive dependency closure for deliberately excluded inputs.
|
|
274
288
|
|
|
@@ -74,7 +74,7 @@ file maps those actions to Pi's extension API.
|
|
|
74
74
|
operator-launched Pi CLI, imported module, package root, and reported version.
|
|
75
75
|
It does not prove publisher authenticity. Verify the source separately with
|
|
76
76
|
`pi list` and `pi config`.
|
|
77
|
-
- The supported promotion target is exact Pi 0.
|
|
77
|
+
- The supported promotion target is exact Pi 1.0.0. The package is
|
|
78
78
|
published as `npm:@arbiterforge/ca-pi` on every `ca-pi-v*` tag (ADR-0029);
|
|
79
79
|
the pinned Git tag remains the reproducible install. A Pi-native embedded
|
|
80
80
|
farm worker is a future spike, not an installed dependency.
|
|
@@ -32,7 +32,7 @@ repository binary, mutate an artifact, or silently fall back to Markdown.
|
|
|
32
32
|
Pi bash wrapper. The exact shared-core `[H-03]` block is healthy and cannot stage files; execution
|
|
33
33
|
or a different block is unhealthy. This self-test does not traverse Pi's active dispatcher. Do not
|
|
34
34
|
rerun or respell it.
|
|
35
|
-
4. Its `active-dispatch` row remains degraded because supported Pi 0.
|
|
35
|
+
4. Its `active-dispatch` row remains degraded because supported Pi 1.0.0 public extension
|
|
36
36
|
APIs cannot submit that deterministic call through the active dispatcher. PI-AC-28 remains blocked
|
|
37
37
|
until supported-version real-host promotion/CI evidence closes the gap.
|
|
38
38
|
5. Resolve Python once, run `<plugin-root>/hooks/doctor.py`, and present its mechanical report
|