shennian 0.2.66 → 0.2.68

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.
@@ -298,6 +298,7 @@ function bindAdapterEvents(runtime, sessionId, agentType, adapter) {
298
298
  adapter.on('error', (error) => {
299
299
  console.error(`[chat.send] adapter error sessionId=${sessionId} agentType=${agentType}: ${error.message}`);
300
300
  runtime.sessions.delete(sessionId);
301
+ runtime.chatQueue?.noteTerminal(sessionId);
301
302
  runtime.client.sendEvent({
302
303
  type: 'event',
303
304
  event: 'agent',
@@ -359,6 +360,7 @@ function emitSyntheticAbort(runtime, sessionId) {
359
360
  seq,
360
361
  id: `agent-evt-${runId}-${seq}`,
361
362
  });
363
+ runtime.chatQueue?.noteTerminal(sessionId);
362
364
  }
363
365
  export async function handleChatSend(runtime, req) {
364
366
  if (runtime.processedReqIds.has(req.id)) {
@@ -474,8 +476,13 @@ export async function handleChatSend(runtime, req) {
474
476
  agentSessionId: session.agentSessionId ?? incomingAgentSid ?? null,
475
477
  modelId,
476
478
  });
477
- session.currentRunId = null;
478
- session.nextEventSeq = 0;
479
+ // Some adapters (notably Codex app-server) emit `start` synchronously
480
+ // during `send()` before chat.send has acknowledged dispatch. Do not wipe
481
+ // that live run state here, or later chat.enqueue calls will think the
482
+ // session is idle and bypass the daemon queue.
483
+ if (!session.currentRunId) {
484
+ session.nextEventSeq = 0;
485
+ }
479
486
  runtime.nativeFusion?.registerManagedSend({
480
487
  sessionId,
481
488
  agentType: requestedAgentType,
@@ -14,6 +14,7 @@ export declare class ChatQueueManager {
14
14
  handleEdit(req: ReqFrame): Promise<void>;
15
15
  handleDelete(req: ReqFrame): Promise<void>;
16
16
  noteTerminal(sessionId: string): void;
17
+ private drainIdleQueues;
17
18
  private drainNext;
18
19
  private mergeExternalMessages;
19
20
  private dispatchQueuedMessage;
@@ -73,6 +73,8 @@ export class ChatQueueManager {
73
73
  draining = new Set();
74
74
  constructor(opts) {
75
75
  this.opts = opts;
76
+ const timer = setTimeout(() => this.drainIdleQueues(), 0);
77
+ timer.unref?.();
76
78
  }
77
79
  getSnapshot(sessionId) {
78
80
  return {
@@ -136,6 +138,9 @@ export class ChatQueueManager {
136
138
  ...(materialized.localized ? { localizedAttachments: true } : {}),
137
139
  },
138
140
  });
141
+ if (!isBusy) {
142
+ void this.drainNext(params.sessionId);
143
+ }
139
144
  }
140
145
  async handleGet(req) {
141
146
  const runtime = this.opts.getRuntime();
@@ -150,6 +155,9 @@ export class ChatQueueManager {
150
155
  ok: true,
151
156
  payload: { queue: this.getSnapshot(params.sessionId) },
152
157
  });
158
+ if (!this.getSnapshot(params.sessionId).busy) {
159
+ void this.drainNext(params.sessionId);
160
+ }
153
161
  }
154
162
  async handleEdit(req) {
155
163
  const runtime = this.opts.getRuntime();
@@ -221,6 +229,14 @@ export class ChatQueueManager {
221
229
  noteTerminal(sessionId) {
222
230
  void this.drainNext(sessionId);
223
231
  }
232
+ drainIdleQueues() {
233
+ const queue = readQueue();
234
+ for (const sessionId of Object.keys(queue.sessions)) {
235
+ if (!this.getSnapshot(sessionId).busy) {
236
+ void this.drainNext(sessionId);
237
+ }
238
+ }
239
+ }
224
240
  async drainNext(sessionId) {
225
241
  if (this.draining.has(sessionId))
226
242
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.2.66",
3
+ "version": "0.2.68",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {