@zvada/agent-server 0.3.1 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zvada/agent-server",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Harness-agnostic agent execution engine: run Claude Code, Codex (SDK/CLI + app-server), and any ACP agent behind one interface with a normalized event stream, multi-turn sessions, and resume. Root export is the wire contract; /core, /server, /client are the seats.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -25,7 +25,13 @@
25
25
  "engines": {
26
26
  "bun": ">=1.2.0"
27
27
  },
28
- "files": ["src", "docs", "README.md", "CHANGELOG.md", "LICENSE"],
28
+ "files": [
29
+ "src",
30
+ "docs",
31
+ "README.md",
32
+ "CHANGELOG.md",
33
+ "LICENSE"
34
+ ],
29
35
  "publishConfig": {
30
36
  "access": "public"
31
37
  },
@@ -1,6 +1,9 @@
1
1
  import { type ChildProcess, spawn as nodeSpawn } from "node:child_process";
2
2
  import { StringDecoder } from "node:string_decoder";
3
3
 
4
+ /** Cap the retained stderr tail — enough for the CLI's error, never a log flood. */
5
+ const STDERR_TAIL_MAX = 2048;
6
+
4
7
  /** A JSON-RPC notification (server → client, no id). */
5
8
  export interface CodexNotification {
6
9
  method: string;
@@ -54,6 +57,11 @@ export class CodexAppServerClient {
54
57
  private readonly closeHandlers = new Set<(error?: Error) => void>();
55
58
  private requestHandler?: ServerRequestHandler;
56
59
  private exited = false;
60
+ /** Bounded rolling tail of the subprocess's stderr — folded into the exit
61
+ * error so failures carry the CLI's own explanation. */
62
+ private stderrTail = "";
63
+ /** Chunk-boundary-safe UTF-8 decoding for the stderr tail; reset per spawn. */
64
+ private stderrDecoder = new StringDecoder("utf8");
57
65
 
58
66
  constructor(private readonly opts: CodexAppServerClientOptions = {}) {}
59
67
 
@@ -140,14 +148,32 @@ export class CodexAppServerClient {
140
148
  stdio: ["pipe", "pipe", "pipe"],
141
149
  });
142
150
  this.proc = proc;
151
+ this.stderrTail = "";
152
+ this.stderrDecoder = new StringDecoder("utf8");
143
153
  proc.stdout?.on("data", (chunk: Buffer) => this.onStdout(chunk));
144
154
  proc.stdin?.on("error", (err) => this.onExit(err));
145
155
  proc.stdout?.on("error", (err) => this.onExit(err));
146
- proc.stderr?.on("data", () => {});
156
+ // Keep a bounded tail of stderr: it is the ONLY place the CLI explains
157
+ // itself (a missing/expired login prints there and exits 1). Discarding
158
+ // it reduced every failure to "exited (code=1)" — indistinguishable
159
+ // from a crash for the integrator staring at the turn error. Decoded
160
+ // via StringDecoder so a UTF-8 sequence split across chunks survives.
161
+ proc.stderr?.on("data", (chunk: Buffer) => {
162
+ this.stderrTail = (this.stderrTail + this.stderrDecoder.write(chunk)).slice(-STDERR_TAIL_MAX);
163
+ });
147
164
  proc.on("error", (err) => this.onExit(err));
148
- proc.on("exit", (code, signal) =>
149
- this.onExit(new Error(`codex app-server exited (code=${code} signal=${signal})`)),
150
- );
165
+ // Build the exit error on CLOSE, not exit: `exit` can fire before the
166
+ // stderr stream flushes its final data, and the final flush is usually
167
+ // the line that says WHY. `close` is guaranteed after both.
168
+ proc.on("close", (code, signal) => {
169
+ this.stderrTail = (this.stderrTail + this.stderrDecoder.end()).slice(-STDERR_TAIL_MAX);
170
+ const tail = this.stderrTail.trim().replace(/\s+/g, " ");
171
+ this.onExit(
172
+ new Error(
173
+ `codex app-server exited (code=${code} signal=${signal})${tail ? `: ${tail}` : ""}`,
174
+ ),
175
+ );
176
+ });
151
177
  }
152
178
 
153
179
  private onStdout(chunk: Buffer): void {