kernelpm 0.1.11 → 0.1.13

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.
@@ -116,6 +116,16 @@ class ServeOpencodeDriver {
116
116
  status = 'starting';
117
117
  /** Timestamp (ms) of the last `working` emit; null when not working. */
118
118
  workingSince = null;
119
+ /**
120
+ * True when no assistant turn is in flight. Arms the next `session.status=
121
+ * busy` handler: opencode's SSE stream occasionally emits a stray `busy`
122
+ * event AFTER the assistant message has already completed (and we've emit
123
+ * `awaiting_input`). Honoring it would flip the status back to `working`
124
+ * right after a finished turn, leaving the app stuck on "escribiendo…"
125
+ * until the watchdog's hard timeout (3 min). We only treat `busy` as
126
+ * genuinely working when the user has armed it by sending a prompt.
127
+ */
128
+ turnDone = true;
119
129
  constructor(sessionId, cwd, title, opts, cb, initial) {
120
130
  this.sessionId = sessionId;
121
131
  this.cwd = cwd;
@@ -144,10 +154,12 @@ class ServeOpencodeDriver {
144
154
  }
145
155
  async sendMessage(text) {
146
156
  await this.prompt(text);
157
+ this.turnDone = false; // arm: the next `busy` is a legit in-flight turn
147
158
  this.emitStatus('working');
148
159
  }
149
160
  async answer(option) {
150
161
  await this.prompt(option.send);
162
+ this.turnDone = false;
151
163
  this.emitStatus('working');
152
164
  }
153
165
  async isAlive() {
@@ -465,10 +477,20 @@ class ServeOpencodeDriver {
465
477
  onSessionStatus(p) {
466
478
  if (p.sessionID !== this.opencodeId || !p.status)
467
479
  return;
468
- if (p.status.type === 'busy' || p.status.type === 'retry')
480
+ if (p.status.type === 'busy' || p.status.type === 'retry') {
481
+ // Ignore `busy` events that arrive after a turn already completed.
482
+ // opencode's SSE stream occasionally emits a stale busy in the wake of a
483
+ // message.updated with time.completed, and honoring it would flip us
484
+ // back to 'working' right after we just emitted 'awaiting_input',
485
+ // leaving the app stuck on "escribiendo…" until the watchdog's hard
486
+ // timeout. The user's next prompt re-arms `turnDone`.
487
+ if (this.turnDone)
488
+ return;
469
489
  this.emitStatus('working');
470
- else if (p.status.type === 'idle')
490
+ }
491
+ else if (p.status.type === 'idle') {
471
492
  this.handleIdle();
493
+ }
472
494
  }
473
495
  onSessionIdle(p) {
474
496
  if (p.sessionID !== this.opencodeId)
@@ -584,6 +606,10 @@ class ServeOpencodeDriver {
584
606
  this.emittedTurn.add(messageId);
585
607
  this.lastAssistantMessageId = messageId;
586
608
  this.lastAssistantText = text;
609
+ // The turn is over: disarm so any subsequent stray `session.status=busy`
610
+ // from opencode's SSE stream doesn't flip us back to 'working' before the
611
+ // user's next prompt.
612
+ this.turnDone = true;
587
613
  this.cb.onEvents(events);
588
614
  const decision = (0, decisions_1.parseDecision)(text);
589
615
  if (decision)
package/dist/session.js CHANGED
@@ -27,6 +27,12 @@ class Session {
27
27
  return this.enqueue(async () => {
28
28
  if (this.meta.pendingDecision)
29
29
  await this.clearDecision();
30
+ // Persist the user's prompt as a `user_text` event so it round-trips
31
+ // through the store like the assistant's reply: attached clients see it
32
+ // live (via the push fan-out), and a session reopened later replays it
33
+ // from `sessions/<id>.jsonl`. Without this, the daemon only persisted
34
+ // assistant events and resume showed the AI half of the conversation.
35
+ await this.appendBody({ kind: 'user_text', text });
30
36
  await this.driver.sendMessage(text);
31
37
  });
32
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kernelpm",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "kernelpm daemon — keeps opencode sessions alive on a server (via `opencode serve`) and exposes them over a local control socket.",
5
5
  "license": "MIT",
6
6
  "bin": {