@cjhyy/code-shell-capability-coding 0.9.7 → 0.9.9

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.
@@ -24,6 +24,10 @@ import { spawn } from "node:child_process";
24
24
  import { createInterface } from "node:readline";
25
25
  /** Cap per NDJSON line. Generous for reasoning text; guards parse latency. */
26
26
  const MAX_LINE_BYTES = 16 * 1024 * 1024;
27
+ /** Keep launch diagnostics useful without copying unbounded process metadata. */
28
+ function diagnosticText(value) {
29
+ return value.replace(/[\r\n\t]/g, " ").slice(0, 300);
30
+ }
27
31
  export class CodexAppServerClient {
28
32
  options;
29
33
  child;
@@ -79,9 +83,24 @@ export class CodexAppServerClient {
79
83
  if (text)
80
84
  this.log("appserver.stderr", { bytes: text.length, preview: text.slice(0, 300) });
81
85
  });
82
- const onGone = (why) => () => this.failAll(why);
83
- this.child.on("exit", onGone("app-server exited"));
84
- this.child.on("error", onGone("app-server failed to start"));
86
+ // A GUI host can have a different PATH from the user's terminal. Preserve
87
+ // the OS cause so a missing executable/cwd is distinguishable from a server
88
+ // that launched and exited. Never copy spawnargs or the environment: both
89
+ // can contain credentials for the runtime's MCP bridge.
90
+ const launch = {
91
+ command: diagnosticText(command),
92
+ cwd: diagnosticText(this.options.cwd ?? process.cwd()),
93
+ };
94
+ this.child.on("exit", (code, signal) => {
95
+ const status = signal ? `signal ${signal}` : `code ${code ?? "unknown"}`;
96
+ this.failAll(`app-server exited (${status})`, { ...launch, code, signal });
97
+ });
98
+ this.child.on("error", (error) => {
99
+ const code = error.code ? diagnosticText(error.code) : undefined;
100
+ const message = diagnosticText(error.message);
101
+ this.failAll(`app-server failed to start${code ? ` (${code})` : ""}: ${message}; ` +
102
+ `command=${JSON.stringify(launch.command)}, cwd=${JSON.stringify(launch.cwd)}`, { ...launch, code, error: message });
103
+ });
85
104
  // Handlers are registered by contract before start(); release the buffer.
86
105
  this.handlersReady = true;
87
106
  for (const line of this.preHandlerLines.splice(0))
@@ -203,12 +222,12 @@ export class CodexAppServerClient {
203
222
  return;
204
223
  child.stdin.write(`${JSON.stringify(payload)}\n`);
205
224
  }
206
- failAll(reason) {
225
+ failAll(reason, details = {}) {
207
226
  if (this.closed)
208
227
  return;
209
228
  this.closed = true;
210
229
  this.closeReason = reason;
211
- this.log("appserver.closed", { reason });
230
+ this.log("appserver.closed", { ...details, reason });
212
231
  for (const [, entry] of this.pending) {
213
232
  if (entry.timer)
214
233
  clearTimeout(entry.timer);
@@ -113,6 +113,7 @@ export declare class CodexRuntime {
113
113
  interrupt(): Promise<void>;
114
114
  close(): Promise<void>;
115
115
  private onNotification;
116
+ private emitEvent;
116
117
  /**
117
118
  * Codex's native-tool approvals. CodeShell Host Tools never arrive here —
118
119
  * they come in over the MCP bridge and are authorized by `ToolExecutor`, which
@@ -105,6 +105,14 @@ export class CodexRuntime {
105
105
  threadIdPrefix: threadId.slice(0, 8),
106
106
  resumed: this.resumed,
107
107
  });
108
+ // Announce the host identity after a successful start/resume. Codex may
109
+ // deliver thread/started before this RPC settles (and before the translator
110
+ // exists), so that notification cannot reliably establish the UI binding.
111
+ this.emitEvent({
112
+ type: "session_started",
113
+ sessionId: this.options.businessSessionId,
114
+ promptTokens: 0,
115
+ });
108
116
  }
109
117
  /**
110
118
  * Run one turn. Resolves when the turn is terminal.
@@ -184,20 +192,23 @@ export class CodexRuntime {
184
192
  onNotification(method, params) {
185
193
  const events = this.translator?.translate({ method, params }) ?? [];
186
194
  for (const event of events) {
187
- try {
188
- this.hooks.onEvent?.(event);
189
- }
190
- catch (error) {
191
- this.log("runtime.event_handler_failed", {
192
- error: error instanceof Error ? error.name : "unknown",
193
- });
194
- }
195
+ this.emitEvent(event);
195
196
  if (event.type === "turn_complete") {
196
197
  this.activeTurn?.resolve();
197
198
  this.activeTurn = undefined;
198
199
  }
199
200
  }
200
201
  }
202
+ emitEvent(event) {
203
+ try {
204
+ this.hooks.onEvent?.(event);
205
+ }
206
+ catch (error) {
207
+ this.log("runtime.event_handler_failed", {
208
+ error: error instanceof Error ? error.name : "unknown",
209
+ });
210
+ }
211
+ }
201
212
  /**
202
213
  * Codex's native-tool approvals. CodeShell Host Tools never arrive here —
203
214
  * they come in over the MCP bridge and are authorized by `ToolExecutor`, which
@@ -78,8 +78,10 @@ export async function startExternalRuntimeSession(options) {
78
78
  ...(options.codexClient ? { client: options.codexClient } : {}),
79
79
  log,
80
80
  }, options.hooks ?? {});
81
- await codex.start();
81
+ // Own the process before its handshake can fail, so the catch path also
82
+ // closes an app-server that launched but could not initialize a thread.
82
83
  runtime = codex;
84
+ await codex.start();
83
85
  }
84
86
  else {
85
87
  runtime = new ClaudeCodeRuntime({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cjhyy/code-shell-capability-coding",
3
- "version": "0.9.7",
3
+ "version": "0.9.9",
4
4
  "description": "Coding capability pack for the generic code-shell agent core.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -39,7 +39,7 @@
39
39
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
40
40
  },
41
41
  "dependencies": {
42
- "@cjhyy/code-shell-core": "0.9.7"
42
+ "@cjhyy/code-shell-core": "0.9.9"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=20.10"