shennian 0.2.50 → 0.2.51

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.
@@ -13,6 +13,8 @@ export declare class CustomAgentAdapter extends AgentAdapter {
13
13
  private seq;
14
14
  private stdioProcess;
15
15
  private spawnProcess;
16
+ private dispatchGuard;
17
+ private dispatchObservedEvent;
16
18
  constructor(name: string, entry: CustomAgentEntry);
17
19
  start(sessionId: string, workDir: string, agentSessionId?: string | null): Promise<void>;
18
20
  send(text: string, modelId?: string): Promise<void>;
@@ -25,6 +27,9 @@ export declare class CustomAgentAdapter extends AgentAdapter {
25
27
  private stopStdioProcess;
26
28
  private attachOutputHandlers;
27
29
  private handleProtocolEvent;
30
+ private startDispatchGuard;
31
+ private acceptDispatch;
32
+ private rejectDispatch;
28
33
  private emitEvent;
29
34
  }
30
35
  export declare function registerCustomAgent(name: string, entry: CustomAgentEntry): void;
@@ -4,6 +4,7 @@ import { randomUUID } from 'node:crypto';
4
4
  import { AgentAdapter, registerAgent } from './adapter.js';
5
5
  import { spawnCommandString } from './command-spec.js';
6
6
  import { buildAgentProcessEnv } from '../agent-env.js';
7
+ const DISPATCH_GUARD_MS = 3000;
7
8
  export class CustomAgentAdapter extends AgentAdapter {
8
9
  type;
9
10
  command;
@@ -18,6 +19,8 @@ export class CustomAgentAdapter extends AgentAdapter {
18
19
  stdioProcess = null;
19
20
  // spawn mode: current single-turn process
20
21
  spawnProcess = null;
22
+ dispatchGuard = null;
23
+ dispatchObservedEvent = false;
21
24
  constructor(name, entry) {
22
25
  super();
23
26
  this.type = `custom:${name}`;
@@ -66,6 +69,8 @@ export class CustomAgentAdapter extends AgentAdapter {
66
69
  // ─── Spawn mode ───────────────────────────────────────────────────────────
67
70
  async runSpawn(text, modelId) {
68
71
  await this.killSpawnProcess();
72
+ const guard = this.startDispatchGuard();
73
+ this.dispatchObservedEvent = false;
69
74
  const args = ['/run', '--workdir', this.workDir ?? process.cwd()];
70
75
  if (this.sessionId)
71
76
  args.push('--session', this.sessionId);
@@ -83,8 +88,10 @@ export class CustomAgentAdapter extends AgentAdapter {
83
88
  if (this.spawnProcess === proc)
84
89
  this.spawnProcess = null;
85
90
  });
91
+ proc.stdin?.once('error', (error) => this.rejectDispatch(error));
86
92
  proc.stdin?.write(text);
87
93
  proc.stdin?.end();
94
+ await guard.promise;
88
95
  }
89
96
  async killSpawnProcess() {
90
97
  const proc = this.spawnProcess;
@@ -116,6 +123,8 @@ export class CustomAgentAdapter extends AgentAdapter {
116
123
  if (!this.stdioProcess) {
117
124
  await this.startStdioProcess();
118
125
  }
126
+ const guard = this.startDispatchGuard();
127
+ this.dispatchObservedEvent = false;
119
128
  const msg = JSON.stringify({
120
129
  method: 'send',
121
130
  id: randomUUID(),
@@ -125,7 +134,9 @@ export class CustomAgentAdapter extends AgentAdapter {
125
134
  modelId,
126
135
  },
127
136
  });
137
+ this.stdioProcess?.stdin?.once('error', (error) => this.rejectDispatch(error));
128
138
  this.stdioProcess?.stdin?.write(msg + '\n');
139
+ await guard.promise;
129
140
  }
130
141
  async stopStdioProcess() {
131
142
  const proc = this.stdioProcess;
@@ -165,11 +176,19 @@ export class CustomAgentAdapter extends AgentAdapter {
165
176
  onClose();
166
177
  if (code !== 0 && code !== null) {
167
178
  const msg = stderrBuf.trim() || `Agent exited with code ${code}`;
179
+ this.rejectDispatch(new Error(msg));
168
180
  this.emitEvent({ state: 'error', message: msg });
169
181
  }
182
+ else if (!this.dispatchObservedEvent) {
183
+ this.rejectDispatch(new Error('Custom agent exited without protocol events'));
184
+ }
185
+ else {
186
+ this.acceptDispatch();
187
+ }
170
188
  });
171
189
  proc.on('error', (err) => {
172
190
  onClose();
191
+ this.rejectDispatch(err);
173
192
  this.emit('error', err);
174
193
  });
175
194
  }
@@ -177,29 +196,70 @@ export class CustomAgentAdapter extends AgentAdapter {
177
196
  const { state } = event;
178
197
  if (!state)
179
198
  return;
199
+ this.dispatchObservedEvent = true;
180
200
  switch (state) {
181
201
  case 'delta':
202
+ this.acceptDispatch();
182
203
  this.emitEvent({ state: 'delta', text: event.text ?? '', thinking: event.thinking });
183
204
  break;
184
205
  case 'final':
206
+ this.acceptDispatch();
185
207
  if (event.agentSessionId)
186
208
  this.agentSessionId = event.agentSessionId;
187
209
  this.emitEvent({ state: 'final', usage: event.usage, agentSessionId: this.agentSessionId ?? undefined });
188
210
  break;
189
211
  case 'error':
212
+ this.rejectDispatch(new Error(event.message ?? 'unknown error'));
190
213
  this.emitEvent({ state: 'error', message: event.message ?? 'unknown error' });
191
214
  break;
192
215
  case 'tool-call':
216
+ this.acceptDispatch();
193
217
  this.emitEvent({ state: 'tool-call', name: event.name ?? '', args: event.args });
194
218
  break;
195
219
  case 'tool-result':
220
+ this.acceptDispatch();
196
221
  this.emitEvent({ state: 'tool-result', name: event.name ?? '', result: event.result ?? '' });
197
222
  break;
198
223
  case 'notify':
224
+ this.acceptDispatch();
199
225
  this.emitEvent({ state: 'notify', text: event.text ?? '', title: event.title, source: event.source });
200
226
  break;
201
227
  }
202
228
  }
229
+ startDispatchGuard() {
230
+ this.acceptDispatch();
231
+ let guard;
232
+ const promise = new Promise((resolve, reject) => {
233
+ guard = {
234
+ settled: false,
235
+ timer: setTimeout(() => this.acceptDispatch(guard), DISPATCH_GUARD_MS),
236
+ resolve,
237
+ reject,
238
+ promise: Promise.resolve(),
239
+ };
240
+ });
241
+ guard.promise = promise;
242
+ this.dispatchGuard = guard;
243
+ return guard;
244
+ }
245
+ acceptDispatch(guard = this.dispatchGuard) {
246
+ if (!guard || guard.settled)
247
+ return;
248
+ guard.settled = true;
249
+ clearTimeout(guard.timer);
250
+ if (this.dispatchGuard === guard)
251
+ this.dispatchGuard = null;
252
+ guard.resolve();
253
+ }
254
+ rejectDispatch(error, guard = this.dispatchGuard) {
255
+ if (!guard || guard.settled)
256
+ return;
257
+ guard.settled = true;
258
+ clearTimeout(guard.timer);
259
+ if (this.dispatchGuard === guard)
260
+ this.dispatchGuard = null;
261
+ guard.reject(error);
262
+ }
203
263
  emitEvent(partial) {
204
264
  const event = { ...partial, runId: this.runId, seq: this.seq++ };
205
265
  this.emit('agentEvent', event);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.2.50",
3
+ "version": "0.2.51",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {