@staix/agent-hub 0.12.13 → 0.12.14

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
@@ -4,6 +4,10 @@ Issue and pull request numbers in the entries for 0.7.7 and earlier refer to the
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.12.14
8
+
9
+ - Normalize the remaining `ps lstart` identity reads to the shared pinned `LC_ALL=C`/`TZ=UTC` contract: the terminal-recovery launcher signature now goes through `processSignature`, and the MLX owner/generation identity read pins the same environment, so a record written under one timezone or locale still authenticates the same live process under another; strict PID/start/command identity and the fail-closed refusal paths are unchanged (#177).
10
+
7
11
  ## 0.12.13
8
12
 
9
13
  - Bind serialized original manifest copies to their own pin in study summary export: the copy's bytes verify against `original_copy_sha256` with the raw source pin kept distinct, the copy must equal the supervisor's canonical serialization of its parsed contents, and the runtime manifest may differ only by the recorded hub-version amendments; legacy provenance without the copy pin refuses explicitly instead of receiving an invented pin (#173).
@@ -11,3 +11,4 @@ Scope: the control WS and its protocol, state files, the Claude channel's reconn
11
11
  - Adding a native peer requires testing the command emitted by the actual recovery driver, not only its inspection or launch helpers. Never let a generic non-Codex branch treat a new peer as Claude.
12
12
  - Cross-version recovery must use the authenticated source protocol for prepare/commit/abort and the target protocol after startup. Prove the transition against a real prior release before claiming compatibility.
13
13
  - A zero-turn Claude session has no transcript, so it can never be resumed: every identity gate that compares session ids (restore, coordinator verify, daemon readiness) must tolerate a fresh session while the transcript is absent and stay strict the moment one exists. The commit snapshot records `sessionPersisted` for the restored daemon, and snapshots too old to have it are re-derived from disk. Never treat "session id changed" as proof of a lost conversation without checking the transcript first.
14
+ - The terminal-recovery launcher signature is the shared pinned `processSignature` (`src/pi/process-signature.ts`): a record written by one CLI invocation must compare equal when a later invocation re-reads the live launcher under another TZ/LC_ALL (#177). In-process `process.env` mutation does not reach Bun.spawnSync's default child environment, so cross-environment regressions are tested with real subprocesses.
@@ -7,3 +7,4 @@ Scope: the model relay and its journal, the hub's own inference and its slots, O
7
7
  - Secrets stay inside `OmniRoute`: never put the key or Access values in a log line, an error message, a return value or the generated Switchyard file (the key goes by env var name).
8
8
  - Switchyard's docs drift from the released binary. Any change to `switchyardToml` is checked with the real `switchyard-server --dry-run`, not only the stand-in in `test/fakes/`.
9
9
  - Shared inference slots must recover after a hub process dies, without evicting a live owner. Cancellation has to reach slot acquisition from the relay caller, not just exist in the helper signature.
10
+ - MLX owner and generation identity read `ps lstart` with LC_ALL=C/TZ=UTC pinned (#177): unpinned, a record written under one environment reads as a foreign process under another, and the fail-closed paths would refuse the hub's own server.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staix/agent-hub",
3
- "version": "0.12.13",
3
+ "version": "0.12.14",
4
4
  "description": "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-hub",
3
- "version": "0.12.13",
3
+ "version": "0.12.14",
4
4
  "description": "Channel between Claude Code and the agent-hub daemon: peer messages from Codex, Kimi and the local worker arrive as channel events; hub_send replies.",
5
5
  "author": {
6
6
  "name": "Young Joon Lee",
@@ -15403,7 +15403,7 @@ class ControlClient {
15403
15403
  // package.json
15404
15404
  var package_default = {
15405
15405
  name: "@staix/agent-hub",
15406
- version: "0.12.13",
15406
+ version: "0.12.14",
15407
15407
  description: "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
15408
15408
  license: "MIT",
15409
15409
  type: "module",
@@ -2,6 +2,7 @@ import { isAbsolute, join, resolve } from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
4
  import { randomUUID } from "node:crypto";
5
+ import { processSignature } from "../pi/process-signature.ts";
5
6
 
6
7
  /** The only terminal commands used by this adapter. Keep this list in sync with Orca's public CLI. */
7
8
  export type OrcaTerminalCommand = "list" | "show" | "wait" | "close" | "create";
@@ -226,15 +227,7 @@ function resolveOrcaExecutable(): string {
226
227
 
227
228
  function defaultProcessIdentity(pid: number): string | undefined {
228
229
  if (!Number.isInteger(pid) || pid <= 0) return undefined;
229
- try {
230
- const result = Bun.spawnSync(["ps", "-p", String(pid), "-o", "lstart=,comm="], { stdout: "pipe", stderr: "pipe" });
231
- if (result.exitCode !== 0) return undefined;
232
- const identity = result.stdout.toString().trim();
233
- if (!identity) return undefined;
234
- return new Bun.CryptoHasher("sha256").update(identity).digest("hex");
235
- } catch {
236
- return undefined;
237
- }
230
+ return processSignature(pid);
238
231
  }
239
232
 
240
233
  function defaultRunner(executable: string): CommandRunner {
package/src/models/mlx.ts CHANGED
@@ -97,7 +97,9 @@ function readOwner(runtimeDir: string): OwnerRecord | undefined {
97
97
 
98
98
  function processInfo(pid: number): ProcessSignature | undefined {
99
99
  try {
100
- const result = spawnSync("ps", ["-p", String(pid), "-o", "lstart=", "-o", "command="], { encoding: "utf8" });
100
+ // `lstart` follows the reader's TZ and locale; pin the read so a record written by
101
+ // one hub invocation compares equal when a later one re-reads it elsewhere (#177).
102
+ const result = spawnSync("ps", ["-p", String(pid), "-o", "lstart=", "-o", "command="], { encoding: "utf8", env: { ...process.env, LC_ALL: "C", TZ: "UTC" } });
101
103
  if (result.status !== 0) return undefined;
102
104
  const line = String(result.stdout).trim();
103
105
  const match = /^(\w{3}\s+\w{3}\s+\d+\s+\d+:\d+:\d+\s+\d{4})\s+(.*)$/.exec(line);
@@ -2,13 +2,14 @@ import { execFileSync } from "node:child_process";
2
2
  import { createHash } from "node:crypto";
3
3
 
4
4
  /**
5
- * The one process-identity contract behind the Pi owner claim and every later liveness
6
- * re-inspection: pid plus the `ps` rendering of its start time and command. `lstart`
7
- * follows the reader's TZ and locale, so the read pins LC_ALL=C and TZ=UTC, the same
8
- * contract as `processTable` in src/hub/child-process.ts. Without the pin a hub on one
9
- * timezone and its Pi child on another hash different strings for the same process and the
10
- * valid owner is refused at startup (#174). Only the `ps` invocation is pinned; the user's
11
- * own environment is untouched. Node APIs only: the extension runs inside Pi, not Bun.
5
+ * The one process-identity contract behind the Pi owner claim, every later liveness
6
+ * re-inspection, the terminal-recovery launcher signature and any other hub identity
7
+ * hash: pid plus the `ps` rendering of its start time and command. `lstart` follows
8
+ * the reader's TZ and locale, so the read pins LC_ALL=C and TZ=UTC, the same contract
9
+ * as `processTable` in src/hub/child-process.ts. Without the pin a writer and a reader
10
+ * in different environments hash different strings for the same process and the valid
11
+ * owner is refused (#174, #177). Only the `ps` invocation is pinned; the user's own
12
+ * environment is untouched. Node APIs only: the extension runs inside Pi, not Bun.
12
13
  */
13
14
  export function processSignature(pid: number): string | undefined {
14
15
  try {