pi-orchestrator-config 4.3.4 → 4.3.5

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
@@ -452,28 +452,34 @@ variables. Pi-config settings (image_model, acpx_agents, vertex_claude_1m, etc.)
452
452
  go in `.pi/pi-config-settings.json` — not here:
453
453
 
454
454
  ```env
455
- # Timezone (host timezone for correct timestamps)
456
- TZ=Asia/Jerusalem
455
+ # Timezone (host timezone for correct timestamps) — plain value, no inline comment:
456
+ # `docker run --env-file` does not strip trailing comments.
457
+ TZ=America/New_York
457
458
 
458
459
  # Host username (creates /home/<user> -> container home symlink so host paths resolve)
459
- PI_HOST_USER=myakove
460
+ PI_HOST_USER=youruser
461
+
462
+ # Host UID/GID belong on the docker alias (see below), not hardcoded here:
463
+ # -e PI_HOST_UID="$(id -u)" -e PI_HOST_GID="$(id -g)"
464
+ # The entrypoint remaps user `node` to those ids so bind-mounted 0600/0700
465
+ # files (~/.npmrc, ~/.ssh) are readable. IPA/LDAP users are not UID 1000.
460
466
 
461
467
  # Google Cloud / Vertex AI
462
468
  GOOGLE_CLOUD_PROJECT=your-project-id
463
469
  GOOGLE_CLOUD_LOCATION=us-east5
464
- GOOGLE_APPLICATION_CREDENTIALS=/home/myakove/.config/gcloud/application_default_credentials.json
470
+ GOOGLE_APPLICATION_CREDENTIALS=/home/youruser/.config/gcloud/application_default_credentials.json
465
471
 
466
472
  # GitHub
467
473
  GITHUB_TOKEN=ghp_xxx
468
474
  GITHUB_API_TOKEN=ghp_xxx
469
- GH_CONFIG_DIR=/home/myakove/.config/gh
475
+ GH_CONFIG_DIR=/home/youruser/.config/gh
470
476
 
471
477
  # Gemini (optional — required for image generation)
472
478
  GEMINI_API_KEY=xxx
473
479
 
474
480
  # mcpl (MCP Launchpad) config path inside the container (must match mount target)
475
481
  # Use your actual home path — it resolves inside the container via the PI_HOST_USER symlink
476
- MCPL_CONFIG_FILES=/home/myakove/.config/mcpl/mcp.json
482
+ MCPL_CONFIG_FILES=/home/youruser/.config/mcpl/mcp.json
477
483
  ```
478
484
 
479
485
  Pass via `--env-file /path/to/.env` in the docker run command.
@@ -645,6 +651,7 @@ PI_PIDIFF_ENABLE=false pi
645
651
  | `kubectl` / `oc` | Kubernetes and OpenShift CLI |
646
652
  | `agent-browser` | Browser automation CLI (navigate, click, screenshot, forms) |
647
653
  | `procps` | Process utilities (ps, top, pgrep, pkill) |
654
+ | `passwd` | `usermod`/`groupmod` — init remaps user `node` to `PI_HOST_UID`/`PI_HOST_GID` |
648
655
  | `docker` / `podman` | Container CLIs (used via `docker-safe` read-only wrapper) |
649
656
  | `docker-safe` | Restricted Docker/Podman wrapper — container only (ps, logs, inspect, top, stats) |
650
657
  | `jq` | JSON processing |
@@ -701,6 +708,8 @@ alias pi-docker='docker pull ghcr.io/myk-org/pi-config:latest && \
701
708
  --name "pi-config-$(basename $PWD)-$(date +%s)" \
702
709
  --network host \
703
710
  --env-file "$HOME/.pi/.env" \
711
+ -e PI_HOST_UID="$(id -u)" \
712
+ -e PI_HOST_GID="$(id -g)" \
704
713
  -v "$PWD":"$PWD":rw \
705
714
  -v "$HOME/.pi":"$HOME/.pi":rw \
706
715
  -v "$HOME/.gitconfig":"$HOME/.gitconfig":ro \
@@ -722,7 +731,10 @@ alias pi-docker='docker pull ghcr.io/myk-org/pi-config:latest && \
722
731
 
723
732
  Then just run `pi-docker` from any project directory.
724
733
 
725
- > **Startup note:** The container runs as non-root user `node` (UID 1000).
734
+ > **Startup note:** The container runs as non-root user `node`. At start the
735
+ > entrypoint remaps `node` to `PI_HOST_UID`/`PI_HOST_GID` (pass `$(id -u)` /
736
+ > `$(id -g)` on the alias) so bind-mounted secrets match the host user.
737
+ > Without those env vars, `node` stays UID 1000 (breaks IPA/LDAP hosts).
726
738
  > `pi install` runs on each start.
727
739
  > A `WARNING` on stderr is normal when the package is already cached in `~/.pi`.
728
740
  > If pi misbehaves or the warning persists, verify network connectivity
@@ -919,15 +919,31 @@ export function registerAsyncAgents(
919
919
  }), { mode: 0o600 });
920
920
 
921
921
  // Find the runner script
922
- const runnerPath = path.join(path.dirname(new URL(import.meta.url).pathname), "async-runner.ts");
922
+ const runnerPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "async-runner.ts");
923
923
 
924
924
  // Find jiti for TypeScript execution
925
925
  let jitiCliPath: string | undefined;
926
926
  // Strategy 1: resolve from pi's own require context
927
+ // NOTE: newer pi SDK "exports" maps hide ./package.json — fall back to
928
+ // resolving the main entry and walking up to its package root.
927
929
  try {
928
- const piPkgDir = path.dirname(require.resolve("@earendil-works/pi-coding-agent/package.json"));
929
- const candidate = path.join(piPkgDir, "node_modules/jiti/lib/jiti-cli.mjs");
930
- if (fs.existsSync(candidate)) jitiCliPath = candidate;
930
+ const req = createRequire(import.meta.url);
931
+ let piPkgJsonPath: string | undefined;
932
+ try {
933
+ piPkgJsonPath = req.resolve("@earendil-works/pi-coding-agent/package.json");
934
+ } catch {
935
+ const entry = req.resolve("@earendil-works/pi-coding-agent");
936
+ let dir = path.dirname(entry);
937
+ while (dir !== path.dirname(dir)) {
938
+ const pj = path.join(dir, "package.json");
939
+ if (fs.existsSync(pj) && JSON.parse(fs.readFileSync(pj, "utf8"))?.name === "@earendil-works/pi-coding-agent") { piPkgJsonPath = pj; break; }
940
+ dir = path.dirname(dir);
941
+ }
942
+ }
943
+ if (piPkgJsonPath) {
944
+ const candidate = path.join(path.dirname(piPkgJsonPath), "node_modules/jiti/lib/jiti-cli.mjs");
945
+ if (fs.existsSync(candidate)) jitiCliPath = candidate;
946
+ }
931
947
  } catch (e: any) {
932
948
  log.debug(`jiti strategy 1 (require.resolve) failed: ${e?.message || e}`);
933
949
  }
@@ -947,6 +963,28 @@ export function registerAsyncAgents(
947
963
  }
948
964
  }
949
965
 
966
+ // Strategy 3: this package's own node_modules — git/stow installs ship
967
+ // jiti themselves (npm consumers are covered by strategies 1-2).
968
+ if (!jitiCliPath) {
969
+ try {
970
+ const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
971
+ const candidate = path.join(pkgRoot, "node_modules/jiti/lib/jiti-cli.mjs");
972
+ if (fs.existsSync(candidate)) jitiCliPath = candidate;
973
+ } catch (e: any) {
974
+ log.debug(`jiti strategy 3 (package root) failed: ${e?.message || e}`);
975
+ }
976
+ }
977
+
978
+ // Fail fast with a self-explanatory error instead of spawning a child that
979
+ // cannot load TypeScript (bare node dies on ".js"→".ts" specifiers).
980
+ if (!jitiCliPath) {
981
+ log.error(`async-spawn: ${id} — no jiti-cli.mjs found (strategies 1-3 failed); refusing to spawn`);
982
+ // Clean up the artifacts we already created — nothing will consume them.
983
+ try { fs.rmSync(configPath, { force: true }); } catch {}
984
+ try { fs.rmSync(workerDir, { recursive: true, force: true }); } catch {}
985
+ return { id, error: "jiti-cli.mjs not found — cannot execute async agent (see orchestrator logs)" };
986
+ }
987
+
950
988
  const spawnArgs = jitiCliPath
951
989
  ? [jitiCliPath, runnerPath, configPath]
952
990
  : [runnerPath, configPath];
@@ -957,6 +995,10 @@ export function registerAsyncAgents(
957
995
  PI_SUBAGENT_CHILD: "1",
958
996
  PI_AGENT_NAME: agentName,
959
997
  PI_PRIMARY_MODEL: process.env.PI_PRIMARY_MODEL || process.env.PI_MODEL || "",
998
+ // Isolate children from the shared jiti FS cache — other sessions running
999
+ // different node/jiti versions can poison it and kill children at boot.
1000
+ JITI_CACHE: "false",
1001
+ JITI_FS_CACHE: "false",
960
1002
  __PI_CONFIG_SESSION_ID: (globalThis as any).__piConfigSessionId || "",
961
1003
  __PI_PARENT_SESSION_ID: (globalThis as any).__piConfigSessionId || "",
962
1004
  };
@@ -1004,14 +1046,21 @@ export function registerAsyncAgents(
1004
1046
  };
1005
1047
  asyncState.jobs.set(id, job);
1006
1048
 
1049
+ const stderrLogPath = path.join(workerDir, "child-stderr.log");
1050
+ const stderrLog = fs.createWriteStream(stderrLogPath, { flags: "w" });
1051
+ stderrLog.on("error", () => {}); // never let a log write failure crash the session
1052
+ log.debug(`async-spawn: ${job.id} stderr → ${stderrLogPath}`);
1053
+
1007
1054
  const proc = spawn(process.execPath, spawnArgs, {
1008
1055
  cwd,
1009
- stdio: "ignore",
1056
+ stdio: ["ignore", "ignore", "pipe"],
1010
1057
  windowsHide: true,
1011
1058
  env: spawnEnv,
1012
1059
  });
1060
+ proc.stderr?.pipe(stderrLog);
1013
1061
 
1014
1062
  proc.once("error", (err) => {
1063
+ try { stderrLog.destroy(); } catch {}
1015
1064
  log.info(`spawn-error: ${job.id} — ${err.message}`);
1016
1065
  if (job.status === "complete" || job.status === "failed") return;
1017
1066
  job.status = "failed";
@@ -1037,8 +1086,27 @@ export function registerAsyncAgents(
1037
1086
  setTimeout(() => {
1038
1087
  if (job.status === "complete" || job.status === "failed") return;
1039
1088
  log.info(`spawn-exit: ${job.id} — code=${code} signal=${signal}`);
1089
+ let stderrTail = "";
1090
+ try {
1091
+ if (fs.existsSync(stderrLogPath)) {
1092
+ // Tail-read only — the log can be arbitrarily large on noisy failures.
1093
+ const size = fs.statSync(stderrLogPath).size;
1094
+ const start = Math.max(0, size - 4096);
1095
+ let raw = "";
1096
+ if (size > 0) {
1097
+ const fd = fs.openSync(stderrLogPath, "r");
1098
+ try {
1099
+ const buf = Buffer.alloc(Math.min(size - start, 4096));
1100
+ fs.readSync(fd, buf, 0, buf.length, start);
1101
+ raw = buf.toString("utf8");
1102
+ } finally { fs.closeSync(fd); }
1103
+ }
1104
+ stderrTail = raw.trim().slice(-1200);
1105
+ }
1106
+ } catch {}
1107
+ log.warn(`async-child failed: ${job.id} code=${code}${stderrTail ? " (see child-stderr.log)" : " (no stderr captured)"}`);
1040
1108
  job.status = "failed";
1041
- job.output = `Process exited with code ${code} signal ${signal}`;
1109
+ job.output = `Process exited with code ${code} signal ${signal}${stderrTail ? `\n--- child stderr (tail) ---\n${stderrTail}` : ""}`;
1042
1110
  job.durationMs = Date.now() - job.startedAt;
1043
1111
  job.updatedAt = Date.now();
1044
1112
  job.sideEffectsApplied = true;
@@ -152,18 +152,28 @@ export const MIN_PI_VERSION = "0.84.0";
152
152
 
153
153
  /** Get the installed pi version from its package.json. */
154
154
  export function getPiVersion(): string | null {
155
+ // 1) Resolve from the running binary — walk up until we find the package
156
+ // root (works for any layout: dist/cli.js, dist/bundle/cli.js, src/…).
155
157
  try {
156
- // Resolve from pi's own install location (process.argv[1] → dist/cli.js → package root)
157
158
  const piScript = process.argv[1];
158
159
  if (piScript) {
159
- const realPath = fs.realpathSync(piScript);
160
- const piPkgPath = path.join(path.dirname(path.dirname(realPath)), "package.json");
161
- if (fs.existsSync(piPkgPath)) {
162
- const pkg = JSON.parse(fs.readFileSync(piPkgPath, "utf-8"));
163
- if (pkg.name === "@earendil-works/pi-coding-agent" && pkg.version) return pkg.version;
160
+ let dir = path.dirname(fs.realpathSync(piScript));
161
+ while (dir !== path.dirname(dir)) {
162
+ const pkgPath = path.join(dir, "package.json");
163
+ if (fs.existsSync(pkgPath)) {
164
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
165
+ if (pkg.name === "@earendil-works/pi-coding-agent" && pkg.version) return pkg.version;
166
+ }
167
+ dir = path.dirname(dir);
164
168
  }
165
169
  }
166
170
  } catch {}
171
+ // 2) Ask the CLI the user actually runs — authoritative for wrapper/bundle layouts.
172
+ try {
173
+ const out = execFileSync("pi", ["--version"], { timeout: 5000, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
174
+ const m = out.match(/\d+\.\d+\.\d+(?:-[\w.]+)?/);
175
+ if (m) return m[0];
176
+ } catch {}
167
177
  return null;
168
178
  }
169
179
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-orchestrator-config",
3
- "version": "4.3.4",
3
+ "version": "4.3.5",
4
4
  "description": "Orchestrator pattern for pi: specialist subagents, enforcement, code review loop, workflow commands",
5
5
  "keywords": [
6
6
  "pi-package"