@arbiterforge/ca-pi 0.10.0 → 0.10.2

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 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.17.0" src="https://img.shields.io/badge/version-2.17.0-2b7489">
13
+ <img alt="version 2.17.1" src="https://img.shields.io/badge/version-2.17.1-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-23-555">
16
16
  <img alt="agents" src="https://img.shields.io/badge/agents-19-555">
@@ -115,7 +115,7 @@ Approve the normal plugin trust prompt, open the target repository, and continue
115
115
 
116
116
  ### Codex CLI
117
117
 
118
- The public GitHub-slug flow is **available now**. The repository currently ships `ca-codex 0.9.0`;
118
+ The public GitHub-slug flow is **available now**. The repository currently ships `ca-codex 0.9.1`;
119
119
  the dated end-to-end public-install record discovered `ca-codex 0.2.4` from release `v2.8.13`.
120
120
  Current packaging and shared-core parity are continuously verified, while that dated live-install
121
121
  record stays labeled rather than being silently promoted to evidence for a newer adapter:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arbiterforge/ca-pi",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "license": "AGPL-3.0-only",
5
5
  "repository": {
6
6
  "type": "git",
@@ -4,6 +4,18 @@ All notable changes to `ca-pi` are documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.10.2] - 2026-09-03
8
+
9
+ ### Fixed
10
+
11
+ - Report precise Windows containment startup stages and reject output-overflow admission races.
12
+
13
+ ## [0.10.1] - 2026-09-03
14
+
15
+ ### Fixed
16
+
17
+ - Prevent read-only filename searches from falsely triggering protected-state interpreter guards while preserving mutation enforcement.
18
+
7
19
  ## [0.10.0] - 2026-09-02
8
20
 
9
21
  ### Added
@@ -1546,10 +1546,14 @@ public static class CodeArbiterJob {
1546
1546
  '@
1547
1547
  try {
1548
1548
  Add-Type -TypeDefinition $source -Language CSharp | Out-Null
1549
+ [Console]::Out.WriteLine('COMPILED')
1550
+ [Console]::Out.Flush()
1549
1551
  $line = [Console]::In.ReadLine()
1550
1552
  if ($null -eq $line -or $line -notmatch '^([1-9][0-9]*) ([1-9][0-9]*)$') { exit 40 }
1551
1553
  [UInt32]$target = $Matches[1]
1552
1554
  [UInt32]$parent = $Matches[2]
1555
+ [Console]::Out.WriteLine('PID_ACCEPTED')
1556
+ [Console]::Out.Flush()
1553
1557
  $job = [CodeArbiterJob]::CreateAndAssign($target)
1554
1558
  if ($job -eq [IntPtr]::Zero) { exit 41 }
1555
1559
  try {
@@ -1642,12 +1646,31 @@ async function awaitProgressTokens(readLine, expected, options) {
1642
1646
  if (idleMs > ceilingMs) throw new Error("progress idleMs must be bounded by the progress ceiling");
1643
1647
  const now = options.now ?? Date.now;
1644
1648
  const deadline = now() + ceilingMs;
1649
+ if (options.windowsJobStages && (expected.length !== 2 || expected[0] !== WINDOWS_JOB_STARTING || expected[1] !== WINDOWS_JOB_READY)) {
1650
+ throw new Error("Windows startup diagnostics require the bounded admission sequence");
1651
+ }
1652
+ let lastStage = "WAITING";
1645
1653
  for (const token of expected) {
1646
- const remaining = deadline - now();
1647
- if (remaining <= 0) return Object.freeze({ state: "stalled", phase: token, waitedMs: 0 });
1648
1654
  const started = now();
1649
- const line = await readLine(Math.min(idleMs, remaining));
1650
- if (line !== token) return Object.freeze({ state: "stalled", phase: token, waitedMs: now() - started });
1655
+ const phaseDeadline = Math.min(deadline, started + idleMs);
1656
+ const stalled = () => Object.freeze({
1657
+ state: "stalled",
1658
+ phase: token,
1659
+ waitedMs: now() - started,
1660
+ ...options.windowsJobStages ? { lastStage } : {}
1661
+ });
1662
+ const diagnostics = options.windowsJobStages && token === WINDOWS_JOB_READY ? ["COMPILED", "PID_ACCEPTED"] : [];
1663
+ for (const diagnostic of diagnostics) {
1664
+ const remaining2 = phaseDeadline - now();
1665
+ if (remaining2 <= 0) return stalled();
1666
+ if (await readLine(remaining2) !== diagnostic) return stalled();
1667
+ lastStage = diagnostic;
1668
+ }
1669
+ const remaining = phaseDeadline - now();
1670
+ if (remaining <= 0) return stalled();
1671
+ const line = await readLine(remaining);
1672
+ if (line !== token) return stalled();
1673
+ if (options.windowsJobStages && token === WINDOWS_JOB_STARTING) lastStage = "STARTING";
1651
1674
  }
1652
1675
  return Object.freeze({ state: "ready" });
1653
1676
  }
@@ -1831,7 +1854,9 @@ function startWindowsJobGuard(pid, timing) {
1831
1854
  let closePending;
1832
1855
  let armed = false;
1833
1856
  let outputEnded = false;
1857
+ let protocolFailed = false;
1834
1858
  let outputBuffer = "";
1859
+ let queuedOutputBytes = 0;
1835
1860
  const outputLines = [];
1836
1861
  const outputWaiters = [];
1837
1862
  let resolveExitCode;
@@ -1851,7 +1876,11 @@ function startWindowsJobGuard(pid, timing) {
1851
1876
  settleExitCode();
1852
1877
  };
1853
1878
  const readOutputLine = (timeoutMs) => {
1854
- if (outputLines.length > 0) return Promise.resolve(outputLines.shift());
1879
+ if (outputLines.length > 0) {
1880
+ const { line, bytes } = outputLines.shift();
1881
+ queuedOutputBytes -= bytes;
1882
+ return Promise.resolve(line);
1883
+ }
1855
1884
  if (outputEnded) return Promise.resolve(void 0);
1856
1885
  return new Promise((resolveLine) => {
1857
1886
  let timer;
@@ -1870,8 +1899,11 @@ function startWindowsJobGuard(pid, timing) {
1870
1899
  helper.stdout.setEncoding("utf8");
1871
1900
  helper.stdout.on("data", (chunk) => {
1872
1901
  if (outputEnded) return;
1873
- outputBuffer += chunk;
1874
- if (Buffer.byteLength(outputBuffer, "utf8") > MAX_JOB_PROTOCOL_BYTES) {
1902
+ if (queuedOutputBytes + Buffer.byteLength(outputBuffer, "utf8") + Buffer.byteLength(chunk, "utf8") > MAX_JOB_PROTOCOL_BYTES) {
1903
+ protocolFailed = true;
1904
+ outputLines.length = 0;
1905
+ queuedOutputBytes = 0;
1906
+ outputBuffer = "";
1875
1907
  finishOutput();
1876
1908
  try {
1877
1909
  helper.stdin.end();
@@ -1879,13 +1911,17 @@ function startWindowsJobGuard(pid, timing) {
1879
1911
  }
1880
1912
  return;
1881
1913
  }
1914
+ outputBuffer += chunk;
1882
1915
  let newline = outputBuffer.indexOf("\n");
1883
1916
  while (newline >= 0) {
1917
+ const bytes = Buffer.byteLength(outputBuffer.slice(0, newline + 1), "utf8");
1884
1918
  const line = outputBuffer.slice(0, newline).replace(/\r$/u, "");
1885
1919
  outputBuffer = outputBuffer.slice(newline + 1);
1886
1920
  const waiter = outputWaiters.shift();
1887
- if (waiter === void 0) outputLines.push(line);
1888
- else waiter(line);
1921
+ if (waiter === void 0) {
1922
+ outputLines.push({ line, bytes });
1923
+ queuedOutputBytes += bytes;
1924
+ } else waiter(line);
1889
1925
  newline = outputBuffer.indexOf("\n");
1890
1926
  }
1891
1927
  });
@@ -1912,8 +1948,9 @@ function startWindowsJobGuard(pid, timing) {
1912
1948
  const finish = (accepted) => {
1913
1949
  if (settled) return false;
1914
1950
  settled = true;
1915
- resolveReady(accepted);
1916
- if (!accepted) {
1951
+ const admitted = accepted && !protocolFailed;
1952
+ resolveReady(admitted);
1953
+ if (!admitted) {
1917
1954
  try {
1918
1955
  helper.stdin.end();
1919
1956
  } catch {
@@ -1930,9 +1967,9 @@ function startWindowsJobGuard(pid, timing) {
1930
1967
  if (!intentional) finish(false);
1931
1968
  });
1932
1969
  helper.once("error", () => finish(false));
1933
- void awaitProgressTokens(readOutputLine, WINDOWS_JOB_READY_TOKENS, { ceilingMs, idleMs }).then((outcome) => {
1970
+ void awaitProgressTokens(readOutputLine, WINDOWS_JOB_READY_TOKENS, { ceilingMs, idleMs, windowsJobStages: true }).then((outcome) => {
1934
1971
  const refused = finish(outcome.state === "ready");
1935
- if (refused && outcome.state === "stalled") stallDiagnostic = `stalled at ${outcome.phase} after ${outcome.waitedMs}ms`;
1972
+ if (refused && outcome.state === "stalled") stallDiagnostic = `stalled at ${outcome.phase} after ${outcome.waitedMs}ms; last startup stage ${outcome.lastStage}`;
1936
1973
  }, () => finish(false));
1937
1974
  try {
1938
1975
  helper.stdin.write(`${pid} ${process.pid}
@@ -1950,7 +1987,7 @@ function startWindowsJobGuard(pid, timing) {
1950
1987
  return stallDiagnostic;
1951
1988
  },
1952
1989
  async arm(rootPid) {
1953
- if (armed || !positivePid(rootPid) || !await ready || closed) return false;
1990
+ if (armed || !positivePid(rootPid) || !await ready || closed || protocolFailed) return false;
1954
1991
  armed = true;
1955
1992
  const watched = readOutputLine(idleMs);
1956
1993
  const written = await new Promise((resolveWrite) => {
@@ -449,6 +449,16 @@ GATE_MARKER_WRITE_RE = re.compile(
449
449
  _INTERP_TOKENS = (r"python3?|python2|py|node|deno|bun|perl|ruby|php"
450
450
  r"|sh|bash|zsh|pwsh|powershell")
451
451
 
452
+ # A word boundary also matches the `py` in `catalog.py`: that is a filename,
453
+ # not an interpreter. Share the corrected left edge across every interpreter
454
+ # leg so read-only filename arguments cannot falsely imply a protected write.
455
+ # Preserve slash/backslash and quote boundaries for executable paths, and the
456
+ # existing right edge for versioned executables and Windows `.exe` spellings.
457
+ # Consume leading dots at that boundary: `./.python` is an executable basename,
458
+ # unlike the interpreter-looking extension in `catalog.python`.
459
+ # This remains conservative lexical detection, not command-position parsing.
460
+ _INTERP_EXECUTABLE = r"(?<![\w.])\.*(" + _INTERP_TOKENS + r")\b"
461
+
452
462
  # The inline-code switch that makes an interpreter EXECUTE A STRING rather
453
463
  # than run a file. `-c` (python/py/sh/bash/zsh/pwsh), `-e`/`-E` (perl,
454
464
  # ruby, node, bun), `-r` (php), `-p`/`--print`/`--eval` (node), and
@@ -476,7 +486,7 @@ _INTERP_INLINE_CODE = (
476
486
  # whereas handing a board filename to `taskwrite.py` is the sanctioned
477
487
  # call.
478
488
  GATE_MARKER_INTERP_RE = re.compile(
479
- r"\b(" + _INTERP_TOKENS + r")\b[\s\S]*" + GATE_MARKER, re.I,
489
+ _INTERP_EXECUTABLE + r"[\s\S]*" + GATE_MARKER, re.I,
480
490
  )
481
491
 
482
492
  # #574: H-05/H-11/H-18 carried NO interpreter leg at all — an inline-code
@@ -496,13 +506,13 @@ GATE_MARKER_INTERP_RE = re.compile(
496
506
  # target name may sit on different physical lines of the SAME multi-line
497
507
  # `-c`/`-e` payload.
498
508
  LOG_INTERP_RE = re.compile(
499
- r"\b(" + _INTERP_TOKENS + r")\b[\s\S]*" + LOG_NAMES, re.I,
509
+ _INTERP_EXECUTABLE + r"[\s\S]*" + LOG_NAMES, re.I,
500
510
  )
501
511
  DECISIONS_INTERP_RE = re.compile(
502
- r"\b(" + _INTERP_TOKENS + r")\b[\s\S]*" + DECISIONS, re.I,
512
+ _INTERP_EXECUTABLE + r"[\s\S]*" + DECISIONS, re.I,
503
513
  )
504
514
  CONTEXT_INTERP_RE = re.compile(
505
- r"\b(" + _INTERP_TOKENS + r")\b[\s\S]*" + CONTEXT_MD, re.I,
515
+ _INTERP_EXECUTABLE + r"[\s\S]*" + CONTEXT_MD, re.I,
506
516
  )
507
517
 
508
518
  # H-22's shell flank: the protected-state registry (B1/#564) — Write/Edit are
@@ -731,7 +741,7 @@ def _state_write_res(basename, rel_path=None):
731
741
  + _STATE_NAME_RIGHT_EDGE, re.I,
732
742
  )
733
743
  interp_re = re.compile(
734
- r"\b(" + _INTERP_TOKENS + r")\b[^\n]*?" + _INTERP_INLINE_CODE
744
+ _INTERP_EXECUTABLE + r"[^\n]*?" + _INTERP_INLINE_CODE
735
745
  + r"[\s\S]*" + name + _STATE_NAME_RIGHT_EDGE, re.I,
736
746
  )
737
747
  return redirect_re, write_re, git_restore_re, interp_re
@@ -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.10.0"
14
+ adapter_version = "0.10.2"
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.17.0"
316
+ adapter_version = "2.17.1"
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,