@mirasoth/soothe-client 0.5.0 → 0.5.2

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.
package/dist/index.cjs CHANGED
@@ -357,14 +357,14 @@ var init_protocol = __esm({
357
357
  import_node_crypto = require("crypto");
358
358
  PROTO_VERSION = "1";
359
359
  DEFAULT_CLIENT_CAPABILITIES = ["streaming", "batch", "heartbeat", "receipts"];
360
- CLIENT_VERSION = "0.5.0";
360
+ CLIENT_VERSION = "0.5.2";
361
361
  }
362
362
  });
363
363
 
364
364
  // src/intent_hints.ts
365
365
  function validateLoopInputIntentHint(hint) {
366
366
  const key = hint.trim().toLowerCase();
367
- if (key === "direct_llm" || key === "quiz") {
367
+ if (key in REMOVED_INTENT_HINT_MESSAGES) {
368
368
  return REMOVED_INTENT_HINT_MESSAGES[key];
369
369
  }
370
370
  return null;
@@ -377,16 +377,17 @@ var init_intent_hints = __esm({
377
377
  INTENT_HINT_IMAGE_TO_TEXT = "image_to_text";
378
378
  INTENT_HINT_OCR = "ocr";
379
379
  INTENT_HINT_EMBED = "embed";
380
- REMOVED_INTENT_HINTS = ["direct_llm", "quiz"];
380
+ REMOVED_INTENT_HINTS = ["direct_llm", "quiz", "direct_model"];
381
381
  REMOVED_INTENT_HINT_MESSAGES = {
382
382
  direct_llm: "intent_hint direct_llm is removed; use text_completion (text-only) or image_to_text (with attachments)",
383
- quiz: "intent_hint quiz is removed; omit intent_hint and let intake classify the turn"
383
+ quiz: "intent_hint quiz is removed; omit intent_hint and let intake classify the turn",
384
+ direct_model: "intent_hint direct_model is removed; use text_completion, image_to_text, ocr, or embed"
384
385
  };
385
386
  LOOP_ASSISTANT_OUTPUT_PHASES = [
386
387
  "goal_completion",
387
388
  "quiz",
388
389
  "autonomous_goal",
389
- "direct_model",
390
+ "chitchat",
390
391
  "text_completion",
391
392
  "image_to_text",
392
393
  "ocr",
@@ -396,7 +397,7 @@ var init_intent_hints = __esm({
396
397
  DEFAULT_DELIVERABLE_PHASES = /* @__PURE__ */ new Set([
397
398
  "quiz",
398
399
  "goal_completion",
399
- "direct_model",
400
+ "chitchat",
400
401
  "text_completion",
401
402
  "image_to_text",
402
403
  "ocr",
@@ -2068,9 +2069,14 @@ __export(index_exports, {
2068
2069
  StaleLoopError: () => StaleLoopError,
2069
2070
  StreamCloseFail: () => StreamCloseFail,
2070
2071
  StreamCloseSoftComplete: () => StreamCloseSoftComplete,
2072
+ TURN_END_IDLE: () => TURN_END_IDLE,
2073
+ TURN_END_STOPPED: () => TURN_END_STOPPED,
2074
+ TURN_END_STREAM_END: () => TURN_END_STREAM_END,
2071
2075
  TimeoutError: () => TimeoutError,
2072
2076
  TimeoutPolicy: () => TimeoutPolicy,
2077
+ TurnBoundary: () => TurnBoundary,
2073
2078
  TurnEventStats: () => TurnEventStats,
2079
+ TurnLifecycleGate: () => TurnLifecycleGate,
2074
2080
  TurnRunner: () => TurnRunner,
2075
2081
  VerbosityTier: () => VerbosityTier,
2076
2082
  authenticate: () => authenticate,
@@ -2100,6 +2106,7 @@ __export(index_exports, {
2100
2106
  inputMessageForLoop: () => inputMessageForLoop,
2101
2107
  isCompletionEvent: () => isCompletionEvent,
2102
2108
  isDaemonLive: () => isDaemonLive,
2109
+ isDaemonTurnEndEvent: () => isDaemonTurnEndEvent,
2103
2110
  isSubagentProgressEvent: () => isSubagentProgressEvent,
2104
2111
  isTurnEndCustomData: () => isTurnEndCustomData,
2105
2112
  isTurnProgressChunk: () => isTurnProgressChunk,
@@ -2742,6 +2749,8 @@ var EventClassifier = class {
2742
2749
  if (!eventType) return false;
2743
2750
  switch (eventType) {
2744
2751
  case "status.idle":
2752
+ case "status.stopped":
2753
+ case "soothe.stream.end":
2745
2754
  case "idle_timeout":
2746
2755
  case "query_timeout":
2747
2756
  case "stream_closed":
@@ -2935,9 +2944,9 @@ var EventClassifier = class {
2935
2944
  return this.continueResult(content);
2936
2945
  }
2937
2946
  }
2938
- const [directContent, directOk] = this.messagesModeAssistantContent(data);
2939
- if (directOk && this.isSubstantiveAssistantReply(directContent)) {
2940
- return this.deliverableResult(directContent, "soothe.protocol.message.direct_model");
2947
+ const [unphasedContent, unphasedOk] = this.messagesModeAssistantContent(data);
2948
+ if (unphasedOk) {
2949
+ return this.continueResult(unphasedContent);
2941
2950
  }
2942
2951
  if (hasPayload && rawContent) {
2943
2952
  if (isTerminalMessageType(msgType) || msgType === "") {
@@ -2952,8 +2961,8 @@ var EventClassifier = class {
2952
2961
  }
2953
2962
  /**
2954
2963
  * Extracts plain assistant text from mode="messages" events that carry a
2955
- * terminal AIMessage without loop-tagged phase metadata (legacy direct_llm turns
2956
- * before phase tagging; prefer deliverablePhases including text_completion).
2964
+ * terminal AIMessage without loop-tagged phase metadata (prefer named
2965
+ * deliverablePhases such as text_completion).
2957
2966
  */
2958
2967
  messagesModeAssistantContent(data) {
2959
2968
  if (!Array.isArray(data) || data.length === 0) return ["", false];
@@ -3452,6 +3461,110 @@ async function compactAttachments(atts, opts) {
3452
3461
 
3453
3462
  // src/appkit/turn_runner.ts
3454
3463
  init_intent_hints();
3464
+
3465
+ // src/appkit/turn_boundary.ts
3466
+ init_stream_terminal();
3467
+ var TURN_END_STREAM_END = STREAM_END;
3468
+ var TURN_END_IDLE = "status.idle";
3469
+ var TURN_END_STOPPED = "status.stopped";
3470
+ var TurnLifecycleGate = class {
3471
+ sawRunning = false;
3472
+ sawStreamPayload = false;
3473
+ sawTurnProgress = false;
3474
+ observe(msg) {
3475
+ const frame = normalizeFrame(msg);
3476
+ if (!frame) return;
3477
+ const typ = String(frame.type ?? "");
3478
+ if (typ === "status") {
3479
+ if (String(frame.state ?? "").trim().toLowerCase() === "running") {
3480
+ this.sawRunning = true;
3481
+ }
3482
+ return;
3483
+ }
3484
+ if (typ === "event") {
3485
+ this.sawStreamPayload = true;
3486
+ const mode = String(frame.mode ?? "");
3487
+ if (isTurnProgressChunk(mode, frame.data)) {
3488
+ this.sawTurnProgress = true;
3489
+ }
3490
+ }
3491
+ }
3492
+ allowStreamEnd() {
3493
+ return this.sawRunning && this.sawTurnProgress;
3494
+ }
3495
+ allowIdleComplete() {
3496
+ return this.sawRunning && this.sawStreamPayload;
3497
+ }
3498
+ };
3499
+ var TurnBoundary = class {
3500
+ gate = new TurnLifecycleGate();
3501
+ ended = false;
3502
+ reason = "";
3503
+ feed(msg) {
3504
+ if (this.ended) return [true, this.reason];
3505
+ this.gate.observe(msg);
3506
+ const frame = normalizeFrame(msg);
3507
+ if (!frame) return [false, ""];
3508
+ const typ = String(frame.type ?? "");
3509
+ if (typ === "status") {
3510
+ const state = String(frame.state ?? "").trim().toLowerCase();
3511
+ if (state === "stopped" && this.gate.sawRunning) {
3512
+ return this.mark(TURN_END_STOPPED);
3513
+ }
3514
+ if (state === "idle" && this.gate.allowIdleComplete()) {
3515
+ return this.mark(TURN_END_IDLE);
3516
+ }
3517
+ return [false, ""];
3518
+ }
3519
+ if (typ === "event") {
3520
+ const mode = String(frame.mode ?? "");
3521
+ if (mode === "custom" && isTurnEndCustomData(frame.data) && this.gate.allowStreamEnd()) {
3522
+ return this.mark(TURN_END_STREAM_END);
3523
+ }
3524
+ }
3525
+ return [false, ""];
3526
+ }
3527
+ mark(reason) {
3528
+ this.ended = true;
3529
+ this.reason = reason;
3530
+ return [true, reason];
3531
+ }
3532
+ };
3533
+ function isDaemonTurnEndEvent(completionEvent) {
3534
+ const e = (completionEvent ?? "").trim();
3535
+ return e === TURN_END_STREAM_END || e === TURN_END_IDLE || e === TURN_END_STOPPED;
3536
+ }
3537
+ function normalizeFrame(msg) {
3538
+ if (!msg || typeof msg !== "object") return null;
3539
+ const m = msg;
3540
+ if (m.type === "next") {
3541
+ const payload = m.payload ?? {};
3542
+ const inner = payload.data;
3543
+ if (inner && typeof inner === "object" && inner.type === "status") {
3544
+ return inner;
3545
+ }
3546
+ if (inner && typeof inner === "object" && inner.mode) {
3547
+ return {
3548
+ type: "event",
3549
+ mode: inner.mode,
3550
+ data: inner.data,
3551
+ namespace: inner.namespace ?? payload.namespace
3552
+ };
3553
+ }
3554
+ if (payload.mode) {
3555
+ return {
3556
+ type: "event",
3557
+ mode: payload.mode,
3558
+ data: payload.data,
3559
+ namespace: payload.namespace
3560
+ };
3561
+ }
3562
+ return null;
3563
+ }
3564
+ return m;
3565
+ }
3566
+
3567
+ // src/appkit/turn_runner.ts
3455
3568
  var ErrQueryTimeout = class extends Error {
3456
3569
  constructor() {
3457
3570
  super("appkit: query timeout");
@@ -3591,6 +3704,7 @@ var TurnRunner = class {
3591
3704
  }
3592
3705
  let assistantContent = "";
3593
3706
  const startedAt = Date.now();
3707
+ const boundary = new TurnBoundary();
3594
3708
  const idleForTurn = idleTimeoutForTurn(this.cfg, (attachments?.length ?? 0) > 0);
3595
3709
  let idleReject = null;
3596
3710
  const armIdle = () => {
@@ -3683,6 +3797,7 @@ var TurnRunner = class {
3683
3797
  }
3684
3798
  idleRace = armIdle();
3685
3799
  const msg = res.value;
3800
+ const [ended, endReason] = boundary.feed(msg);
3686
3801
  const eventResult = this.classifier.classify(msg, assistantContent);
3687
3802
  if (eventResult.err && eventResult.terminal === 2 /* FailedComplete */) {
3688
3803
  clearIdle();
@@ -3704,7 +3819,7 @@ var TurnRunner = class {
3704
3819
  eventResult,
3705
3820
  assistantContent
3706
3821
  );
3707
- if (deliverable) {
3822
+ if (deliverable && !isDaemonTurnEndEvent(eventResult.completionEvent ?? "")) {
3708
3823
  clearIdle();
3709
3824
  await this.completeTurn(
3710
3825
  sessionID,
@@ -3715,6 +3830,24 @@ var TurnRunner = class {
3715
3830
  );
3716
3831
  return;
3717
3832
  }
3833
+ if (ended) {
3834
+ clearIdle();
3835
+ if (this.classifier.isSubstantiveAssistantReply(assistantContent)) {
3836
+ await this.completeTurn(
3837
+ sessionID,
3838
+ loopID,
3839
+ assistantContent.trim(),
3840
+ startedAt,
3841
+ endReason
3842
+ );
3843
+ return;
3844
+ }
3845
+ const err = new Error(`turn ended (${endReason}) with no assistant content`);
3846
+ await this.persistFailed(sessionID, loopID, err);
3847
+ this.broadcastError(sessionID, err);
3848
+ this.onError?.(sessionID, loopID, err);
3849
+ throw err;
3850
+ }
3718
3851
  }
3719
3852
  } finally {
3720
3853
  clearIdle();
@@ -4308,9 +4441,14 @@ var DaemonSession = class {
4308
4441
  StaleLoopError,
4309
4442
  StreamCloseFail,
4310
4443
  StreamCloseSoftComplete,
4444
+ TURN_END_IDLE,
4445
+ TURN_END_STOPPED,
4446
+ TURN_END_STREAM_END,
4311
4447
  TimeoutError,
4312
4448
  TimeoutPolicy,
4449
+ TurnBoundary,
4313
4450
  TurnEventStats,
4451
+ TurnLifecycleGate,
4314
4452
  TurnRunner,
4315
4453
  VerbosityTier,
4316
4454
  authenticate,
@@ -4340,6 +4478,7 @@ var DaemonSession = class {
4340
4478
  inputMessageForLoop,
4341
4479
  isCompletionEvent,
4342
4480
  isDaemonLive,
4481
+ isDaemonTurnEndEvent,
4343
4482
  isSubagentProgressEvent,
4344
4483
  isTurnEndCustomData,
4345
4484
  isTurnProgressChunk,