@cjhyy/code-shell-capability-coding 0.9.29 → 0.9.31

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.
@@ -29,12 +29,11 @@ export declare class ClaudeCodeRuntime {
29
29
  private readonly hooks;
30
30
  readonly kind: "claude-code";
31
31
  private readonly log;
32
- private translator;
33
32
  private child?;
34
33
  private claudeSessionId?;
35
34
  private closed;
36
35
  private firstTurn;
37
- private terminalSeen;
36
+ private turnCount;
38
37
  constructor(options: ClaudeRuntimeOptions, hooks?: ClaudeRuntimeHooks);
39
38
  /** Claude session id, once the first turn has reported it. Resume key only. */
40
39
  get runtimeSessionId(): string | undefined;
@@ -46,7 +45,7 @@ export declare class ClaudeCodeRuntime {
46
45
  * positional prompt after it is swallowed as another config value (measured),
47
46
  * and a prompt on the command line would also be visible in `ps`.
48
47
  */
49
- send(input: ExternalRuntimeTurnInput): Promise<ClaudeTurnHandle>;
48
+ send(input: ExternalRuntimeTurnInput, onEvent?: (event: StreamEvent) => void): Promise<ClaudeTurnHandle>;
50
49
  private onLine;
51
50
  private emit;
52
51
  /**
@@ -25,20 +25,15 @@ export class ClaudeCodeRuntime {
25
25
  hooks;
26
26
  kind = "claude-code";
27
27
  log;
28
- translator;
29
28
  child;
30
29
  claudeSessionId;
31
30
  closed = false;
32
31
  firstTurn = true;
33
- terminalSeen = false;
32
+ turnCount = 0;
34
33
  constructor(options, hooks = {}) {
35
34
  this.options = options;
36
35
  this.hooks = hooks;
37
36
  this.log = options.log ?? (() => { });
38
- this.translator = new ClaudeEventTranslator({
39
- sessionId: options.businessSessionId,
40
- codeshellServerName: options.serverName ?? CLAUDE_MCP_SERVER_NAME,
41
- });
42
37
  this.claudeSessionId = options.resumeRuntimeSessionId;
43
38
  }
44
39
  /** Claude session id, once the first turn has reported it. Resume key only. */
@@ -53,13 +48,18 @@ export class ClaudeCodeRuntime {
53
48
  * positional prompt after it is swallowed as another config value (measured),
54
49
  * and a prompt on the command line would also be visible in `ps`.
55
50
  */
56
- async send(input) {
51
+ async send(input, onEvent) {
57
52
  if (this.closed)
58
53
  throw new Error("ClaudeCodeRuntime is closed");
59
54
  if (this.child)
60
55
  throw new Error("a turn is already running");
61
- this.terminalSeen = false;
62
- this.translator.beginTurn();
56
+ let terminalSeen = false;
57
+ const turnNumber = ++this.turnCount;
58
+ const translator = new ClaudeEventTranslator({
59
+ sessionId: this.options.businessSessionId,
60
+ codeshellServerName: this.options.serverName ?? CLAUDE_MCP_SERVER_NAME,
61
+ });
62
+ translator.beginTurn();
63
63
  const wiring = claudeBridgeArgs({
64
64
  bridge: this.options.bridge,
65
65
  exposedToolNames: this.options.exposedToolNames,
@@ -93,7 +93,13 @@ export class ClaudeCodeRuntime {
93
93
  this.child = child;
94
94
  child.stdout.setEncoding("utf8");
95
95
  const lines = createInterface({ input: child.stdout, crlfDelay: Infinity });
96
- lines.on("line", (line) => this.onLine(line));
96
+ lines.on("line", (line) => {
97
+ this.onLine(line, translator, (event) => {
98
+ if (event.type === "turn_complete")
99
+ terminalSeen = true;
100
+ this.emit(event.type === "stream_request_start" ? { ...event, turnNumber } : event, onEvent);
101
+ });
102
+ });
97
103
  child.stderr.setEncoding("utf8");
98
104
  child.stderr.on("data", (chunk) => {
99
105
  const trimmed = chunk.trim();
@@ -116,11 +122,11 @@ export class ClaudeCodeRuntime {
116
122
  // Clean up the config file that carried the bearer token.
117
123
  wiring.cleanup();
118
124
  this.child = undefined;
119
- if (!this.terminalSeen) {
125
+ if (!terminalSeen) {
120
126
  const detail = error?.message ??
121
127
  `Claude Code exited without a terminal result (code ${code ?? "unknown"})`;
122
- this.emit({ type: "error", error: detail });
123
- this.emit({ type: "turn_complete", reason: "model_error" });
128
+ this.emit({ type: "error", error: detail }, onEvent);
129
+ this.emit({ type: "turn_complete", reason: "model_error" }, onEvent);
124
130
  }
125
131
  resolve();
126
132
  };
@@ -132,7 +138,7 @@ export class ClaudeCodeRuntime {
132
138
  });
133
139
  return { done };
134
140
  }
135
- onLine(line) {
141
+ onLine(line, translator, emit) {
136
142
  if (!line.trim())
137
143
  return;
138
144
  let parsed;
@@ -144,22 +150,19 @@ export class ClaudeCodeRuntime {
144
150
  this.log("claude.unparsable_line", { bytes: line.length });
145
151
  return;
146
152
  }
147
- for (const event of this.translator.translate(parsed)) {
148
- if (event.type === "turn_complete")
149
- this.terminalSeen = true;
150
- this.emit(event);
151
- }
152
- if (!this.claudeSessionId && this.translator.runtimeSessionId) {
153
- this.claudeSessionId = this.translator.runtimeSessionId;
153
+ for (const event of translator.translate(parsed))
154
+ emit(event);
155
+ if (!this.claudeSessionId && translator.runtimeSessionId) {
156
+ this.claudeSessionId = translator.runtimeSessionId;
154
157
  this.log("claude.session_started", {
155
158
  businessSessionId: this.options.businessSessionId,
156
159
  runtimeSessionIdPrefix: this.claudeSessionId.slice(0, 8),
157
160
  });
158
161
  }
159
162
  }
160
- emit(event) {
163
+ emit(event, onEvent) {
161
164
  try {
162
- this.hooks.onEvent?.(event);
165
+ (onEvent ?? this.hooks.onEvent)?.(event);
163
166
  }
164
167
  catch (error) {
165
168
  this.log("claude.event_handler_failed", {
@@ -186,9 +189,6 @@ export class ClaudeCodeRuntime {
186
189
  /** Start a fresh conversation on the next `send()`, discarding resume state. */
187
190
  resetConversation() {
188
191
  this.claudeSessionId = undefined;
189
- this.translator = new ClaudeEventTranslator({
190
- sessionId: this.options.businessSessionId,
191
- codeshellServerName: this.options.serverName ?? CLAUDE_MCP_SERVER_NAME,
192
- });
192
+ this.turnCount = 0;
193
193
  }
194
194
  }
@@ -101,7 +101,7 @@ export declare class CodexRuntime {
101
101
  * tombstones are what keep a late `turn/started` from reactivating a session
102
102
  * that already reported terminal.
103
103
  */
104
- send(input: ExternalRuntimeTurnInput): Promise<CodexTurnHandle>;
104
+ send(input: ExternalRuntimeTurnInput, onEvent?: (event: StreamEvent) => void): Promise<CodexTurnHandle>;
105
105
  /**
106
106
  * Interrupt the active turn.
107
107
  *
@@ -122,12 +122,12 @@ export class CodexRuntime {
122
122
  * tombstones are what keep a late `turn/started` from reactivating a session
123
123
  * that already reported terminal.
124
124
  */
125
- async send(input) {
125
+ async send(input, onEvent) {
126
126
  if (!this.threadId)
127
127
  throw new Error("CodexRuntime.send() before start()");
128
128
  let resolveDone;
129
129
  const done = new Promise((resolve) => (resolveDone = resolve));
130
- this.activeTurn = { resolve: resolveDone };
130
+ this.activeTurn = { resolve: resolveDone, onEvent };
131
131
  let text = textWithAttachmentReferences(input);
132
132
  if (this.firstTurn && !this.resumed && this.options.initialContext) {
133
133
  text = `${this.options.initialContext}\n\n<current_user_request>\n${text}\n</current_user_request>`;
@@ -201,7 +201,7 @@ export class CodexRuntime {
201
201
  }
202
202
  emitEvent(event) {
203
203
  try {
204
- this.hooks.onEvent?.(event);
204
+ (this.activeTurn?.onEvent ?? this.hooks.onEvent)?.(event);
205
205
  }
206
206
  catch (error) {
207
207
  this.log("runtime.event_handler_failed", {
@@ -19,7 +19,7 @@
19
19
  * Teardown reverses it, and unregisters before disposing the host so a late
20
20
  * request finds nothing rather than a disposed host (§13.4).
21
21
  */
22
- import type { PermissionRule, ToolDefinition } from "@cjhyy/code-shell-core/extension";
22
+ import type { PermissionRule, StreamEvent, ToolDefinition } from "@cjhyy/code-shell-core/extension";
23
23
  import { createSessionToolHost, type ExternalToolExposurePolicy, type ToolVisibilityInputs } from "@cjhyy/code-shell-core/extension";
24
24
  import { type CodexRuntimeHooks, type CodexRuntimeOptions } from "./codex/runtime.js";
25
25
  import { type ClaudeRuntimeHooks } from "./claude-code/runtime.js";
@@ -83,7 +83,7 @@ export interface ExternalRuntimeSession {
83
83
  readonly runtimeSessionId: string | undefined;
84
84
  /** Tools actually exposed, after the allowlist and visibility guards. */
85
85
  listTools(): readonly ToolDefinition[];
86
- send(input: ExternalRuntimeTurnInput): Promise<{
86
+ send(input: ExternalRuntimeTurnInput, onEvent?: (event: StreamEvent) => void): Promise<{
87
87
  done: Promise<void>;
88
88
  }>;
89
89
  interrupt(): Promise<void>;
@@ -28,6 +28,7 @@ export async function startExternalRuntimeSession(options) {
28
28
  let host;
29
29
  let runtime;
30
30
  let closed = false;
31
+ let turnSink;
31
32
  const close = async () => {
32
33
  if (closed)
33
34
  return;
@@ -53,6 +54,9 @@ export async function startExternalRuntimeSession(options) {
53
54
  visibility: options.visibility,
54
55
  ...(options.approvalBackend ? { approvalBackend: options.approvalBackend } : {}),
55
56
  ...(options.contextOverrides ? { contextOverrides: options.contextOverrides } : {}),
57
+ // Outside a concrete provider call there is no run output owner. Never
58
+ // capture the lifetime callback and let it borrow a later active turn.
59
+ getStreamCallback: () => turnSink,
56
60
  ...(options.settingsScope ? { settingsScope: options.settingsScope } : {}),
57
61
  });
58
62
  // 3. Register before the runtime starts, so a tool call on the very first
@@ -116,7 +120,26 @@ export async function startExternalRuntimeSession(options) {
116
120
  return activeRuntime.runtimeSessionId;
117
121
  },
118
122
  listTools: () => activeHost.listTools(),
119
- send: (input) => activeRuntime.send(input),
123
+ send: async (input, onEvent) => {
124
+ if (turnSink)
125
+ throw new Error("An external runtime turn is already running");
126
+ const sink = onEvent ?? options.hooks?.onEvent;
127
+ turnSink = sink;
128
+ try {
129
+ const turn = await activeRuntime.send(input, sink);
130
+ return {
131
+ done: turn.done.finally(() => {
132
+ if (turnSink === sink)
133
+ turnSink = undefined;
134
+ }),
135
+ };
136
+ }
137
+ catch (error) {
138
+ if (turnSink === sink)
139
+ turnSink = undefined;
140
+ throw error;
141
+ }
142
+ },
120
143
  interrupt: () => activeRuntime.interrupt(),
121
144
  close,
122
145
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cjhyy/code-shell-capability-coding",
3
- "version": "0.9.29",
3
+ "version": "0.9.31",
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.29"
42
+ "@cjhyy/code-shell-core": "0.9.31"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=20.10"