@mirasoth/soothe-client 0.2.1 → 0.4.1

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
@@ -51,7 +51,7 @@ var init_errors = __esm({
51
51
  }
52
52
  };
53
53
  DaemonError = class extends Error {
54
- /** Numeric error code from the RFC-450 §7.3 registry. */
54
+ /** Numeric error code from the daemon error registry. */
55
55
  code;
56
56
  /** The daemon's error message text. */
57
57
  daemonMessage;
@@ -106,6 +106,37 @@ var init_errors = __esm({
106
106
  }
107
107
  });
108
108
 
109
+ // src/verbosity.ts
110
+ function shouldShow(tier, verbosity) {
111
+ if (tier === 99 /* Internal */) {
112
+ return false;
113
+ }
114
+ const level = verbosityLevelValues[verbosity] ?? 1;
115
+ return tier <= level;
116
+ }
117
+ function isValidVerbosityLevel(s) {
118
+ return s in verbosityLevelValues;
119
+ }
120
+ var VerbosityTier, verbosityLevelValues;
121
+ var init_verbosity = __esm({
122
+ "src/verbosity.ts"() {
123
+ "use strict";
124
+ VerbosityTier = /* @__PURE__ */ ((VerbosityTier2) => {
125
+ VerbosityTier2[VerbosityTier2["Quiet"] = 0] = "Quiet";
126
+ VerbosityTier2[VerbosityTier2["Normal"] = 1] = "Normal";
127
+ VerbosityTier2[VerbosityTier2["Detailed"] = 2] = "Detailed";
128
+ VerbosityTier2[VerbosityTier2["Debug"] = 3] = "Debug";
129
+ VerbosityTier2[VerbosityTier2["Internal"] = 99] = "Internal";
130
+ return VerbosityTier2;
131
+ })(VerbosityTier || {});
132
+ verbosityLevelValues = {
133
+ quiet: 0,
134
+ normal: 1,
135
+ debug: 3
136
+ };
137
+ }
138
+ });
139
+
109
140
  // src/config.ts
110
141
  function defaultConfig() {
111
142
  return {
@@ -326,7 +357,7 @@ var init_protocol = __esm({
326
357
  import_node_crypto = require("crypto");
327
358
  PROTO_VERSION = "1";
328
359
  DEFAULT_CLIENT_CAPABILITIES = ["streaming", "batch", "heartbeat", "receipts"];
329
- CLIENT_VERSION = "0.1.0";
360
+ CLIENT_VERSION = "0.4.1";
330
361
  }
331
362
  });
332
363
 
@@ -374,6 +405,129 @@ var init_intent_hints = __esm({
374
405
  }
375
406
  });
376
407
 
408
+ // src/events.ts
409
+ function parseNamespace(ns) {
410
+ const parts = splitNamespace(ns);
411
+ if (parts.length < 4 || parts[0] !== "soothe") {
412
+ return null;
413
+ }
414
+ if (parts[1] === "internal") {
415
+ return null;
416
+ }
417
+ return { domain: parts[1], component: parts[2], action: parts[3] };
418
+ }
419
+ function splitNamespace(ns) {
420
+ const parts = [];
421
+ let start = 0;
422
+ for (let i = 0; i < ns.length; i++) {
423
+ if (ns[i] === ".") {
424
+ parts.push(ns.slice(start, i));
425
+ start = i + 1;
426
+ }
427
+ }
428
+ parts.push(ns.slice(start));
429
+ return parts;
430
+ }
431
+ function classifyEventVerbosity(eventTypeOrNamespace) {
432
+ const parsed = parseNamespace(eventTypeOrNamespace);
433
+ if (!parsed) {
434
+ return classifyByEventTypeString(eventTypeOrNamespace);
435
+ }
436
+ return classifyByDomainAndComponent(parsed.domain, parsed.component, eventTypeOrNamespace);
437
+ }
438
+ function classifyByDomainAndComponent(domain, _component, full) {
439
+ switch (domain) {
440
+ case "cognition":
441
+ return 1 /* Normal */;
442
+ case "protocol":
443
+ return 2 /* Detailed */;
444
+ case "tool":
445
+ return 99 /* Internal */;
446
+ case "subagent":
447
+ return classifySubagentEvent(full);
448
+ case "autopilot":
449
+ return 1 /* Normal */;
450
+ case "output":
451
+ case "error":
452
+ return 0 /* Quiet */;
453
+ default:
454
+ return 1 /* Normal */;
455
+ }
456
+ }
457
+ function classifySubagentEvent(full) {
458
+ const parsed = parseNamespace(full);
459
+ if (!parsed) return 1 /* Normal */;
460
+ switch (parsed.action) {
461
+ case "started":
462
+ case "completed":
463
+ return 1 /* Normal */;
464
+ default:
465
+ return 2 /* Detailed */;
466
+ }
467
+ }
468
+ function classifyByEventTypeString(eventType) {
469
+ if (eventType === EventFinalReport || eventType === EventGeneralFailed) {
470
+ return 0 /* Quiet */;
471
+ }
472
+ if (eventType === EventToolStarted) {
473
+ return 99 /* Internal */;
474
+ }
475
+ return 1 /* Normal */;
476
+ }
477
+ function isCompletionEvent(eventType) {
478
+ return eventType.endsWith(".completed") || eventType.endsWith(".failed") || eventType === EventGeneralFailed;
479
+ }
480
+ function isSubagentProgressEvent(eventType) {
481
+ const parsed = parseNamespace(eventType);
482
+ if (!parsed || parsed.domain !== "subagent") {
483
+ return false;
484
+ }
485
+ return parsed.action === "started" || parsed.action === "completed";
486
+ }
487
+ var EventPlanCreated, EventExploreStarted, EventExploreMilestone, EventExploreStepCompleted, EventExploreCompleted, EventTacitusStarted, EventTacitusGatherSummary, EventTacitusCompleted, EventReplayComplete, EventLoopReattachedWire, EventCardReplayBegin, EventCardCreated, EventCardReplayEnd, EventToolStarted, EventToolCompleted, EventToolError, EventStreamToolCallUpdate, EventToolCallUpdatesBatch, EventStrangeLoopStarted, EventStrangeLoopCompleted, EventStrangeLoopPlanDecision, EventStrangeLoopReasoned, EventStrangeLoopStepStarted, EventStrangeLoopStepQueued, EventStrangeLoopStepCompleted, EventStrangeLoopContextCompacted, EventMessageReceived, EventMessageSent, EventFinalReport, EventAutopilotGoalStatus, EventAutopilotGoalProgress, EventAutopilotGoalCreated, EventAutopilotGoalCompleted, EventAutopilotWorkerAssigned, EventAutopilotWorkerUnassigned, EventGeneralFailed;
488
+ var init_events = __esm({
489
+ "src/events.ts"() {
490
+ "use strict";
491
+ init_verbosity();
492
+ EventPlanCreated = "soothe.cognition.plan.created";
493
+ EventExploreStarted = "soothe.subagent.explore.started";
494
+ EventExploreMilestone = "soothe.subagent.explore.milestone";
495
+ EventExploreStepCompleted = "soothe.subagent.explore.step.completed";
496
+ EventExploreCompleted = "soothe.subagent.explore.completed";
497
+ EventTacitusStarted = "soothe.subagent.tacitus.started";
498
+ EventTacitusGatherSummary = "soothe.subagent.tacitus.gather.summary";
499
+ EventTacitusCompleted = "soothe.subagent.tacitus.completed";
500
+ EventReplayComplete = "replay_complete";
501
+ EventLoopReattachedWire = "loop_reattached";
502
+ EventCardReplayBegin = "card.replay_begin";
503
+ EventCardCreated = "card.created";
504
+ EventCardReplayEnd = "card.replay_end";
505
+ EventToolStarted = "soothe.tool.execution.started";
506
+ EventToolCompleted = "soothe.tool.execution.completed";
507
+ EventToolError = "soothe.tool.execution.error";
508
+ EventStreamToolCallUpdate = "soothe.stream.tool_call.update";
509
+ EventToolCallUpdatesBatch = "tool_call_updates_batch";
510
+ EventStrangeLoopStarted = "soothe.cognition.strange_loop.started";
511
+ EventStrangeLoopCompleted = "soothe.cognition.strange_loop.completed";
512
+ EventStrangeLoopPlanDecision = "soothe.cognition.strange_loop.plan.decision";
513
+ EventStrangeLoopReasoned = "soothe.cognition.strange_loop.reasoned";
514
+ EventStrangeLoopStepStarted = "soothe.cognition.strange_loop.step.started";
515
+ EventStrangeLoopStepQueued = "soothe.cognition.strange_loop.step.queued";
516
+ EventStrangeLoopStepCompleted = "soothe.cognition.strange_loop.step.completed";
517
+ EventStrangeLoopContextCompacted = "soothe.cognition.strange_loop.context.compacted";
518
+ EventMessageReceived = "soothe.protocol.message.received";
519
+ EventMessageSent = "soothe.protocol.message.sent";
520
+ EventFinalReport = "soothe.output.autonomous.final_report.reported";
521
+ EventAutopilotGoalStatus = "soothe.autopilot.goal.status";
522
+ EventAutopilotGoalProgress = "soothe.autopilot.goal.progress";
523
+ EventAutopilotGoalCreated = "soothe.autopilot.goal.created";
524
+ EventAutopilotGoalCompleted = "soothe.autopilot.goal.completed";
525
+ EventAutopilotWorkerAssigned = "soothe.autopilot.worker.assigned";
526
+ EventAutopilotWorkerUnassigned = "soothe.autopilot.worker.unassigned";
527
+ EventGeneralFailed = "soothe.error.general.failed";
528
+ }
529
+ });
530
+
377
531
  // src/multiplexer.ts
378
532
  var Multiplexer;
379
533
  var init_multiplexer = __esm({
@@ -517,6 +671,203 @@ var init_multiplexer = __esm({
517
671
  }
518
672
  });
519
673
 
674
+ // src/stream_terminal.ts
675
+ function isTurnEndCustomData(data) {
676
+ if (!data || typeof data !== "object") return false;
677
+ const customType = String(data.type ?? "").trim();
678
+ if (!TURN_END_CUSTOM_TYPES.has(customType)) return false;
679
+ if (customType === STREAM_END) {
680
+ const scope = String(data.scope ?? "turn").trim().toLowerCase();
681
+ return scope === "" || scope === "turn";
682
+ }
683
+ return true;
684
+ }
685
+ function isTurnProgressChunk(mode, data) {
686
+ if (mode === "messages" || mode === "updates") return true;
687
+ if (mode !== "custom" || !data || typeof data !== "object") return false;
688
+ if (isTurnEndCustomData(data)) return false;
689
+ const customType = String(data.type ?? "").trim();
690
+ if (TURN_PROGRESS_CUSTOM_TYPES.has(customType)) return true;
691
+ if (customType.startsWith("soothe.cognition.strange_loop.step")) return true;
692
+ return false;
693
+ }
694
+ function stalePendingFrameLabel(event) {
695
+ const eventType = String(event.type ?? "");
696
+ if (STALE_TURN_PENDING_TYPES.has(eventType)) return eventType;
697
+ if (eventType === "next") {
698
+ const payload = event.payload;
699
+ if (!payload || typeof payload !== "object") return null;
700
+ const p = payload;
701
+ const staleMode = String(p.mode ?? "");
702
+ if (STALE_TURN_PENDING_TYPES.has(staleMode)) return staleMode;
703
+ const inner = p.data;
704
+ if (inner && typeof inner === "object") {
705
+ return stalePendingFrameLabel(inner);
706
+ }
707
+ return null;
708
+ }
709
+ if (eventType === "event") {
710
+ const mode = String(event.mode ?? "");
711
+ const data = event.data;
712
+ if (mode === "custom" && isTurnEndCustomData(data)) {
713
+ return String(data.type ?? "").trim();
714
+ }
715
+ }
716
+ return null;
717
+ }
718
+ function inboundNeedsDeliveryAck(event) {
719
+ const eventType = String(event.type ?? "");
720
+ if (eventType === "complete") return true;
721
+ if (eventType === "next") {
722
+ const payload = event.payload;
723
+ if (!payload || typeof payload !== "object") return false;
724
+ const p = payload;
725
+ const inner = p.data;
726
+ if (!inner || typeof inner !== "object") return false;
727
+ if (String(p.mode ?? "") === "event") {
728
+ return inboundNeedsAckFromEventShape(inner);
729
+ }
730
+ return false;
731
+ }
732
+ if (eventType === "event") return inboundNeedsAckFromEventShape(event);
733
+ return false;
734
+ }
735
+ function inboundNeedsAckFromEventShape(event) {
736
+ const mode = String(event.mode ?? "");
737
+ const data = event.data;
738
+ if (mode === "custom" && isTurnEndCustomData(data)) return true;
739
+ if (mode === "messages" && Array.isArray(data) && data.length > 0) {
740
+ const body = data[0];
741
+ if (!body || typeof body !== "object") return false;
742
+ const t = String(body.type ?? "");
743
+ return t === STREAM_END || t.includes("stream.end");
744
+ }
745
+ return false;
746
+ }
747
+ function extractLoopIdFromInbound(event) {
748
+ const direct = String(event.loop_id ?? "").trim();
749
+ if (direct) return direct;
750
+ if (String(event.type ?? "") !== "next") return "";
751
+ const payload = event.payload;
752
+ if (!payload || typeof payload !== "object") return "";
753
+ const p = payload;
754
+ const fromPayload = String(p.loop_id ?? "").trim();
755
+ if (fromPayload) return fromPayload;
756
+ const inner = p.data;
757
+ if (inner && typeof inner === "object") {
758
+ return String(inner.loop_id ?? "").trim();
759
+ }
760
+ return "";
761
+ }
762
+ var STREAM_END, TURN_END_CUSTOM_TYPES, TURN_PROGRESS_CUSTOM_TYPES, STALE_TURN_PENDING_TYPES;
763
+ var init_stream_terminal = __esm({
764
+ "src/stream_terminal.ts"() {
765
+ "use strict";
766
+ init_events();
767
+ STREAM_END = "soothe.stream.end";
768
+ TURN_END_CUSTOM_TYPES = /* @__PURE__ */ new Set([
769
+ STREAM_END,
770
+ EventStrangeLoopCompleted
771
+ ]);
772
+ TURN_PROGRESS_CUSTOM_TYPES = /* @__PURE__ */ new Set([
773
+ EventPlanCreated,
774
+ EventStrangeLoopStepStarted,
775
+ EventStrangeLoopStepQueued,
776
+ EventStrangeLoopStepCompleted
777
+ ]);
778
+ STALE_TURN_PENDING_TYPES = /* @__PURE__ */ new Set([
779
+ "connection_ack",
780
+ EventCardReplayBegin,
781
+ EventCardReplayEnd,
782
+ EventCardCreated,
783
+ "complete"
784
+ ]);
785
+ }
786
+ });
787
+
788
+ // src/inbound_priority.ts
789
+ function inboundFrameDropPriority(event) {
790
+ if (!event) return DROP_PRIORITY_CRITICAL;
791
+ let eventType = String(event.type ?? "");
792
+ if (eventType === "event_batch" || eventType === "tool_call_updates_batch") {
793
+ return DROP_PRIORITY_HIGH;
794
+ }
795
+ if (eventType === "next") {
796
+ const payload = event.payload;
797
+ if (payload && typeof payload === "object") {
798
+ const p = payload;
799
+ const innerMode = String(p.mode ?? "");
800
+ const innerData = p.data;
801
+ if (innerMode === "messages") {
802
+ if (messagesWireTerminal(innerData)) return DROP_PRIORITY_CRITICAL;
803
+ if (Array.isArray(innerData) && innerData[0] && typeof innerData[0] === "object") {
804
+ if (String(innerData[0].phase ?? "") === "goal_completion") {
805
+ return DROP_PRIORITY_CRITICAL;
806
+ }
807
+ }
808
+ }
809
+ if (String(p.type ?? "") === "complete") return DROP_PRIORITY_CRITICAL;
810
+ if (innerData && typeof innerData === "object") {
811
+ return inboundFrameDropPriority(innerData);
812
+ }
813
+ eventType = String(p.type ?? "");
814
+ }
815
+ }
816
+ if (eventType === "complete" || eventType === "error" || eventType === "connection_ack") {
817
+ return DROP_PRIORITY_CRITICAL;
818
+ }
819
+ if (eventType === "status") {
820
+ const state = String(event.state ?? "");
821
+ if (["idle", "running", "stopped", "detached"].includes(state)) {
822
+ return DROP_PRIORITY_CRITICAL;
823
+ }
824
+ }
825
+ if (eventType === "event") {
826
+ const mode = String(event.mode ?? "");
827
+ const data = event.data;
828
+ if (mode === "custom") {
829
+ if (isTurnEndCustomData(data)) return DROP_PRIORITY_CRITICAL;
830
+ if (data && typeof data === "object") {
831
+ const customType = String(data.type ?? "");
832
+ if (customType.startsWith("soothe.cognition.")) return DROP_PRIORITY_HIGH;
833
+ if (customType.startsWith("soothe.error.") || customType === "stream_degraded") {
834
+ return DROP_PRIORITY_CRITICAL;
835
+ }
836
+ if (customType === "soothe.ux.stream_tool_wire.tool_call_updates_batch") {
837
+ return DROP_PRIORITY_HIGH;
838
+ }
839
+ }
840
+ }
841
+ if (mode === "messages") {
842
+ if (messagesWireTerminal(data)) return DROP_PRIORITY_CRITICAL;
843
+ if (Array.isArray(data) && data[0] && typeof data[0] === "object") {
844
+ if (String(data[0].phase ?? "") === "goal_completion") {
845
+ return DROP_PRIORITY_CRITICAL;
846
+ }
847
+ }
848
+ }
849
+ }
850
+ return DROP_PRIORITY_NORMAL;
851
+ }
852
+ function messagesWireTerminal(data) {
853
+ if (!Array.isArray(data) || data.length === 0) return false;
854
+ const body = data[0];
855
+ if (!body || typeof body !== "object") return false;
856
+ const t = String(body.type ?? "");
857
+ return t === STREAM_END || t.includes("stream.end");
858
+ }
859
+ var DROP_PRIORITY_CRITICAL, DROP_PRIORITY_HIGH, DROP_PRIORITY_NORMAL, DEFAULT_INBOUND_MAX_SIZE;
860
+ var init_inbound_priority = __esm({
861
+ "src/inbound_priority.ts"() {
862
+ "use strict";
863
+ init_stream_terminal();
864
+ DROP_PRIORITY_CRITICAL = 0;
865
+ DROP_PRIORITY_HIGH = 1;
866
+ DROP_PRIORITY_NORMAL = 2;
867
+ DEFAULT_INBOUND_MAX_SIZE = 2e4;
868
+ }
869
+ });
870
+
520
871
  // src/client.ts
521
872
  var client_exports = {};
522
873
  __export(client_exports, {
@@ -532,14 +883,19 @@ var init_client = __esm({
532
883
  init_errors();
533
884
  init_multiplexer();
534
885
  init_intent_hints();
886
+ init_stream_terminal();
887
+ init_inbound_priority();
535
888
  init_protocol();
536
889
  Client = class extends import_node_events.EventEmitter {
537
890
  url;
538
891
  config;
539
892
  ws = null;
540
893
  messageBuffer = [];
894
+ inboundMaxSize = DEFAULT_INBOUND_MAX_SIZE;
895
+ inboundDroppedCount = 0;
896
+ onStreamDegraded = null;
541
897
  resolvers = [];
542
- // Protocol-1 handshake state (RFC-450 §8.2)
898
+ // Protocol-1 handshake state
543
899
  handshakeComplete = false;
544
900
  negotiatedCapabilities = /* @__PURE__ */ new Set();
545
901
  protocolVersion = null;
@@ -547,14 +903,16 @@ var init_client = __esm({
547
903
  heartbeatIntervalMs = 0;
548
904
  heartbeatTimer = null;
549
905
  lastPongMonotonic = 0;
550
- // Mid-session drop signal (RFC-450 §8.3). The 'disconnected' event is
906
+ // Mid-session drop signal. The 'disconnected' event is
551
907
  // emitted exactly once when the connection drops, carrying a DisconnectCause
552
908
  // that distinguishes clean (peer `disconnect`) from unclean (read/write
553
909
  // error or missed pong). `disconnFired` guards the once-only delivery.
554
910
  disconnFired = false;
555
- // Pending-request/subscription multiplexer (RFC-629 constraint #1). Routes
911
+ // Pending-request/subscription multiplexer. Routes
556
912
  // inbound frames by (type, id) instead of discarding non-matching events.
557
913
  mux = new Multiplexer();
914
+ deliveryRecvSeq = /* @__PURE__ */ new Map();
915
+ deliveryAckedSeq = /* @__PURE__ */ new Map();
558
916
  constructor(url, config) {
559
917
  super();
560
918
  this.url = url;
@@ -622,13 +980,15 @@ var init_client = __esm({
622
980
  this._signalDisconnect(1 /* Clean */);
623
981
  }
624
982
  if (this.mux.route(m)) {
983
+ this._trackInboundDeliveryAck(m);
625
984
  continue;
626
985
  }
986
+ this._trackInboundDeliveryAck(m);
627
987
  const resolver = this.resolvers.shift();
628
988
  if (resolver) {
629
989
  resolver(msg);
630
990
  } else {
631
- this.messageBuffer.push(msg);
991
+ this.enqueueMessageBuffer(msg);
632
992
  }
633
993
  this.emit("message", msg);
634
994
  }
@@ -665,7 +1025,7 @@ var init_client = __esm({
665
1025
  return this.ws !== null && this.ws.readyState === import_ws.default.OPEN && this.handshakeComplete;
666
1026
  }
667
1027
  // ---------------------------------------------------------------------------
668
- // Mid-session drop signal + reconnect/reattach (RFC-450 §8.3, RFC-629 L0)
1028
+ // Mid-session drop signal + reconnect/reattach
669
1029
  // ---------------------------------------------------------------------------
670
1030
  /**
671
1031
  * Returns whether the connection has dropped (the `'disconnected'` event has
@@ -700,8 +1060,8 @@ var init_client = __esm({
700
1060
  }
701
1061
  }
702
1062
  /**
703
- * Re-dials the daemon and re-handshakes after a connection drop (RFC-450
704
- * §8.3). Does not re-establish loop subscriptions; follow with
1063
+ * Re-dials the daemon and re-handshakes after a connection drop.
1064
+ * Does not re-establish loop subscriptions; follow with
705
1065
  * `reattachAndProbe()` to resume a loop session. The caller should invoke
706
1066
  * this after the `'disconnected'` event fires. Reuses the same Client,
707
1067
  * resetting the drop signal and multiplexer.
@@ -735,7 +1095,7 @@ var init_client = __esm({
735
1095
  * Returns a `StaleLoopError` when the probe fails; callers should fall back
736
1096
  * to a fresh `loop_new` bootstrap.
737
1097
  *
738
- * Per RFC-629: connection-level readiness is the handshake's readiness_state
1098
+ * Note: connection-level readiness is the handshake's readiness_state
739
1099
  * (+ daemon_status); loop_get is a loop-scoped probe only, not a readiness
740
1100
  * probe.
741
1101
  */
@@ -776,7 +1136,7 @@ var init_client = __esm({
776
1136
  }
777
1137
  }
778
1138
  // ---------------------------------------------------------------------------
779
- // Protocol-1 handshake (RFC-450 §8.2)
1139
+ // Protocol-1 handshake
780
1140
  // ---------------------------------------------------------------------------
781
1141
  /** Send connection_init and wait for connection_ack with readiness "ready". */
782
1142
  async _performHandshake() {
@@ -825,7 +1185,7 @@ var init_client = __esm({
825
1185
  throw new Error(`timeout after ${this.config.daemonReadyTimeout}ms waiting for connection_ack`);
826
1186
  }
827
1187
  // ---------------------------------------------------------------------------
828
- // Heartbeat (RFC-450 §8.3)
1188
+ // Heartbeat
829
1189
  // ---------------------------------------------------------------------------
830
1190
  _startHeartbeat() {
831
1191
  if (!this.negotiatedCapabilities.has("heartbeat")) return;
@@ -941,8 +1301,86 @@ var init_client = __esm({
941
1301
  this.resolvers.push(resolver);
942
1302
  });
943
1303
  }
1304
+ /**
1305
+ * Remove stale handshake/terminal frames left in `messageBuffer` before a turn.
1306
+ * Returns labels of removed frames (in order).
1307
+ */
1308
+ peelStalePendingControlEvents() {
1309
+ if (this.messageBuffer.length === 0) return [];
1310
+ const kept = [];
1311
+ const removed = [];
1312
+ while (this.messageBuffer.length > 0) {
1313
+ const event = this.messageBuffer.shift();
1314
+ const label = stalePendingFrameLabel(event);
1315
+ if (label !== null) {
1316
+ removed.push(label);
1317
+ continue;
1318
+ }
1319
+ kept.push(event);
1320
+ }
1321
+ this.messageBuffer = kept;
1322
+ return removed;
1323
+ }
1324
+ /** True when the underlying socket is still open (may not be handshaked). */
1325
+ isConnectionAlive() {
1326
+ return this.ws !== null && this.ws.readyState === import_ws.default.OPEN;
1327
+ }
1328
+ /** Override pending buffer cap (tests / tuning). */
1329
+ setInboundMaxSize(n) {
1330
+ if (n > 0) this.inboundMaxSize = n;
1331
+ }
1332
+ /** How many NORMAL-priority frames were dropped under backpressure. */
1333
+ inboundDropped() {
1334
+ return this.inboundDroppedCount;
1335
+ }
1336
+ /** Hook invoked on the first inbound overflow drop. */
1337
+ setStreamDegradedCallback(fn) {
1338
+ this.onStreamDegraded = fn;
1339
+ }
1340
+ enqueueMessageBuffer(msg) {
1341
+ const max = this.inboundMaxSize > 0 ? this.inboundMaxSize : DEFAULT_INBOUND_MAX_SIZE;
1342
+ if (this.messageBuffer.length < max) {
1343
+ this.messageBuffer.push(msg);
1344
+ return;
1345
+ }
1346
+ const ev = msg;
1347
+ let dropIdx = -1;
1348
+ let dropPri = -1;
1349
+ for (let i = 0; i < this.messageBuffer.length; i++) {
1350
+ const p = inboundFrameDropPriority(this.messageBuffer[i]);
1351
+ if (p > dropPri) {
1352
+ dropPri = p;
1353
+ dropIdx = i;
1354
+ }
1355
+ }
1356
+ const incomingPri = inboundFrameDropPriority(ev);
1357
+ if (dropIdx >= 0 && dropPri >= DROP_PRIORITY_NORMAL) {
1358
+ this.messageBuffer.splice(dropIdx, 1);
1359
+ this.messageBuffer.push(msg);
1360
+ this.noteInboundDrop();
1361
+ return;
1362
+ }
1363
+ if (incomingPri >= DROP_PRIORITY_NORMAL) {
1364
+ this.noteInboundDrop();
1365
+ return;
1366
+ }
1367
+ if (this.messageBuffer.length > 0) {
1368
+ this.messageBuffer.shift();
1369
+ this.noteInboundDrop();
1370
+ }
1371
+ this.messageBuffer.push(msg);
1372
+ }
1373
+ noteInboundDrop() {
1374
+ this.inboundDroppedCount += 1;
1375
+ if (this.onStreamDegraded && this.inboundDroppedCount === 1) {
1376
+ try {
1377
+ this.onStreamDegraded(1, "inbound_queue_overflow");
1378
+ } catch {
1379
+ }
1380
+ }
1381
+ }
944
1382
  // ---------------------------------------------------------------------------
945
- // Protocol-1 RPC primitives (RFC-450 §5/§9)
1383
+ // Protocol-1 RPC primitives
946
1384
  // ---------------------------------------------------------------------------
947
1385
  /**
948
1386
  * Reads the next frame directly from the live socket (via a resolver),
@@ -968,16 +1406,16 @@ var init_client = __esm({
968
1406
  });
969
1407
  }
970
1408
  /**
971
- * Sends a `request` envelope and waits for the matching `response` (or
972
- * `error`) correlated by `id` (RFC-450 §5/§9). Returns the `result` object.
973
- *
974
- * Multiplexer-aware (RFC-629 constraint #1): registers a pending RPC wait
975
- * keyed by the request id so that, even when a `receiveMessages()` reader
976
- * is concurrently active, the matching `response`/`error` is routed to
977
- * this caller instead of being discarded or buffered behind a stream.
978
- * Non-matching frames are routed to their own waiters by the multiplexer
979
- * or flow on to the resolver queue for stream readers.
980
- */
1409
+ * Sends a `request` envelope and waits for the matching `response` (or
1410
+ * `error`) correlated by `id`. Returns the `result` object.
1411
+ *
1412
+ * Multiplexer-aware: registers a pending RPC wait
1413
+ * keyed by the request id so that, even when a `receiveMessages()` reader
1414
+ * is concurrently active, the matching `response`/`error` is routed to
1415
+ * this caller instead of being discarded or buffered behind a stream.
1416
+ * Non-matching frames are routed to their own waiters by the multiplexer
1417
+ * or flow on to the resolver queue for stream readers.
1418
+ */
981
1419
  async requestResponse(method, params, responseType, timeout = 15e3) {
982
1420
  const req = requestEnvelope(method, params);
983
1421
  const rid = req.id;
@@ -1045,6 +1483,35 @@ var init_client = __esm({
1045
1483
  notify(method, params) {
1046
1484
  return this.sendMessage(notificationEnvelope(method, params));
1047
1485
  }
1486
+ _trackInboundDeliveryAck(event) {
1487
+ if (String(event.type ?? "") === "event_batch") {
1488
+ const events = event.events;
1489
+ if (Array.isArray(events)) {
1490
+ for (const sub of events) {
1491
+ if (sub && typeof sub === "object") {
1492
+ this._trackInboundDeliveryAck(sub);
1493
+ }
1494
+ }
1495
+ }
1496
+ return;
1497
+ }
1498
+ if (!inboundNeedsDeliveryAck(event)) return;
1499
+ const loopId = extractLoopIdFromInbound(event);
1500
+ if (!loopId) return;
1501
+ const next = (this.deliveryRecvSeq.get(loopId) ?? 0) + 1;
1502
+ this.deliveryRecvSeq.set(loopId, next);
1503
+ void this._sendDeliveryAck(loopId, next);
1504
+ }
1505
+ async _sendDeliveryAck(loopId, seq) {
1506
+ const acked = this.deliveryAckedSeq.get(loopId) ?? 0;
1507
+ if (seq <= acked) return;
1508
+ this.deliveryAckedSeq.set(loopId, seq);
1509
+ if (!this.isConnected()) return;
1510
+ try {
1511
+ await this.notify("delivery_ack", { loop_id: loopId, seq });
1512
+ } catch {
1513
+ }
1514
+ }
1048
1515
  /**
1049
1516
  * Starts a subscription stream. Returns the subscription `id` for later
1050
1517
  * correlation and `unsubscribe()`. Stream events arrive as `next` frames
@@ -1062,7 +1529,7 @@ var init_client = __esm({
1062
1529
  if (ev === null) break;
1063
1530
  const evId = ev.id;
1064
1531
  if (evId !== subId) {
1065
- this.messageBuffer.push(ev);
1532
+ this.enqueueMessageBuffer(ev);
1066
1533
  continue;
1067
1534
  }
1068
1535
  const typ = ev.type;
@@ -1099,7 +1566,7 @@ var init_client = __esm({
1099
1566
  return ev;
1100
1567
  }
1101
1568
  // ---------------------------------------------------------------------------
1102
- // High-level API methods (Loop-first, RFC-503)
1569
+ // High-level API methods
1103
1570
  // ---------------------------------------------------------------------------
1104
1571
  /** Sends user input to the daemon (loop_input notification; requires loopID). */
1105
1572
  sendInput(text, options) {
@@ -1138,7 +1605,7 @@ var init_client = __esm({
1138
1605
  return this.notify("slash_command", { cmd });
1139
1606
  }
1140
1607
  // ---------------------------------------------------------------------------
1141
- // Loop lifecycle methods (RFC-503)
1608
+ // Loop lifecycle methods
1142
1609
  // ---------------------------------------------------------------------------
1143
1610
  /** Requests the daemon to create a new StrangeLoop and waits for the response. */
1144
1611
  sendLoopNew(opts) {
@@ -1233,7 +1700,7 @@ var init_client = __esm({
1233
1700
  sendLoopCardsFetch(loopID) {
1234
1701
  return this.sendMessage(requestEnvelope("loop_cards_fetch", { loop_id: loopID }));
1235
1702
  }
1236
- /** Requests the full loop history (RFC-631). */
1703
+ /** Requests the full loop history. */
1237
1704
  sendLoopHistoryFetch(loopID) {
1238
1705
  return this.sendMessage(requestEnvelope("loop_history_fetch", { loop_id: loopID }));
1239
1706
  }
@@ -1328,7 +1795,7 @@ var init_client = __esm({
1328
1795
  );
1329
1796
  }
1330
1797
  // ---------------------------------------------------------------------------
1331
- // RFC-228 Job IPC methods
1798
+ // Job IPC methods
1332
1799
  // ---------------------------------------------------------------------------
1333
1800
  /** Creates an autopilot job and waits for the response. */
1334
1801
  createJob(goal, verificationRules, workspace, timeout) {
@@ -1363,6 +1830,98 @@ var init_client = __esm({
1363
1830
  if (goalId) params.goal_id = goalId;
1364
1831
  return this.requestResponse("job_guidance", params, "job_guidance", timeout ?? 3e4);
1365
1832
  }
1833
+ // ---------------------------------------------------------------------------
1834
+ // Autopilot goal RPCs (protocol-1 request methods)
1835
+ // ---------------------------------------------------------------------------
1836
+ /** Return autopilot scheduler status (running / dreaming / pool). */
1837
+ autopilotStatus(timeout) {
1838
+ return this.requestResponse("autopilot_status", {}, "autopilot_status", timeout ?? 15e3);
1839
+ }
1840
+ /** Submit a new autopilot goal (returns goal_id). */
1841
+ autopilotSubmit(description, opts) {
1842
+ const params = {
1843
+ description,
1844
+ priority: opts?.priority ?? 50
1845
+ };
1846
+ if (opts?.workspace) params.workspace = opts.workspace;
1847
+ return this.requestResponse(
1848
+ "autopilot_submit",
1849
+ params,
1850
+ "autopilot_submit",
1851
+ opts?.timeout ?? 15e3
1852
+ );
1853
+ }
1854
+ /** List all goals (including non-root children). */
1855
+ autopilotListGoals(timeout) {
1856
+ return this.requestResponse(
1857
+ "autopilot_list_goals",
1858
+ {},
1859
+ "autopilot_list_goals",
1860
+ timeout ?? 15e3
1861
+ );
1862
+ }
1863
+ /** Fetch one goal by id. */
1864
+ autopilotGetGoal(goalId, timeout) {
1865
+ return this.requestResponse(
1866
+ "autopilot_get_goal",
1867
+ { goal_id: goalId },
1868
+ "autopilot_get_goal",
1869
+ timeout ?? 15e3
1870
+ );
1871
+ }
1872
+ /** Cancel a goal and its non-terminal descendants. */
1873
+ autopilotCancelGoal(goalId, timeout) {
1874
+ return this.requestResponse(
1875
+ "autopilot_cancel_goal",
1876
+ { goal_id: goalId },
1877
+ "autopilot_cancel_goal",
1878
+ timeout ?? 15e3
1879
+ );
1880
+ }
1881
+ /** Cancel every open (non-terminal) goal. */
1882
+ autopilotCancelAll(timeout) {
1883
+ return this.requestResponse(
1884
+ "autopilot_cancel_all",
1885
+ {},
1886
+ "autopilot_cancel_all",
1887
+ timeout ?? 15e3
1888
+ );
1889
+ }
1890
+ /** Exit dreaming mode and resume scheduling. */
1891
+ autopilotWake(timeout) {
1892
+ return this.requestResponse("autopilot_wake", {}, "autopilot_wake", timeout ?? 15e3);
1893
+ }
1894
+ /** Force dreaming mode. */
1895
+ autopilotDream(timeout) {
1896
+ return this.requestResponse("autopilot_dream", {}, "autopilot_dream", timeout ?? 15e3);
1897
+ }
1898
+ /** Resume a suspended or blocked goal. */
1899
+ autopilotResume(goalId, timeout) {
1900
+ return this.requestResponse(
1901
+ "autopilot_resume",
1902
+ { goal_id: goalId },
1903
+ "autopilot_resume",
1904
+ timeout ?? 15e3
1905
+ );
1906
+ }
1907
+ /** List root goals only (jobs). Prefer createJob / getJobStatus for job control. */
1908
+ autopilotListJobs(timeout) {
1909
+ return this.requestResponse(
1910
+ "autopilot_list_jobs",
1911
+ {},
1912
+ "autopilot_list_jobs",
1913
+ timeout ?? 15e3
1914
+ );
1915
+ }
1916
+ /** Get a root job with DAG snapshot. Prefer getJobStatus / getJobDag. */
1917
+ autopilotGetJob(jobId, timeout) {
1918
+ return this.requestResponse(
1919
+ "autopilot_get_job",
1920
+ { job_id: jobId },
1921
+ "autopilot_get_job",
1922
+ timeout ?? 15e3
1923
+ );
1924
+ }
1366
1925
  /** Subscribes to autopilot worker events. */
1367
1926
  autopilotSubscribe(timeout) {
1368
1927
  return this.subscribe("autopilot_events", {}, timeout ?? 15e3);
@@ -1373,7 +1932,7 @@ var init_client = __esm({
1373
1932
  return this._requestResponseForEnvelope(req, "autopilot_unsubscribe", timeout ?? 15e3);
1374
1933
  }
1375
1934
  // ---------------------------------------------------------------------------
1376
- // RFC-229 Cron IPC methods
1935
+ // Cron IPC methods
1377
1936
  // ---------------------------------------------------------------------------
1378
1937
  /** Creates a scheduled job from natural language. */
1379
1938
  cronAdd(text, priority, timeout) {
@@ -1437,13 +1996,17 @@ __export(index_exports, {
1437
1996
  CLIENT_VERSION: () => CLIENT_VERSION,
1438
1997
  ChatEventTerminal: () => ChatEventTerminal,
1439
1998
  Client: () => Client,
1999
+ CommandClient: () => CommandClient,
1440
2000
  ConnectionError: () => ConnectionError,
1441
2001
  ConnectionPool: () => ConnectionPool,
1442
2002
  DEFAULT_CLIENT_CAPABILITIES: () => DEFAULT_CLIENT_CAPABILITIES,
1443
2003
  DEFAULT_DELIVERABLE_PHASES: () => DEFAULT_DELIVERABLE_PHASES,
2004
+ DEFAULT_POST_IDLE_DRAIN_MS: () => DEFAULT_POST_IDLE_DRAIN_MS,
1444
2005
  DEFAULT_THINKING_STEP_EVENTS: () => DEFAULT_THINKING_STEP_EVENTS,
1445
2006
  DaemonError: () => DaemonError,
2007
+ DaemonSession: () => DaemonSession,
1446
2008
  DisconnectCause: () => DisconnectCause,
2009
+ ErrIdleTimeout: () => ErrIdleTimeout,
1447
2010
  ErrPoolExhausted: () => ErrPoolExhausted,
1448
2011
  ErrQueryBusy: () => ErrQueryBusy,
1449
2012
  ErrQueryTimeout: () => ErrQueryTimeout,
@@ -1489,26 +2052,31 @@ __export(index_exports, {
1489
2052
  INTENT_HINT_OCR: () => INTENT_HINT_OCR,
1490
2053
  INTENT_HINT_TEXT_COMPLETION: () => INTENT_HINT_TEXT_COMPLETION,
1491
2054
  LOOP_ASSISTANT_OUTPUT_PHASES: () => LOOP_ASSISTANT_OUTPUT_PHASES,
1492
- Multiplexer: () => Multiplexer,
1493
2055
  PROTO_VERSION: () => PROTO_VERSION,
1494
2056
  PooledConn: () => PooledConn,
1495
2057
  QueryGate: () => QueryGate,
1496
2058
  REMOVED_INTENT_HINTS: () => REMOVED_INTENT_HINTS,
1497
2059
  ReconnectError: () => ReconnectError,
1498
2060
  SSEBroadcaster: () => SSEBroadcaster,
2061
+ STREAM_END: () => STREAM_END,
1499
2062
  StaleLoopError: () => StaleLoopError,
2063
+ StreamCloseFail: () => StreamCloseFail,
2064
+ StreamCloseSoftComplete: () => StreamCloseSoftComplete,
1500
2065
  TimeoutError: () => TimeoutError,
2066
+ TimeoutPolicy: () => TimeoutPolicy,
2067
+ TurnEventStats: () => TurnEventStats,
1501
2068
  TurnRunner: () => TurnRunner,
1502
2069
  VerbosityTier: () => VerbosityTier,
1503
2070
  authenticate: () => authenticate,
1504
2071
  bootstrapLoopSession: () => bootstrapLoopSession,
1505
2072
  checkDaemonStatus: () => checkDaemonStatus,
1506
2073
  classifyEventVerbosity: () => classifyEventVerbosity,
2074
+ compactAttachments: () => compactAttachments,
2075
+ compactImageAttachment: () => compactImageAttachment,
1507
2076
  connectWithRetries: () => connectWithRetries,
2077
+ connectedWebsocket: () => connectedWebsocket,
1508
2078
  connectionInitEnvelope: () => connectionInitEnvelope,
1509
2079
  decodeMessage: () => decodeMessage,
1510
- defaultBootstrapFunc: () => defaultBootstrapFunc,
1511
- defaultClientFactory: () => defaultClientFactory,
1512
2080
  defaultConfig: () => defaultConfig,
1513
2081
  defaultPoolConfig: () => defaultPoolConfig,
1514
2082
  disconnectCauseName: () => disconnectCauseName,
@@ -1517,12 +2085,18 @@ __export(index_exports, {
1517
2085
  extractSootheLoopID: () => extractSootheLoopID,
1518
2086
  extractThinkingStep: () => extractThinkingStep,
1519
2087
  fetchConfigSection: () => fetchConfigSection,
2088
+ fetchLoopCards: () => fetchLoopCards,
1520
2089
  fetchLoopHistory: () => fetchLoopHistory,
2090
+ fetchLoopMessages: () => fetchLoopMessages,
1521
2091
  fetchSkillsCatalog: () => fetchSkillsCatalog,
2092
+ idleTimeoutForTurn: () => idleTimeoutForTurn,
2093
+ inboundNeedsDeliveryAck: () => inboundNeedsDeliveryAck,
1522
2094
  inputMessageForLoop: () => inputMessageForLoop,
1523
2095
  isCompletionEvent: () => isCompletionEvent,
1524
2096
  isDaemonLive: () => isDaemonLive,
1525
2097
  isSubagentProgressEvent: () => isSubagentProgressEvent,
2098
+ isTurnEndCustomData: () => isTurnEndCustomData,
2099
+ isTurnProgressChunk: () => isTurnProgressChunk,
1526
2100
  isValidVerbosityLevel: () => isValidVerbosityLevel,
1527
2101
  loadConfigFromEnv: () => loadConfigFromEnv,
1528
2102
  newLoopInputMessage: () => newLoopInputMessage,
@@ -1533,6 +2107,7 @@ __export(index_exports, {
1533
2107
  parseNamespace: () => parseNamespace,
1534
2108
  pingEnvelope: () => pingEnvelope,
1535
2109
  pongEnvelope: () => pongEnvelope,
2110
+ protocol1Rpc: () => protocol1Rpc,
1536
2111
  refreshAuthToken: () => refreshAuthToken,
1537
2112
  requestDaemonConfigReload: () => requestDaemonConfigReload,
1538
2113
  requestDaemonShutdown: () => requestDaemonShutdown,
@@ -1548,237 +2123,16 @@ __export(index_exports, {
1548
2123
  });
1549
2124
  module.exports = __toCommonJS(index_exports);
1550
2125
  init_errors();
1551
-
1552
- // src/verbosity.ts
1553
- var VerbosityTier = /* @__PURE__ */ ((VerbosityTier2) => {
1554
- VerbosityTier2[VerbosityTier2["Quiet"] = 0] = "Quiet";
1555
- VerbosityTier2[VerbosityTier2["Normal"] = 1] = "Normal";
1556
- VerbosityTier2[VerbosityTier2["Detailed"] = 2] = "Detailed";
1557
- VerbosityTier2[VerbosityTier2["Debug"] = 3] = "Debug";
1558
- VerbosityTier2[VerbosityTier2["Internal"] = 99] = "Internal";
1559
- return VerbosityTier2;
1560
- })(VerbosityTier || {});
1561
- var verbosityLevelValues = {
1562
- quiet: 0,
1563
- normal: 1,
1564
- debug: 3
1565
- };
1566
- function shouldShow(tier, verbosity) {
1567
- if (tier === 99 /* Internal */) {
1568
- return false;
1569
- }
1570
- const level = verbosityLevelValues[verbosity] ?? 1;
1571
- return tier <= level;
1572
- }
1573
- function isValidVerbosityLevel(s) {
1574
- return s in verbosityLevelValues;
1575
- }
1576
-
1577
- // src/index.ts
2126
+ init_verbosity();
1578
2127
  init_config();
1579
2128
  init_protocol();
1580
2129
  init_intent_hints();
2130
+ init_events();
2131
+ init_client();
1581
2132
 
1582
- // src/events.ts
1583
- var EventPlanCreated = "soothe.cognition.plan.created";
1584
- var EventExploreStarted = "soothe.subagent.explore.started";
1585
- var EventExploreMilestone = "soothe.subagent.explore.milestone";
1586
- var EventExploreStepCompleted = "soothe.subagent.explore.step.completed";
1587
- var EventExploreCompleted = "soothe.subagent.explore.completed";
1588
- var EventTacitusStarted = "soothe.subagent.tacitus.started";
1589
- var EventTacitusGatherSummary = "soothe.subagent.tacitus.gather.summary";
1590
- var EventTacitusCompleted = "soothe.subagent.tacitus.completed";
1591
- var EventReplayComplete = "replay_complete";
1592
- var EventLoopReattachedWire = "loop_reattached";
1593
- var EventCardReplayBegin = "card.replay_begin";
1594
- var EventCardCreated = "card.created";
1595
- var EventCardReplayEnd = "card.replay_end";
1596
- var EventToolStarted = "soothe.tool.execution.started";
1597
- var EventToolCompleted = "soothe.tool.execution.completed";
1598
- var EventToolError = "soothe.tool.execution.error";
1599
- var EventStreamToolCallUpdate = "soothe.stream.tool_call.update";
1600
- var EventToolCallUpdatesBatch = "tool_call_updates_batch";
1601
- var EventStrangeLoopStarted = "soothe.cognition.strange_loop.started";
1602
- var EventStrangeLoopCompleted = "soothe.cognition.strange_loop.completed";
1603
- var EventStrangeLoopPlanDecision = "soothe.cognition.strange_loop.plan.decision";
1604
- var EventStrangeLoopReasoned = "soothe.cognition.strange_loop.reasoned";
1605
- var EventStrangeLoopStepStarted = "soothe.cognition.strange_loop.step.started";
1606
- var EventStrangeLoopStepQueued = "soothe.cognition.strange_loop.step.queued";
1607
- var EventStrangeLoopStepCompleted = "soothe.cognition.strange_loop.step.completed";
1608
- var EventStrangeLoopContextCompacted = "soothe.cognition.strange_loop.context.compacted";
1609
- var EventMessageReceived = "soothe.protocol.message.received";
1610
- var EventMessageSent = "soothe.protocol.message.sent";
1611
- var EventFinalReport = "soothe.output.autonomous.final_report.reported";
1612
- var EventAutopilotGoalStatus = "soothe.autopilot.goal.status";
1613
- var EventAutopilotGoalProgress = "soothe.autopilot.goal.progress";
1614
- var EventAutopilotGoalCreated = "soothe.autopilot.goal.created";
1615
- var EventAutopilotGoalCompleted = "soothe.autopilot.goal.completed";
1616
- var EventAutopilotWorkerAssigned = "soothe.autopilot.worker.assigned";
1617
- var EventAutopilotWorkerUnassigned = "soothe.autopilot.worker.unassigned";
1618
- var EventGeneralFailed = "soothe.error.general.failed";
1619
- function parseNamespace(ns) {
1620
- const parts = splitNamespace(ns);
1621
- if (parts.length < 4 || parts[0] !== "soothe") {
1622
- return null;
1623
- }
1624
- if (parts[1] === "internal") {
1625
- return null;
1626
- }
1627
- return { domain: parts[1], component: parts[2], action: parts[3] };
1628
- }
1629
- function splitNamespace(ns) {
1630
- const parts = [];
1631
- let start = 0;
1632
- for (let i = 0; i < ns.length; i++) {
1633
- if (ns[i] === ".") {
1634
- parts.push(ns.slice(start, i));
1635
- start = i + 1;
1636
- }
1637
- }
1638
- parts.push(ns.slice(start));
1639
- return parts;
1640
- }
1641
- function classifyEventVerbosity(eventTypeOrNamespace) {
1642
- const parsed = parseNamespace(eventTypeOrNamespace);
1643
- if (!parsed) {
1644
- return classifyByEventTypeString(eventTypeOrNamespace);
1645
- }
1646
- return classifyByDomainAndComponent(parsed.domain, parsed.component, eventTypeOrNamespace);
1647
- }
1648
- function classifyByDomainAndComponent(domain, _component, full) {
1649
- switch (domain) {
1650
- case "cognition":
1651
- return 1 /* Normal */;
1652
- case "protocol":
1653
- return 2 /* Detailed */;
1654
- case "tool":
1655
- return 99 /* Internal */;
1656
- case "subagent":
1657
- return classifySubagentEvent(full);
1658
- case "autopilot":
1659
- return 1 /* Normal */;
1660
- case "output":
1661
- case "error":
1662
- return 0 /* Quiet */;
1663
- default:
1664
- return 1 /* Normal */;
1665
- }
1666
- }
1667
- function classifySubagentEvent(full) {
1668
- const parsed = parseNamespace(full);
1669
- if (!parsed) return 1 /* Normal */;
1670
- switch (parsed.action) {
1671
- case "started":
1672
- case "completed":
1673
- return 1 /* Normal */;
1674
- default:
1675
- return 2 /* Detailed */;
1676
- }
1677
- }
1678
- function classifyByEventTypeString(eventType) {
1679
- if (eventType === EventFinalReport || eventType === EventGeneralFailed) {
1680
- return 0 /* Quiet */;
1681
- }
1682
- if (eventType === EventToolStarted) {
1683
- return 99 /* Internal */;
1684
- }
1685
- return 1 /* Normal */;
1686
- }
1687
- function isCompletionEvent(eventType) {
1688
- return eventType.endsWith(".completed") || eventType.endsWith(".failed") || eventType === EventGeneralFailed;
1689
- }
1690
- function isSubagentProgressEvent(eventType) {
1691
- const parsed = parseNamespace(eventType);
1692
- if (!parsed || parsed.domain !== "subagent") {
1693
- return false;
1694
- }
1695
- return parsed.action === "started" || parsed.action === "completed";
1696
- }
1697
-
1698
- // src/index.ts
2133
+ // src/command_client.ts
1699
2134
  init_client();
1700
- init_multiplexer();
1701
-
1702
- // src/helpers.ts
1703
2135
  init_config();
1704
- async function checkDaemonStatus(client, timeout) {
1705
- return client.requestResponse("daemon_status", {}, "daemon_status", timeout ?? 5e3);
1706
- }
1707
- async function isDaemonLive(wsURL, timeout) {
1708
- const { Client: Client2 } = await Promise.resolve().then(() => (init_client(), client_exports));
1709
- const t = timeout ?? 5e3;
1710
- const client = new Client2(wsURL, defaultConfig());
1711
- try {
1712
- await client.connect();
1713
- } catch {
1714
- return false;
1715
- }
1716
- try {
1717
- await checkDaemonStatus(client, t);
1718
- return true;
1719
- } catch {
1720
- return false;
1721
- } finally {
1722
- client.close();
1723
- }
1724
- }
1725
- async function requestDaemonShutdown(client, timeout) {
1726
- const resp = await client.requestResponse(
1727
- "daemon_shutdown",
1728
- {},
1729
- "daemon_shutdown",
1730
- timeout ?? 1e4
1731
- );
1732
- if (resp.status !== "acknowledged") {
1733
- throw new Error(`shutdown not acknowledged: ${JSON.stringify(resp)}`);
1734
- }
1735
- }
1736
- async function fetchSkillsCatalog(client, timeout) {
1737
- const resp = await client.requestResponse("skills_list", {}, "skills_list", timeout ?? 15e3);
1738
- const skillsRaw = resp.skills;
1739
- if (!skillsRaw || !Array.isArray(skillsRaw)) return [];
1740
- return skillsRaw.filter((s) => typeof s === "object" && s !== null);
1741
- }
1742
- async function fetchConfigSection(client, section, timeout) {
1743
- const resp = await client.requestResponse(
1744
- "config_get",
1745
- { section },
1746
- "config_get",
1747
- timeout ?? 5e3
1748
- );
1749
- const sec = resp[section];
1750
- if (sec && typeof sec === "object") {
1751
- return sec;
1752
- }
1753
- return resp;
1754
- }
1755
- async function requestDaemonConfigReload(client, timeout) {
1756
- return client.requestResponse("config_reload", {}, "config_reload", timeout ?? 15e3);
1757
- }
1758
- async function fetchLoopHistory(client, loopID, timeout) {
1759
- return client.requestResponse(
1760
- "loop_history_fetch",
1761
- { loop_id: loopID },
1762
- "loop_history_fetch",
1763
- timeout ?? 15e3
1764
- );
1765
- }
1766
- async function authenticate(client, accessKey, secretKey, timeout) {
1767
- return client.requestResponse(
1768
- "auth",
1769
- { access_key: accessKey, secret_key: secretKey },
1770
- "auth",
1771
- timeout ?? 15e3
1772
- );
1773
- }
1774
- async function refreshAuthToken(client, refreshToken, timeout) {
1775
- return client.requestResponse(
1776
- "auth_refresh",
1777
- { refresh_token: refreshToken },
1778
- "auth_refresh",
1779
- timeout ?? 15e3
1780
- );
1781
- }
1782
2136
 
1783
2137
  // src/session.ts
1784
2138
  init_config();
@@ -1882,6 +2236,256 @@ async function connectWithRetries(client, maxRetries, retryDelay) {
1882
2236
  );
1883
2237
  }
1884
2238
 
2239
+ // src/command_client.ts
2240
+ var CommandClient = class {
2241
+ url;
2242
+ timeoutMs;
2243
+ config;
2244
+ constructor(url, opts) {
2245
+ this.url = url;
2246
+ this.timeoutMs = opts?.timeoutMs && opts.timeoutMs > 0 ? opts.timeoutMs : 3e4;
2247
+ this.config = opts?.config ?? defaultConfig();
2248
+ }
2249
+ async withClient(fn) {
2250
+ const client = new Client(this.url, this.config);
2251
+ try {
2252
+ await connectWithRetries(client, 5, 250);
2253
+ return await fn(client);
2254
+ } finally {
2255
+ client.close();
2256
+ }
2257
+ }
2258
+ /** Generic one-shot RPC. */
2259
+ async request(method, params = {}) {
2260
+ return this.withClient(
2261
+ (client) => client.requestResponse(method, params, void 0, this.timeoutMs)
2262
+ );
2263
+ }
2264
+ async jobCreate(goal, workspace = "") {
2265
+ const params = { goal };
2266
+ if (workspace) params.workspace = workspace;
2267
+ return this.request("job_create", params);
2268
+ }
2269
+ async jobStatus(jobId) {
2270
+ return this.request("job_status", { job_id: jobId });
2271
+ }
2272
+ async jobCancel(jobId) {
2273
+ return this.request("job_cancel", { job_id: jobId });
2274
+ }
2275
+ /** Return autopilot scheduler status (running / dreaming / pool). */
2276
+ async autopilotStatus() {
2277
+ return this.request("autopilot_status");
2278
+ }
2279
+ /** Submit a new autopilot goal (returns goal_id). */
2280
+ async autopilotSubmit(description, opts) {
2281
+ const params = {
2282
+ description,
2283
+ priority: opts?.priority ?? 50
2284
+ };
2285
+ if (opts?.workspace) params.workspace = opts.workspace;
2286
+ return this.request("autopilot_submit", params);
2287
+ }
2288
+ /** List all goals (including non-root children). */
2289
+ async autopilotListGoals() {
2290
+ return this.request("autopilot_list_goals");
2291
+ }
2292
+ /** Fetch one goal by id. */
2293
+ async autopilotGetGoal(goalId) {
2294
+ return this.request("autopilot_get_goal", { goal_id: goalId });
2295
+ }
2296
+ /** Cancel a goal and its non-terminal descendants. */
2297
+ async autopilotCancelGoal(goalId) {
2298
+ return this.request("autopilot_cancel_goal", { goal_id: goalId });
2299
+ }
2300
+ /** Cancel every open (non-terminal) goal. */
2301
+ async autopilotCancelAll() {
2302
+ return this.request("autopilot_cancel_all");
2303
+ }
2304
+ /** Exit dreaming mode and resume scheduling. */
2305
+ async autopilotWake() {
2306
+ return this.request("autopilot_wake");
2307
+ }
2308
+ /** Force dreaming mode. */
2309
+ async autopilotDream() {
2310
+ return this.request("autopilot_dream");
2311
+ }
2312
+ /** Resume a suspended or blocked goal. */
2313
+ async autopilotResume(goalId) {
2314
+ return this.request("autopilot_resume", { goal_id: goalId });
2315
+ }
2316
+ /** List root goals only (jobs). Prefer job* for job control. */
2317
+ async autopilotListJobs() {
2318
+ return this.request("autopilot_list_jobs");
2319
+ }
2320
+ /** Get a root job with DAG snapshot. Prefer jobStatus / getJobDag. */
2321
+ async autopilotGetJob(jobId) {
2322
+ return this.request("autopilot_get_job", { job_id: jobId });
2323
+ }
2324
+ async cronAdd(text, priority = 0) {
2325
+ const params = { text };
2326
+ if (priority > 0) params.priority = priority;
2327
+ return this.request("cron_add", params);
2328
+ }
2329
+ async cronList(status = "") {
2330
+ const params = {};
2331
+ if (status) params.status = status;
2332
+ return this.request("cron_list", params);
2333
+ }
2334
+ };
2335
+
2336
+ // src/helpers.ts
2337
+ init_config();
2338
+ async function checkDaemonStatus(client, timeout) {
2339
+ return client.requestResponse("daemon_status", {}, "daemon_status", timeout ?? 5e3);
2340
+ }
2341
+ async function isDaemonLive(wsURL, timeout) {
2342
+ const { Client: Client2 } = await Promise.resolve().then(() => (init_client(), client_exports));
2343
+ const t = timeout ?? 5e3;
2344
+ const client = new Client2(wsURL, defaultConfig());
2345
+ try {
2346
+ await client.connect();
2347
+ } catch {
2348
+ return false;
2349
+ }
2350
+ try {
2351
+ await checkDaemonStatus(client, t);
2352
+ return true;
2353
+ } catch {
2354
+ return false;
2355
+ } finally {
2356
+ client.close();
2357
+ }
2358
+ }
2359
+ async function requestDaemonShutdown(client, timeout) {
2360
+ const resp = await client.requestResponse(
2361
+ "daemon_shutdown",
2362
+ {},
2363
+ "daemon_shutdown",
2364
+ timeout ?? 1e4
2365
+ );
2366
+ if (resp.status !== "acknowledged") {
2367
+ throw new Error(`shutdown not acknowledged: ${JSON.stringify(resp)}`);
2368
+ }
2369
+ }
2370
+ async function fetchSkillsCatalog(client, timeout) {
2371
+ const resp = await client.requestResponse("skills_list", {}, "skills_list", timeout ?? 15e3);
2372
+ const skillsRaw = resp.skills;
2373
+ if (!skillsRaw || !Array.isArray(skillsRaw)) return [];
2374
+ return skillsRaw.filter((s) => typeof s === "object" && s !== null);
2375
+ }
2376
+ async function fetchConfigSection(client, section, timeout) {
2377
+ const resp = await client.requestResponse(
2378
+ "config_get",
2379
+ { section },
2380
+ "config_get",
2381
+ timeout ?? 5e3
2382
+ );
2383
+ const sec = resp[section];
2384
+ if (sec && typeof sec === "object") {
2385
+ return sec;
2386
+ }
2387
+ return resp;
2388
+ }
2389
+ async function requestDaemonConfigReload(client, timeout) {
2390
+ return client.requestResponse("config_reload", {}, "config_reload", timeout ?? 15e3);
2391
+ }
2392
+ async function fetchLoopHistory(client, loopID, timeout) {
2393
+ return client.requestResponse(
2394
+ "loop_history_fetch",
2395
+ { loop_id: loopID },
2396
+ "loop_history_fetch",
2397
+ timeout ?? 15e3
2398
+ );
2399
+ }
2400
+ async function authenticate(client, accessKey, secretKey, timeout) {
2401
+ return client.requestResponse(
2402
+ "auth",
2403
+ { access_key: accessKey, secret_key: secretKey },
2404
+ "auth",
2405
+ timeout ?? 15e3
2406
+ );
2407
+ }
2408
+ async function refreshAuthToken(client, refreshToken, timeout) {
2409
+ return client.requestResponse(
2410
+ "auth_refresh",
2411
+ { refresh_token: refreshToken },
2412
+ "auth_refresh",
2413
+ timeout ?? 15e3
2414
+ );
2415
+ }
2416
+ async function fetchLoopCards(client, loopID, timeout) {
2417
+ return client.fetchLoopCards(loopID, timeout);
2418
+ }
2419
+ async function fetchLoopMessages(client, loopID, opts) {
2420
+ return client.getLoopMessages(
2421
+ loopID,
2422
+ opts?.limit,
2423
+ opts?.offset,
2424
+ opts?.includeEvents,
2425
+ opts?.timeout
2426
+ );
2427
+ }
2428
+ async function connectedWebsocket(wsUrl, fn, timeoutMs = 3e4) {
2429
+ const { Client: Client2 } = await Promise.resolve().then(() => (init_client(), client_exports));
2430
+ const client = new Client2(wsUrl, defaultConfig());
2431
+ const deadline = Date.now() + timeoutMs;
2432
+ try {
2433
+ await client.connect();
2434
+ while (!client.isConnected() && Date.now() < deadline) {
2435
+ await new Promise((r) => setTimeout(r, 25));
2436
+ }
2437
+ if (!client.isConnected()) {
2438
+ throw new Error("Timed out waiting for daemon handshake");
2439
+ }
2440
+ return await fn(client);
2441
+ } finally {
2442
+ client.close();
2443
+ }
2444
+ }
2445
+ async function protocol1Rpc(wsUrl, method, params = null, opts = {}) {
2446
+ const mode = opts.mode ?? "request";
2447
+ const timeoutMs = opts.timeoutMs ?? 3e4;
2448
+ try {
2449
+ return await connectedWebsocket(
2450
+ wsUrl,
2451
+ async (client) => {
2452
+ if (mode === "notify") {
2453
+ await client.notify(method, params ?? {});
2454
+ return {};
2455
+ }
2456
+ if (mode === "subscribe") {
2457
+ const subId = await client.subscribe(
2458
+ method,
2459
+ params ?? {},
2460
+ timeoutMs
2461
+ );
2462
+ return { subscription_id: subId };
2463
+ }
2464
+ const result = await client.requestResponse(
2465
+ method,
2466
+ params ?? {},
2467
+ method,
2468
+ timeoutMs
2469
+ );
2470
+ return result && typeof result === "object" ? result : { result };
2471
+ },
2472
+ timeoutMs
2473
+ );
2474
+ } catch (exc) {
2475
+ const msg = exc instanceof Error ? exc.message : String(exc);
2476
+ if (msg.toLowerCase().includes("timed out") || msg.toLowerCase().includes("timeout")) {
2477
+ return { error: "Timed out waiting for daemon response" };
2478
+ }
2479
+ if (msg.toLowerCase().includes("connect") || msg.toLowerCase().includes("dial")) {
2480
+ return { error: `Connection error: ${msg}` };
2481
+ }
2482
+ return { error: msg };
2483
+ }
2484
+ }
2485
+
2486
+ // src/index.ts
2487
+ init_stream_terminal();
2488
+
1885
2489
  // src/appkit/broadcaster.ts
1886
2490
  var SUBSCRIBER_QUEUE_CAP = 100;
1887
2491
  var SSEBroadcaster = class {
@@ -1985,6 +2589,7 @@ var SSEBroadcaster = class {
1985
2589
 
1986
2590
  // src/appkit/classifier.ts
1987
2591
  init_errors();
2592
+ init_events();
1988
2593
 
1989
2594
  // src/appkit/thinking_step.ts
1990
2595
  var MAX_THINKING_STEP_RUNES = 280;
@@ -2104,6 +2709,7 @@ var EventClassifier = class {
2104
2709
  deliverablePhases;
2105
2710
  minDeliverableRunes;
2106
2711
  thinkingStepEvents;
2712
+ treatStatusIdleAsComplete;
2107
2713
  constructor(cfg) {
2108
2714
  if (!cfg.deliverablePhases) {
2109
2715
  throw new Error("appkit: ClassifierConfig.deliverablePhases must not be nil");
@@ -2111,6 +2717,7 @@ var EventClassifier = class {
2111
2717
  this.deliverablePhases = cfg.deliverablePhases;
2112
2718
  this.minDeliverableRunes = cfg.minDeliverableRunes && cfg.minDeliverableRunes > 0 ? cfg.minDeliverableRunes : 8;
2113
2719
  this.thinkingStepEvents = cfg.thinkingStepEvents;
2720
+ this.treatStatusIdleAsComplete = Boolean(cfg.treatStatusIdleAsComplete);
2114
2721
  }
2115
2722
  /**
2116
2723
  * Inspects one decoded event and returns its outcome. `accumulated` is the
@@ -2127,6 +2734,13 @@ var EventClassifier = class {
2127
2734
  */
2128
2735
  isDeliverableCompletionEvent(eventType) {
2129
2736
  if (!eventType) return false;
2737
+ switch (eventType) {
2738
+ case "status.idle":
2739
+ case "idle_timeout":
2740
+ case "query_timeout":
2741
+ case "stream_closed":
2742
+ return true;
2743
+ }
2130
2744
  if (eventType === EventFinalReport) return true;
2131
2745
  if (eventType.startsWith("soothe.protocol.message.")) {
2132
2746
  const phase = eventType.slice("soothe.protocol.message.".length);
@@ -2162,16 +2776,22 @@ var EventClassifier = class {
2162
2776
  return ["", false];
2163
2777
  }
2164
2778
  /** The event→outcome mapper, ported from triarch's ProcessChatEvent. */
2165
- processChatEvent(msg, _accumulated) {
2779
+ processChatEvent(msg, accumulated) {
2166
2780
  if (!msg || typeof msg !== "object") {
2167
2781
  return { terminal: 0 /* Continue */ };
2168
2782
  }
2169
2783
  const m = msg;
2170
2784
  const typ = m.type;
2171
2785
  if (typ === "next") {
2172
- return this.classifyNextEnvelope(m);
2786
+ return this.classifyNextEnvelope(m, accumulated);
2173
2787
  }
2174
- if (typ === "response" || typ === "complete" || typ === "receipt_response" || typ === "connection_ack" || typ === "status") {
2788
+ if (typ === "response" || typ === "complete" || typ === "receipt_response" || typ === "connection_ack") {
2789
+ return { terminal: 0 /* Continue */ };
2790
+ }
2791
+ if (typ === "status") {
2792
+ if (this.treatStatusIdleAsComplete && String(m.state ?? "").trim().toLowerCase() === "idle" && this.isSubstantiveAssistantReply(accumulated)) {
2793
+ return this.deliverableResult(accumulated.trim(), "status.idle");
2794
+ }
2175
2795
  return { terminal: 0 /* Continue */ };
2176
2796
  }
2177
2797
  if (typ === "error") {
@@ -2191,10 +2811,14 @@ var EventClassifier = class {
2191
2811
  return { terminal: 0 /* Continue */ };
2192
2812
  }
2193
2813
  /** Classifies a `next` envelope by projecting its payload. */
2194
- classifyNextEnvelope(env) {
2814
+ classifyNextEnvelope(env, accumulated) {
2195
2815
  const payload = env.payload ?? {};
2196
2816
  const innerData = payload.data;
2197
2817
  if (innerData && typeof innerData === "object") {
2818
+ const innerType = innerData.type ?? "";
2819
+ if (innerType === "status") {
2820
+ return this.processChatEvent(innerData, accumulated);
2821
+ }
2198
2822
  const innerMode = innerData.mode ?? "";
2199
2823
  if (innerMode) {
2200
2824
  return this.classifyEventPayload(
@@ -2205,6 +2829,11 @@ var EventClassifier = class {
2205
2829
  }
2206
2830
  }
2207
2831
  const mode = payload.mode ?? "";
2832
+ if (mode === "status" || mode === "") {
2833
+ if (typeof payload.state === "string" || innerData && "state" in (innerData ?? {})) {
2834
+ return this.processChatEvent({ type: "status", ...innerData ?? payload }, accumulated);
2835
+ }
2836
+ }
2208
2837
  if (mode) {
2209
2838
  return this.classifyEventPayload(payload.namespace ?? null, mode, payload.data);
2210
2839
  }
@@ -2622,10 +3251,15 @@ var ConnectionPool = class {
2622
3251
  if (existing.isDisconnected() || !existing.isConnected()) {
2623
3252
  await this.release(sessionID);
2624
3253
  } else {
2625
- existing.lastUsed = Date.now();
2626
- await this.store.updateLastUsed(sessionID).catch(() => {
2627
- });
2628
- return existing;
3254
+ const idleTooLong = this.cfg.maxIdleTime > 0 && existing.lastUsed > 0 && Date.now() - existing.lastUsed > this.cfg.maxIdleTime;
3255
+ if (idleTooLong) {
3256
+ await this.release(sessionID);
3257
+ } else {
3258
+ existing.lastUsed = Date.now();
3259
+ await this.store.updateLastUsed(sessionID).catch(() => {
3260
+ });
3261
+ return existing;
3262
+ }
2629
3263
  }
2630
3264
  }
2631
3265
  const conn = this.pool.pop();
@@ -2728,6 +3362,88 @@ var ConnectionPool = class {
2728
3362
  }
2729
3363
  };
2730
3364
 
3365
+ // src/appkit/attachments.ts
3366
+ function compactDefaults(opts) {
3367
+ return {
3368
+ maxDim: opts?.maxDim && opts.maxDim > 0 ? opts.maxDim : 768,
3369
+ quality: opts?.jpegQuality && opts.jpegQuality > 0 ? opts.jpegQuality : 85
3370
+ };
3371
+ }
3372
+ var sharpLoader = null;
3373
+ async function loadSharp() {
3374
+ if (!sharpLoader) {
3375
+ sharpLoader = (async () => {
3376
+ try {
3377
+ const m = await Function('return import("sharp")')();
3378
+ return m;
3379
+ } catch {
3380
+ return null;
3381
+ }
3382
+ })();
3383
+ }
3384
+ return sharpLoader;
3385
+ }
3386
+ async function compactImageAttachment(mimeType, dataB64, opts) {
3387
+ if (!dataB64 || !mimeType.startsWith("image/")) {
3388
+ return [mimeType, dataB64];
3389
+ }
3390
+ let raw;
3391
+ try {
3392
+ raw = Buffer.from(dataB64, "base64");
3393
+ } catch {
3394
+ return [mimeType, dataB64];
3395
+ }
3396
+ if (raw.length === 0) return [mimeType, dataB64];
3397
+ const sharpMod = await loadSharp();
3398
+ if (!sharpMod) return [mimeType, dataB64];
3399
+ const { maxDim, quality } = compactDefaults(opts);
3400
+ try {
3401
+ const img = sharpMod.default(raw, { failOn: "none" });
3402
+ const meta = await img.metadata();
3403
+ const w = meta.width ?? 0;
3404
+ const h = meta.height ?? 0;
3405
+ if (w <= 0 || h <= 0 || w <= maxDim && h <= maxDim) {
3406
+ return [mimeType, dataB64];
3407
+ }
3408
+ let nw = w;
3409
+ let nh = h;
3410
+ if (w >= h) {
3411
+ if (w > maxDim) {
3412
+ nw = maxDim;
3413
+ nh = Math.max(1, Math.round(h * maxDim / w));
3414
+ }
3415
+ } else if (h > maxDim) {
3416
+ nh = maxDim;
3417
+ nw = Math.max(1, Math.round(w * maxDim / h));
3418
+ }
3419
+ const resized = img.resize(nw, nh, { fit: "fill" });
3420
+ if (mimeType === "image/png") {
3421
+ const buf2 = await resized.png().toBuffer();
3422
+ return [mimeType, buf2.toString("base64")];
3423
+ }
3424
+ const buf = await resized.jpeg({ quality }).toBuffer();
3425
+ return ["image/jpeg", buf.toString("base64")];
3426
+ } catch {
3427
+ return [mimeType, dataB64];
3428
+ }
3429
+ }
3430
+ async function compactAttachments(atts, opts) {
3431
+ if (!atts.length) return atts;
3432
+ const out = [];
3433
+ for (const att of atts) {
3434
+ const cp = { ...att };
3435
+ const mime = typeof cp.mime_type === "string" ? cp.mime_type : "";
3436
+ const data = typeof cp.data === "string" ? cp.data : "";
3437
+ if (mime && data) {
3438
+ const [outMime, outData] = await compactImageAttachment(mime, data, opts);
3439
+ cp.mime_type = outMime;
3440
+ cp.data = outData;
3441
+ }
3442
+ out.push(cp);
3443
+ }
3444
+ return out;
3445
+ }
3446
+
2731
3447
  // src/appkit/turn_runner.ts
2732
3448
  init_intent_hints();
2733
3449
  var ErrQueryTimeout = class extends Error {
@@ -2736,6 +3452,19 @@ var ErrQueryTimeout = class extends Error {
2736
3452
  this.name = "ErrQueryTimeout";
2737
3453
  }
2738
3454
  };
3455
+ var ErrIdleTimeout = class extends Error {
3456
+ constructor() {
3457
+ super("appkit: idle timeout");
3458
+ this.name = "ErrIdleTimeout";
3459
+ }
3460
+ };
3461
+ var TimeoutPolicy = /* @__PURE__ */ ((TimeoutPolicy2) => {
3462
+ TimeoutPolicy2[TimeoutPolicy2["Fail"] = 0] = "Fail";
3463
+ TimeoutPolicy2[TimeoutPolicy2["SoftComplete"] = 1] = "SoftComplete";
3464
+ return TimeoutPolicy2;
3465
+ })(TimeoutPolicy || {});
3466
+ var StreamCloseFail = 0 /* Fail */;
3467
+ var StreamCloseSoftComplete = 1 /* SoftComplete */;
2739
3468
  function inputMessageForLoop(text, loopID, attachments, opts) {
2740
3469
  const msg = { type: "loop_input", content: text };
2741
3470
  if (loopID) msg.loop_id = loopID;
@@ -2758,6 +3487,13 @@ function inputMessageForLoop(text, loopID, attachments, opts) {
2758
3487
  }
2759
3488
  return msg;
2760
3489
  }
3490
+ function idleTimeoutForTurn(cfg, hasAttachments) {
3491
+ const idle = cfg.idleTimeout ?? 0;
3492
+ if (idle <= 0) return 0;
3493
+ const floor = cfg.minIdleTimeoutWithAttachments ?? 0;
3494
+ if (hasAttachments && floor > 0 && idle < floor) return floor;
3495
+ return idle;
3496
+ }
2761
3497
  var TurnRunner = class {
2762
3498
  pool;
2763
3499
  gate;
@@ -2768,39 +3504,29 @@ var TurnRunner = class {
2768
3504
  buildInput = inputMessageForLoop;
2769
3505
  onComplete = null;
2770
3506
  onError = null;
2771
- /**
2772
- * Constructs a TurnRunner. pool, gate, classifier, and store are required;
2773
- * broadcaster may be null.
2774
- */
2775
3507
  constructor(pool, gate, classifier, store, broadcaster, cfg) {
2776
3508
  this.pool = pool;
2777
3509
  this.gate = gate;
2778
3510
  this.classifier = classifier;
2779
3511
  this.store = store;
2780
3512
  this.broadcaster = broadcaster;
2781
- this.cfg = { queryTimeout: cfg.queryTimeout > 0 ? cfg.queryTimeout : 30 * 60 * 1e3 };
3513
+ this.cfg = {
3514
+ ...cfg,
3515
+ queryTimeout: cfg.queryTimeout > 0 ? cfg.queryTimeout : 30 * 60 * 1e3
3516
+ };
2782
3517
  }
2783
- /** Overrides the loop_input payload builder. */
2784
3518
  withInputBuilder(f) {
2785
3519
  if (f) this.buildInput = f;
2786
3520
  return this;
2787
3521
  }
2788
- /** Sets a completion hook (runs inline on success). */
2789
3522
  withOnComplete(f) {
2790
3523
  this.onComplete = f;
2791
3524
  return this;
2792
3525
  }
2793
- /** Sets an error hook (runs inline on failure). */
2794
3526
  withOnError(f) {
2795
3527
  this.onError = f;
2796
3528
  return this;
2797
3529
  }
2798
- /**
2799
- * Runs one query turn. The response is broadcast via the SSE broadcaster and
2800
- * persisted via the SessionStore; it is not returned to the caller (SSE
2801
- * subscribers receive it). Resolves on success; rejects on failure
2802
- * (ErrQueryTimeout, AbortError, or a daemon/processing error).
2803
- */
2804
3530
  async execute(sessionID, message, userID, workspaceID, attachments, opts, signal) {
2805
3531
  let conn;
2806
3532
  try {
@@ -2828,13 +3554,19 @@ var TurnRunner = class {
2828
3554
  this.onError?.(sessionID, loopID, err);
2829
3555
  throw err;
2830
3556
  }
3557
+ let idleTimer = null;
3558
+ const clearIdle = () => {
3559
+ if (idleTimer) {
3560
+ clearTimeout(idleTimer);
3561
+ idleTimer = null;
3562
+ }
3563
+ };
2831
3564
  try {
2832
- const inputMsg = this.buildInput(
2833
- message,
2834
- loopID,
2835
- attachments ?? void 0,
2836
- opts ?? void 0
2837
- );
3565
+ let atts = attachments ?? void 0;
3566
+ if (this.cfg.compactAttachmentsBeforeSend && atts && atts.length > 0) {
3567
+ atts = await compactAttachments(atts, this.cfg.compactImageOpts);
3568
+ }
3569
+ const inputMsg = this.buildInput(message, loopID, atts, opts ?? void 0);
2838
3570
  try {
2839
3571
  await conn.client.sendMessage(inputMsg);
2840
3572
  } catch (err) {
@@ -2853,6 +3585,23 @@ var TurnRunner = class {
2853
3585
  }
2854
3586
  let assistantContent = "";
2855
3587
  const startedAt = Date.now();
3588
+ const idleForTurn = idleTimeoutForTurn(this.cfg, (attachments?.length ?? 0) > 0);
3589
+ let idleReject = null;
3590
+ const armIdle = () => {
3591
+ clearIdle();
3592
+ idleReject = null;
3593
+ if (idleForTurn <= 0) {
3594
+ return new Promise(() => {
3595
+ });
3596
+ }
3597
+ return new Promise((resolve) => {
3598
+ idleReject = () => resolve("idle");
3599
+ idleTimer = setTimeout(() => {
3600
+ idleReject?.();
3601
+ }, idleForTurn);
3602
+ });
3603
+ };
3604
+ let idleRace = armIdle();
2856
3605
  const abortRace = new Promise((resolve) => {
2857
3606
  const onTimeout = () => resolve("timeout");
2858
3607
  timeoutController.signal.addEventListener("abort", onTimeout, { once: true });
@@ -2866,34 +3615,71 @@ var TurnRunner = class {
2866
3615
  const next = iterator.next();
2867
3616
  const raced = await Promise.race([
2868
3617
  next.then((res2) => ({ tag: "msg", res: res2 })),
2869
- abortRace.then((tag) => ({ tag }))
3618
+ abortRace.then((tag) => ({ tag })),
3619
+ idleRace.then((tag) => ({ tag }))
2870
3620
  ]);
2871
3621
  if ("tag" in raced && raced.tag !== "msg") {
2872
3622
  if (raced.tag === "caller" || signal?.aborted) {
3623
+ clearIdle();
2873
3624
  const err = new Error("aborted");
2874
3625
  await this.persistFailed(sessionID, loopID, err);
2875
3626
  this.broadcastError(sessionID, err);
2876
3627
  this.onError?.(sessionID, loopID, err);
2877
3628
  throw err;
2878
3629
  }
3630
+ if (raced.tag === "idle") {
3631
+ clearIdle();
3632
+ await this.sendLoopCancel(new AbortController().signal, conn, loopID).catch(() => {
3633
+ });
3634
+ await this.finishTimeout(
3635
+ sessionID,
3636
+ loopID,
3637
+ assistantContent,
3638
+ startedAt,
3639
+ new ErrIdleTimeout(),
3640
+ "idle_timeout",
3641
+ this.cfg.onIdleTimeout ?? 0 /* Fail */
3642
+ );
3643
+ return;
3644
+ }
3645
+ clearIdle();
2879
3646
  await this.sendLoopCancel(new AbortController().signal, conn, loopID).catch(() => {
2880
3647
  });
2881
- await this.persistFailed(sessionID, loopID, new ErrQueryTimeout());
2882
- this.broadcastError(sessionID, new ErrQueryTimeout());
2883
- this.onError?.(sessionID, loopID, new ErrQueryTimeout());
2884
- throw new ErrQueryTimeout();
3648
+ await this.finishTimeout(
3649
+ sessionID,
3650
+ loopID,
3651
+ assistantContent,
3652
+ startedAt,
3653
+ new ErrQueryTimeout(),
3654
+ "query_timeout",
3655
+ this.cfg.onQueryTimeout ?? 0 /* Fail */
3656
+ );
3657
+ return;
2885
3658
  }
2886
3659
  const res = raced.res;
2887
3660
  if (res.done) {
3661
+ clearIdle();
3662
+ if ((this.cfg.onStreamClose ?? 0 /* Fail */) === 1 /* SoftComplete */ && assistantContent.trim() !== "") {
3663
+ await this.completeTurn(
3664
+ sessionID,
3665
+ loopID,
3666
+ assistantContent,
3667
+ startedAt,
3668
+ "stream_closed"
3669
+ );
3670
+ return;
3671
+ }
2888
3672
  const err = new Error("event stream closed");
2889
3673
  await this.persistFailed(sessionID, loopID, err);
2890
3674
  this.broadcastError(sessionID, err);
2891
3675
  this.onError?.(sessionID, loopID, err);
2892
3676
  throw err;
2893
3677
  }
3678
+ idleRace = armIdle();
2894
3679
  const msg = res.value;
2895
3680
  const eventResult = this.classifier.classify(msg, assistantContent);
2896
3681
  if (eventResult.err && eventResult.terminal === 2 /* FailedComplete */) {
3682
+ clearIdle();
2897
3683
  await this.persistFailed(sessionID, loopID, eventResult.err);
2898
3684
  this.broadcastError(sessionID, eventResult.err);
2899
3685
  this.onError?.(sessionID, loopID, eventResult.err);
@@ -2913,25 +3699,39 @@ var TurnRunner = class {
2913
3699
  assistantContent
2914
3700
  );
2915
3701
  if (deliverable) {
2916
- const elapsedMs = Date.now() - startedAt;
2917
- await this.persistResponse(
3702
+ clearIdle();
3703
+ await this.completeTurn(
2918
3704
  sessionID,
2919
3705
  loopID,
2920
3706
  final,
2921
3707
  startedAt,
2922
3708
  eventResult.completionEvent ?? ""
2923
3709
  );
2924
- this.broadcastComplete(sessionID, final);
2925
- this.onComplete?.(sessionID, loopID, final, eventResult.completionEvent ?? "", elapsedMs);
2926
3710
  return;
2927
3711
  }
2928
3712
  }
2929
3713
  } finally {
3714
+ clearIdle();
2930
3715
  clearTimeout(timer);
2931
3716
  this.gate.release(sessionID);
2932
3717
  }
2933
3718
  }
2934
- /** Asks the daemon to cooperatively stop the loop runner on a detached signal. */
3719
+ async finishTimeout(sessionID, loopID, content, startedAt, failErr, completionEvent, policy) {
3720
+ if (policy === 1 /* SoftComplete */ && content.trim() !== "") {
3721
+ await this.completeTurn(sessionID, loopID, content, startedAt, completionEvent);
3722
+ return;
3723
+ }
3724
+ await this.persistFailed(sessionID, loopID, failErr);
3725
+ this.broadcastError(sessionID, failErr);
3726
+ this.onError?.(sessionID, loopID, failErr);
3727
+ throw failErr;
3728
+ }
3729
+ async completeTurn(sessionID, loopID, final, startedAt, completionEvent) {
3730
+ const elapsedMs = Date.now() - startedAt;
3731
+ await this.persistResponse(sessionID, loopID, final, startedAt, completionEvent);
3732
+ this.broadcastComplete(sessionID, final);
3733
+ this.onComplete?.(sessionID, loopID, final, completionEvent, elapsedMs);
3734
+ }
2935
3735
  async sendLoopCancel(_signal, conn, loopID) {
2936
3736
  const lid = (loopID ?? "").trim();
2937
3737
  if (!conn || !lid) return;
@@ -2974,18 +3774,476 @@ var TurnRunner = class {
2974
3774
  this.broadcaster?.broadcast(sessionID, { type: "query_error", data: err.message });
2975
3775
  }
2976
3776
  };
3777
+
3778
+ // src/appkit/daemon_session.ts
3779
+ init_client();
3780
+ init_config();
3781
+ init_errors();
3782
+ init_stream_terminal();
3783
+
3784
+ // src/appkit/chunk_filter.ts
3785
+ var MSG_PAIR_LEN = 2;
3786
+ function updatesChunkIsNoop(data) {
3787
+ if (!data || typeof data !== "object") return true;
3788
+ return !("__interrupt__" in data);
3789
+ }
3790
+ function wireBody(msg) {
3791
+ for (const key of ["kwargs", "data"]) {
3792
+ const nested = msg[key];
3793
+ if (nested && typeof nested === "object") return nested;
3794
+ }
3795
+ return msg;
3796
+ }
3797
+ function dictHasToolInvocation(msg) {
3798
+ const body = wireBody(msg);
3799
+ if (body.tool_calls || body.tool_call_chunks) return true;
3800
+ for (const key of ["content", "content_blocks"]) {
3801
+ const raw = body[key];
3802
+ if (Array.isArray(raw)) {
3803
+ for (const item of raw) {
3804
+ if (item && typeof item === "object" && ["tool_call", "tool_call_chunk", "tool_use"].includes(
3805
+ String(item.type ?? "")
3806
+ )) {
3807
+ return true;
3808
+ }
3809
+ }
3810
+ }
3811
+ }
3812
+ return false;
3813
+ }
3814
+ function plainText(msg) {
3815
+ const body = wireBody(msg);
3816
+ const content = body.content ?? msg.content;
3817
+ if (typeof content === "string") return content;
3818
+ if (Array.isArray(content)) {
3819
+ const parts = [];
3820
+ for (const block of content) {
3821
+ if (typeof block === "string") parts.push(block);
3822
+ else if (block && typeof block === "object") {
3823
+ const text = block.text;
3824
+ if (typeof text === "string") parts.push(text);
3825
+ }
3826
+ }
3827
+ return parts.join("");
3828
+ }
3829
+ return "";
3830
+ }
3831
+ function messageChunkIsNonActionable(data) {
3832
+ if (!Array.isArray(data) || data.length !== MSG_PAIR_LEN) return false;
3833
+ const msg = data[0];
3834
+ if (msg === null || msg === void 0) return true;
3835
+ if (!msg || typeof msg !== "object") return false;
3836
+ const m = msg;
3837
+ const body = wireBody(m);
3838
+ const raw = String(body.type ?? m.type ?? "");
3839
+ if (raw === "tool" || raw === "ToolMessage" || raw.endsWith("ToolMessage")) return false;
3840
+ if (dictHasToolInvocation(m)) return false;
3841
+ if (body.phase || m.phase) return false;
3842
+ return !plainText(m).trim();
3843
+ }
3844
+ function shouldDropStreamChunkEarly(_namespace, mode, data) {
3845
+ if (mode === "updates") return updatesChunkIsNoop(data);
3846
+ if (mode === "messages") return messageChunkIsNonActionable(data);
3847
+ return false;
3848
+ }
3849
+
3850
+ // src/appkit/events.ts
3851
+ function unwrapNext(event) {
3852
+ if (!event || typeof event !== "object") return event;
3853
+ if (event.type !== "next") return event;
3854
+ const payload = event.payload;
3855
+ if (!payload || typeof payload !== "object") return event;
3856
+ const data = payload.data;
3857
+ return data && typeof data === "object" ? data : event;
3858
+ }
3859
+
3860
+ // src/appkit/observability.ts
3861
+ var TurnEventStats = class {
3862
+ total = 0;
3863
+ messages = 0;
3864
+ updates = 0;
3865
+ custom = 0;
3866
+ skipped = 0;
3867
+ filteredEarly = 0;
3868
+ toolCalls = 0;
3869
+ toolResults = 0;
3870
+ textChunks = 0;
3871
+ heartbeatsDropped = 0;
3872
+ postIdleDrained = 0;
3873
+ inboundDropped = 0;
3874
+ };
3875
+
3876
+ // src/appkit/daemon_session.ts
3877
+ var DEFAULT_POST_IDLE_DRAIN_MS = 500;
3878
+ var DaemonSession = class {
3879
+ wsUrl;
3880
+ workspace;
3881
+ streamDelivery;
3882
+ client;
3883
+ rpcClient;
3884
+ loopId = null;
3885
+ readBusy = false;
3886
+ rpcBusy = false;
3887
+ rpcConnected = false;
3888
+ streaming = false;
3889
+ postIdleDrainDeadlineMs;
3890
+ closed = false;
3891
+ earlyDropFn;
3892
+ statsFactory;
3893
+ config;
3894
+ turnEventStats;
3895
+ lastTurnEndState = null;
3896
+ lastTurnCancellationSeen = false;
3897
+ lastTurnErrorMessage = null;
3898
+ constructor(wsUrl, opts = {}) {
3899
+ this.wsUrl = wsUrl;
3900
+ this.workspace = opts.workspace;
3901
+ this.streamDelivery = opts.streamDelivery ?? "adaptive";
3902
+ this.config = opts.config ?? defaultConfig();
3903
+ this.client = new Client(wsUrl, this.config);
3904
+ this.rpcClient = new Client(wsUrl, this.config);
3905
+ this.postIdleDrainDeadlineMs = opts.postIdleDrainDeadlineMs && opts.postIdleDrainDeadlineMs > 0 ? opts.postIdleDrainDeadlineMs : DEFAULT_POST_IDLE_DRAIN_MS;
3906
+ this.earlyDropFn = opts.earlyDropFn ?? shouldDropStreamChunkEarly;
3907
+ this.statsFactory = opts.statsFactory ?? (() => new TurnEventStats());
3908
+ this.turnEventStats = this.statsFactory();
3909
+ }
3910
+ get streamClient() {
3911
+ return this.client;
3912
+ }
3913
+ get rpcSideClient() {
3914
+ return this.rpcClient;
3915
+ }
3916
+ get activeLoopId() {
3917
+ return this.loopId;
3918
+ }
3919
+ resolveStreamDeliveryMode() {
3920
+ const delivery = this.streamDelivery;
3921
+ if (typeof delivery === "function") return String(delivery() || "adaptive");
3922
+ return String(delivery || "adaptive");
3923
+ }
3924
+ get streamDeliveryMode() {
3925
+ return this.resolveStreamDeliveryMode();
3926
+ }
3927
+ shouldDrop(namespace, mode, data) {
3928
+ return Boolean(this.earlyDropFn(namespace, mode, data));
3929
+ }
3930
+ async connect(resumeLoopId) {
3931
+ await connectWithRetries(this.client);
3932
+ return this.bootstrapLoop(resumeLoopId ?? null);
3933
+ }
3934
+ async bootstrapLoop(resumeLoopId) {
3935
+ const loopNew = this.workspace ? { client_workspace: this.workspace, workspace: this.workspace } : void 0;
3936
+ const loopId = await bootstrapLoopSession(this.client, resumeLoopId, this.config, loopNew);
3937
+ this.loopId = loopId;
3938
+ return { type: "status", loop_id: loopId, state: "ready" };
3939
+ }
3940
+ async newLoop() {
3941
+ return this.bootstrapLoop(null);
3942
+ }
3943
+ async switchLoop(loopId) {
3944
+ return this.bootstrapLoop(loopId);
3945
+ }
3946
+ async ensureConnected() {
3947
+ if (this.client.isConnected() && !this.client.isDisconnected()) return;
3948
+ let resumeLoopId = this.loopId;
3949
+ if (this.rpcConnected) {
3950
+ this.rpcClient.close();
3951
+ this.rpcConnected = false;
3952
+ }
3953
+ try {
3954
+ await this.client.reconnect();
3955
+ } catch {
3956
+ this.client.close();
3957
+ await connectWithRetries(this.client);
3958
+ }
3959
+ if (resumeLoopId) {
3960
+ try {
3961
+ await this.client.reattachAndProbe(resumeLoopId);
3962
+ this.loopId = resumeLoopId;
3963
+ return;
3964
+ } catch (err) {
3965
+ if (!(err instanceof StaleLoopError)) throw err;
3966
+ resumeLoopId = null;
3967
+ }
3968
+ }
3969
+ await this.bootstrapLoop(resumeLoopId);
3970
+ }
3971
+ async close() {
3972
+ if (this.closed) return;
3973
+ this.closed = true;
3974
+ this.client.close();
3975
+ this.rpcClient.close();
3976
+ this.rpcConnected = false;
3977
+ }
3978
+ async detach() {
3979
+ if (!this.client.isConnected()) return;
3980
+ try {
3981
+ await this.client.notify("disconnect", {});
3982
+ } catch {
3983
+ }
3984
+ }
3985
+ async sendTurn(text, options) {
3986
+ if (!this.loopId) throw new Error("No active loop session");
3987
+ await this.client.sendInput(text, {
3988
+ loopID: this.loopId,
3989
+ autonomous: options?.autonomous,
3990
+ maxIterations: options?.maxIterations,
3991
+ subagent: options?.preferredSubagent,
3992
+ model: options?.model,
3993
+ modelParams: options?.modelParams,
3994
+ attachments: options?.attachments,
3995
+ clarificationMode: options?.clarificationMode,
3996
+ clarificationAnswer: options?.clarificationAnswer,
3997
+ intentHint: options?.intentHint
3998
+ });
3999
+ }
4000
+ async cancelActiveTurn() {
4001
+ await this.client.notify("slash_command", { cmd: "/cancel" });
4002
+ }
4003
+ async *drainStreamEventsAfterIdle(expectedLoopId) {
4004
+ const deadline = Date.now() + this.postIdleDrainDeadlineMs;
4005
+ let exp = expectedLoopId;
4006
+ while (Date.now() < deadline) {
4007
+ const event = await this.client.readEventWithTimeout(250);
4008
+ if (!event) break;
4009
+ let frame = event;
4010
+ let eventType = String(frame.type ?? "");
4011
+ if (eventType === "next") {
4012
+ frame = unwrapNext(frame) ?? frame;
4013
+ eventType = String(frame.type ?? "");
4014
+ }
4015
+ const eventLoopId = frame.loop_id;
4016
+ if (exp && typeof eventLoopId === "string" && eventLoopId && eventLoopId !== exp) {
4017
+ continue;
4018
+ }
4019
+ if (eventType === "error") {
4020
+ const errObj = frame.error ?? {};
4021
+ throw new Error(String(errObj.message || frame.message || "daemon error"));
4022
+ }
4023
+ if (eventType === "status") {
4024
+ const loopEv = frame.loop_id;
4025
+ if (typeof loopEv === "string" && loopEv) {
4026
+ this.loopId = loopEv;
4027
+ exp = loopEv;
4028
+ }
4029
+ continue;
4030
+ }
4031
+ if (eventType !== "event") continue;
4032
+ const data = frame.data;
4033
+ const namespace = Array.isArray(frame.namespace) ? frame.namespace : [];
4034
+ const mode = String(frame.mode ?? "");
4035
+ if (this.shouldDrop(namespace, mode, data)) {
4036
+ this.turnEventStats.filteredEarly += 1;
4037
+ continue;
4038
+ }
4039
+ this.turnEventStats.postIdleDrained += 1;
4040
+ yield [namespace, mode, data];
4041
+ }
4042
+ }
4043
+ async withRpcLock(fn) {
4044
+ while (this.rpcBusy) {
4045
+ await new Promise((r) => setTimeout(r, 5));
4046
+ }
4047
+ this.rpcBusy = true;
4048
+ try {
4049
+ return await fn();
4050
+ } finally {
4051
+ this.rpcBusy = false;
4052
+ }
4053
+ }
4054
+ async ensureRpcConnected() {
4055
+ if (this.rpcConnected && this.rpcClient.isConnected()) return;
4056
+ await connectWithRetries(this.rpcClient);
4057
+ this.rpcConnected = true;
4058
+ }
4059
+ async listLoops(_limit = 20) {
4060
+ return this.withRpcLock(async () => {
4061
+ await this.ensureRpcConnected();
4062
+ return this.rpcClient.listLoops(15e3);
4063
+ });
4064
+ }
4065
+ async fetchLoopCards(loopId) {
4066
+ const lid = String(loopId || "").trim();
4067
+ if (!lid) return { cards: [], seq: 0, contextTokens: 0, success: false };
4068
+ return this.withRpcLock(async () => {
4069
+ await this.ensureRpcConnected();
4070
+ try {
4071
+ const resp = await this.rpcClient.fetchLoopCards(lid, 3e4);
4072
+ const rawCards = resp.cards;
4073
+ return {
4074
+ cards: Array.isArray(rawCards) ? rawCards : [],
4075
+ seq: Number(resp.seq ?? 0),
4076
+ contextTokens: typeof resp.context_tokens === "number" && resp.context_tokens >= 0 ? resp.context_tokens : 0,
4077
+ success: true
4078
+ };
4079
+ } catch {
4080
+ return { cards: [], seq: 0, contextTokens: 0, success: false };
4081
+ }
4082
+ });
4083
+ }
4084
+ async fetchLoopHistory(loopId) {
4085
+ const lid = String(loopId || "").trim();
4086
+ if (!lid) {
4087
+ return { goals: [], liveCards: [], liveGoalIndex: null, contextTokens: 0, success: false };
4088
+ }
4089
+ return this.withRpcLock(async () => {
4090
+ await this.ensureRpcConnected();
4091
+ try {
4092
+ const resp = await this.rpcClient.fetchLoopHistory(lid, 3e4);
4093
+ const liveGoalIndex = resp.live_goal_index;
4094
+ return {
4095
+ goals: Array.isArray(resp.goals) ? resp.goals : [],
4096
+ liveCards: Array.isArray(resp.live_cards) ? resp.live_cards : [],
4097
+ liveGoalIndex: typeof liveGoalIndex === "number" ? liveGoalIndex : null,
4098
+ contextTokens: typeof resp.context_tokens === "number" && resp.context_tokens >= 0 ? resp.context_tokens : 0,
4099
+ success: Boolean(resp.success ?? true)
4100
+ };
4101
+ } catch {
4102
+ return {
4103
+ goals: [],
4104
+ liveCards: [],
4105
+ liveGoalIndex: null,
4106
+ contextTokens: 0,
4107
+ success: false
4108
+ };
4109
+ }
4110
+ });
4111
+ }
4112
+ async fetchConversationLog(loopId, opts = {}) {
4113
+ const lid = String(loopId || "").trim();
4114
+ if (!lid) return [];
4115
+ return this.withRpcLock(async () => {
4116
+ await this.ensureRpcConnected();
4117
+ const resp = await this.rpcClient.getLoopMessages(
4118
+ lid,
4119
+ opts.limit ?? 100,
4120
+ opts.offset ?? 0,
4121
+ opts.includeEvents ?? false
4122
+ );
4123
+ const raw = resp.messages;
4124
+ if (!Array.isArray(raw)) return [];
4125
+ return raw.filter((m) => !!m && typeof m === "object");
4126
+ });
4127
+ }
4128
+ async *iterTurnChunks(opts = {}) {
4129
+ this.turnEventStats = this.statsFactory();
4130
+ this.lastTurnEndState = null;
4131
+ this.lastTurnCancellationSeen = false;
4132
+ this.lastTurnErrorMessage = null;
4133
+ let queryStarted = false;
4134
+ let expectedLoopId = this.loopId;
4135
+ let streamPayloadSeen = false;
4136
+ let turnProgressSeen = false;
4137
+ this.streaming = true;
4138
+ const absoluteDeadline = opts.maxWaitMs !== void 0 && opts.maxWaitMs > 0 ? Date.now() + opts.maxWaitMs : null;
4139
+ this.client.peelStalePendingControlEvents();
4140
+ while (this.readBusy) {
4141
+ await new Promise((r) => setTimeout(r, 5));
4142
+ }
4143
+ this.readBusy = true;
4144
+ try {
4145
+ while (true) {
4146
+ if (absoluteDeadline !== null && Date.now() >= absoluteDeadline) {
4147
+ throw new Error(
4148
+ `Turn timed out after ${opts.maxWaitMs}ms (loop=${expectedLoopId ?? "?"})`
4149
+ );
4150
+ }
4151
+ const event = await this.client.readEvent();
4152
+ if (!event) {
4153
+ if (queryStarted && !this.client.isConnectionAlive()) {
4154
+ this.lastTurnEndState = "connection_lost";
4155
+ throw new Error("Daemon connection lost");
4156
+ }
4157
+ break;
4158
+ }
4159
+ let frame = event;
4160
+ let eventType = String(frame.type ?? "");
4161
+ if (eventType === "next") {
4162
+ frame = unwrapNext(frame) ?? frame;
4163
+ eventType = String(frame.type ?? "");
4164
+ }
4165
+ const eventLoopId = frame.loop_id;
4166
+ if (expectedLoopId && typeof eventLoopId === "string" && eventLoopId && eventLoopId !== expectedLoopId) {
4167
+ continue;
4168
+ }
4169
+ if (eventType === "error") {
4170
+ const errObj = frame.error ?? {};
4171
+ throw new Error(String(errObj.message || frame.message || "daemon error"));
4172
+ }
4173
+ if (eventType === "status") {
4174
+ const loopEv = frame.loop_id;
4175
+ if (typeof loopEv === "string" && loopEv) {
4176
+ this.loopId = loopEv;
4177
+ expectedLoopId = loopEv;
4178
+ }
4179
+ const state = String(frame.state ?? "");
4180
+ if (state === "running") {
4181
+ queryStarted = true;
4182
+ } else if (queryStarted && state === "stopped") {
4183
+ this.lastTurnEndState = state;
4184
+ yield* this.drainStreamEventsAfterIdle(expectedLoopId);
4185
+ break;
4186
+ } else if (queryStarted && state === "idle") {
4187
+ if (!streamPayloadSeen && !this.lastTurnCancellationSeen) continue;
4188
+ this.lastTurnEndState = state;
4189
+ yield* this.drainStreamEventsAfterIdle(expectedLoopId);
4190
+ break;
4191
+ }
4192
+ continue;
4193
+ }
4194
+ if (eventType === "command_response") {
4195
+ const content = String(frame.content ?? "");
4196
+ if (content.includes("Cancellation requested")) {
4197
+ this.lastTurnCancellationSeen = true;
4198
+ }
4199
+ continue;
4200
+ }
4201
+ if (eventType !== "event") continue;
4202
+ const data = frame.data;
4203
+ const namespace = Array.isArray(frame.namespace) ? frame.namespace : [];
4204
+ const mode = String(frame.mode ?? "");
4205
+ if (this.shouldDrop(namespace, mode, data)) {
4206
+ this.turnEventStats.filteredEarly += 1;
4207
+ continue;
4208
+ }
4209
+ if (mode === "custom" && isTurnEndCustomData(data)) {
4210
+ if (!queryStarted || !turnProgressSeen) continue;
4211
+ }
4212
+ streamPayloadSeen = true;
4213
+ if (isTurnProgressChunk(mode, data)) turnProgressSeen = true;
4214
+ yield [namespace, mode, data];
4215
+ if (mode === "custom" && isTurnEndCustomData(data)) {
4216
+ const customType = String(data.type ?? "").trim();
4217
+ this.lastTurnEndState = customType === STREAM_END ? "stream_end" : "completed";
4218
+ yield* this.drainStreamEventsAfterIdle(expectedLoopId);
4219
+ break;
4220
+ }
4221
+ }
4222
+ } catch (exc) {
4223
+ this.lastTurnErrorMessage = String(exc);
4224
+ throw exc;
4225
+ } finally {
4226
+ this.streaming = false;
4227
+ this.readBusy = false;
4228
+ }
4229
+ }
4230
+ };
2977
4231
  // Annotate the CommonJS export names for ESM import in node:
2978
4232
  0 && (module.exports = {
2979
4233
  CLIENT_VERSION,
2980
4234
  ChatEventTerminal,
2981
4235
  Client,
4236
+ CommandClient,
2982
4237
  ConnectionError,
2983
4238
  ConnectionPool,
2984
4239
  DEFAULT_CLIENT_CAPABILITIES,
2985
4240
  DEFAULT_DELIVERABLE_PHASES,
4241
+ DEFAULT_POST_IDLE_DRAIN_MS,
2986
4242
  DEFAULT_THINKING_STEP_EVENTS,
2987
4243
  DaemonError,
4244
+ DaemonSession,
2988
4245
  DisconnectCause,
4246
+ ErrIdleTimeout,
2989
4247
  ErrPoolExhausted,
2990
4248
  ErrQueryBusy,
2991
4249
  ErrQueryTimeout,
@@ -3031,26 +4289,31 @@ var TurnRunner = class {
3031
4289
  INTENT_HINT_OCR,
3032
4290
  INTENT_HINT_TEXT_COMPLETION,
3033
4291
  LOOP_ASSISTANT_OUTPUT_PHASES,
3034
- Multiplexer,
3035
4292
  PROTO_VERSION,
3036
4293
  PooledConn,
3037
4294
  QueryGate,
3038
4295
  REMOVED_INTENT_HINTS,
3039
4296
  ReconnectError,
3040
4297
  SSEBroadcaster,
4298
+ STREAM_END,
3041
4299
  StaleLoopError,
4300
+ StreamCloseFail,
4301
+ StreamCloseSoftComplete,
3042
4302
  TimeoutError,
4303
+ TimeoutPolicy,
4304
+ TurnEventStats,
3043
4305
  TurnRunner,
3044
4306
  VerbosityTier,
3045
4307
  authenticate,
3046
4308
  bootstrapLoopSession,
3047
4309
  checkDaemonStatus,
3048
4310
  classifyEventVerbosity,
4311
+ compactAttachments,
4312
+ compactImageAttachment,
3049
4313
  connectWithRetries,
4314
+ connectedWebsocket,
3050
4315
  connectionInitEnvelope,
3051
4316
  decodeMessage,
3052
- defaultBootstrapFunc,
3053
- defaultClientFactory,
3054
4317
  defaultConfig,
3055
4318
  defaultPoolConfig,
3056
4319
  disconnectCauseName,
@@ -3059,12 +4322,18 @@ var TurnRunner = class {
3059
4322
  extractSootheLoopID,
3060
4323
  extractThinkingStep,
3061
4324
  fetchConfigSection,
4325
+ fetchLoopCards,
3062
4326
  fetchLoopHistory,
4327
+ fetchLoopMessages,
3063
4328
  fetchSkillsCatalog,
4329
+ idleTimeoutForTurn,
4330
+ inboundNeedsDeliveryAck,
3064
4331
  inputMessageForLoop,
3065
4332
  isCompletionEvent,
3066
4333
  isDaemonLive,
3067
4334
  isSubagentProgressEvent,
4335
+ isTurnEndCustomData,
4336
+ isTurnProgressChunk,
3068
4337
  isValidVerbosityLevel,
3069
4338
  loadConfigFromEnv,
3070
4339
  newLoopInputMessage,
@@ -3075,6 +4344,7 @@ var TurnRunner = class {
3075
4344
  parseNamespace,
3076
4345
  pingEnvelope,
3077
4346
  pongEnvelope,
4347
+ protocol1Rpc,
3078
4348
  refreshAuthToken,
3079
4349
  requestDaemonConfigReload,
3080
4350
  requestDaemonShutdown,