@mirasoth/soothe-client 0.2.1 → 0.4.0

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
@@ -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.0";
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,12 +883,17 @@ 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
898
  // Protocol-1 handshake state (RFC-450 §8.2)
543
899
  handshakeComplete = false;
@@ -555,6 +911,8 @@ var init_client = __esm({
555
911
  // Pending-request/subscription multiplexer (RFC-629 constraint #1). 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
  }
@@ -941,6 +1301,84 @@ 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
1383
  // Protocol-1 RPC primitives (RFC-450 §5/§9)
946
1384
  // ---------------------------------------------------------------------------
@@ -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;
@@ -1437,13 +1904,17 @@ __export(index_exports, {
1437
1904
  CLIENT_VERSION: () => CLIENT_VERSION,
1438
1905
  ChatEventTerminal: () => ChatEventTerminal,
1439
1906
  Client: () => Client,
1907
+ CommandClient: () => CommandClient,
1440
1908
  ConnectionError: () => ConnectionError,
1441
1909
  ConnectionPool: () => ConnectionPool,
1442
1910
  DEFAULT_CLIENT_CAPABILITIES: () => DEFAULT_CLIENT_CAPABILITIES,
1443
1911
  DEFAULT_DELIVERABLE_PHASES: () => DEFAULT_DELIVERABLE_PHASES,
1912
+ DEFAULT_POST_IDLE_DRAIN_MS: () => DEFAULT_POST_IDLE_DRAIN_MS,
1444
1913
  DEFAULT_THINKING_STEP_EVENTS: () => DEFAULT_THINKING_STEP_EVENTS,
1445
1914
  DaemonError: () => DaemonError,
1915
+ DaemonSession: () => DaemonSession,
1446
1916
  DisconnectCause: () => DisconnectCause,
1917
+ ErrIdleTimeout: () => ErrIdleTimeout,
1447
1918
  ErrPoolExhausted: () => ErrPoolExhausted,
1448
1919
  ErrQueryBusy: () => ErrQueryBusy,
1449
1920
  ErrQueryTimeout: () => ErrQueryTimeout,
@@ -1489,26 +1960,31 @@ __export(index_exports, {
1489
1960
  INTENT_HINT_OCR: () => INTENT_HINT_OCR,
1490
1961
  INTENT_HINT_TEXT_COMPLETION: () => INTENT_HINT_TEXT_COMPLETION,
1491
1962
  LOOP_ASSISTANT_OUTPUT_PHASES: () => LOOP_ASSISTANT_OUTPUT_PHASES,
1492
- Multiplexer: () => Multiplexer,
1493
1963
  PROTO_VERSION: () => PROTO_VERSION,
1494
1964
  PooledConn: () => PooledConn,
1495
1965
  QueryGate: () => QueryGate,
1496
1966
  REMOVED_INTENT_HINTS: () => REMOVED_INTENT_HINTS,
1497
1967
  ReconnectError: () => ReconnectError,
1498
1968
  SSEBroadcaster: () => SSEBroadcaster,
1969
+ STREAM_END: () => STREAM_END,
1499
1970
  StaleLoopError: () => StaleLoopError,
1971
+ StreamCloseFail: () => StreamCloseFail,
1972
+ StreamCloseSoftComplete: () => StreamCloseSoftComplete,
1500
1973
  TimeoutError: () => TimeoutError,
1974
+ TimeoutPolicy: () => TimeoutPolicy,
1975
+ TurnEventStats: () => TurnEventStats,
1501
1976
  TurnRunner: () => TurnRunner,
1502
1977
  VerbosityTier: () => VerbosityTier,
1503
1978
  authenticate: () => authenticate,
1504
1979
  bootstrapLoopSession: () => bootstrapLoopSession,
1505
1980
  checkDaemonStatus: () => checkDaemonStatus,
1506
1981
  classifyEventVerbosity: () => classifyEventVerbosity,
1982
+ compactAttachments: () => compactAttachments,
1983
+ compactImageAttachment: () => compactImageAttachment,
1507
1984
  connectWithRetries: () => connectWithRetries,
1985
+ connectedWebsocket: () => connectedWebsocket,
1508
1986
  connectionInitEnvelope: () => connectionInitEnvelope,
1509
1987
  decodeMessage: () => decodeMessage,
1510
- defaultBootstrapFunc: () => defaultBootstrapFunc,
1511
- defaultClientFactory: () => defaultClientFactory,
1512
1988
  defaultConfig: () => defaultConfig,
1513
1989
  defaultPoolConfig: () => defaultPoolConfig,
1514
1990
  disconnectCauseName: () => disconnectCauseName,
@@ -1517,12 +1993,18 @@ __export(index_exports, {
1517
1993
  extractSootheLoopID: () => extractSootheLoopID,
1518
1994
  extractThinkingStep: () => extractThinkingStep,
1519
1995
  fetchConfigSection: () => fetchConfigSection,
1996
+ fetchLoopCards: () => fetchLoopCards,
1520
1997
  fetchLoopHistory: () => fetchLoopHistory,
1998
+ fetchLoopMessages: () => fetchLoopMessages,
1521
1999
  fetchSkillsCatalog: () => fetchSkillsCatalog,
2000
+ idleTimeoutForTurn: () => idleTimeoutForTurn,
2001
+ inboundNeedsDeliveryAck: () => inboundNeedsDeliveryAck,
1522
2002
  inputMessageForLoop: () => inputMessageForLoop,
1523
2003
  isCompletionEvent: () => isCompletionEvent,
1524
2004
  isDaemonLive: () => isDaemonLive,
1525
2005
  isSubagentProgressEvent: () => isSubagentProgressEvent,
2006
+ isTurnEndCustomData: () => isTurnEndCustomData,
2007
+ isTurnProgressChunk: () => isTurnProgressChunk,
1526
2008
  isValidVerbosityLevel: () => isValidVerbosityLevel,
1527
2009
  loadConfigFromEnv: () => loadConfigFromEnv,
1528
2010
  newLoopInputMessage: () => newLoopInputMessage,
@@ -1533,6 +2015,7 @@ __export(index_exports, {
1533
2015
  parseNamespace: () => parseNamespace,
1534
2016
  pingEnvelope: () => pingEnvelope,
1535
2017
  pongEnvelope: () => pongEnvelope,
2018
+ protocol1Rpc: () => protocol1Rpc,
1536
2019
  refreshAuthToken: () => refreshAuthToken,
1537
2020
  requestDaemonConfigReload: () => requestDaemonConfigReload,
1538
2021
  requestDaemonShutdown: () => requestDaemonShutdown,
@@ -1548,156 +2031,166 @@ __export(index_exports, {
1548
2031
  });
1549
2032
  module.exports = __toCommonJS(index_exports);
1550
2033
  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
2034
+ init_verbosity();
1578
2035
  init_config();
1579
2036
  init_protocol();
1580
2037
  init_intent_hints();
2038
+ init_events();
2039
+ init_client();
1581
2040
 
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;
2041
+ // src/command_client.ts
2042
+ init_client();
2043
+ init_config();
2044
+
2045
+ // src/session.ts
2046
+ init_config();
2047
+ init_protocol();
2048
+ init_errors();
2049
+ async function bootstrapLoopSession(client, resumeLoopId, config, loopNew) {
2050
+ const cfg = config ?? defaultConfig();
2051
+ let loopId = (resumeLoopId ?? "").trim();
2052
+ if (!loopId) {
2053
+ const env = newLoopNewMessage(loopNew);
2054
+ const newResp = await client.requestResponse(
2055
+ env.method,
2056
+ env.params ?? {},
2057
+ "loop_new",
2058
+ cfg.loopStatusTimeout
2059
+ );
2060
+ loopId = String(newResp.loop_id ?? "").trim();
2061
+ if (!loopId) {
2062
+ throw new Error("loop_new response missing loop_id");
1636
2063
  }
1637
2064
  }
1638
- parts.push(ns.slice(start));
1639
- return parts;
2065
+ await client.subscribe(
2066
+ "loop_events",
2067
+ { loop_id: loopId, verbosity: cfg.verbosityLevel },
2068
+ cfg.subscriptionTimeout
2069
+ );
2070
+ return loopId;
1640
2071
  }
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 */;
2072
+ async function waitDaemonReady(client, timeout) {
2073
+ if (client.isConnected()) return;
2074
+ const deadline = Date.now() + timeout;
2075
+ while (Date.now() < deadline) {
2076
+ const remaining = deadline - Date.now();
2077
+ if (remaining <= 0) break;
2078
+ const ev = await client.readEventWithTimeout(remaining);
2079
+ if (ev === null) break;
2080
+ if (ev.type === "connection_ack") {
2081
+ const result = ev.result ?? {};
2082
+ const state = result.readiness_state;
2083
+ if (state === "ready") return;
2084
+ throw new Error(`daemon not ready: state=${JSON.stringify(state ?? "unknown")}`);
2085
+ }
1665
2086
  }
2087
+ throw new Error(`timeout after ${timeout}ms waiting for connection_ack (ready)`);
1666
2088
  }
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 */;
2089
+ async function waitLoopStatusWithID(client, timeout) {
2090
+ const deadline = Date.now() + timeout;
2091
+ while (Date.now() < deadline) {
2092
+ const remaining = deadline - Date.now();
2093
+ if (remaining <= 0) break;
2094
+ const ev = await client.readEventWithTimeout(remaining);
2095
+ if (ev === null) break;
2096
+ if (ev.type === "error") {
2097
+ const errObj = ev.error ?? {};
2098
+ throw new DaemonError(errObj.code ?? -32603, errObj.message ?? "daemon error");
2099
+ }
2100
+ if (ev.type === "status") {
2101
+ const lid = ev.loop_id;
2102
+ if (lid && lid !== "") {
2103
+ return ev;
2104
+ }
2105
+ }
1676
2106
  }
2107
+ throw new Error(`timeout after ${timeout}ms waiting for status with loop_id`);
1677
2108
  }
1678
- function classifyByEventTypeString(eventType) {
1679
- if (eventType === EventFinalReport || eventType === EventGeneralFailed) {
1680
- return 0 /* Quiet */;
1681
- }
1682
- if (eventType === EventToolStarted) {
1683
- return 99 /* Internal */;
2109
+ async function waitSubscriptionConfirmed(client, wantLoopID, _wantVerbosity, timeout) {
2110
+ const deadline = Date.now() + timeout;
2111
+ while (Date.now() < deadline) {
2112
+ const remaining = deadline - Date.now();
2113
+ if (remaining <= 0) break;
2114
+ const ev = await client.readEventWithTimeout(remaining);
2115
+ if (ev === null) break;
2116
+ if (ev.type === "next") {
2117
+ const payload = ev.payload ?? {};
2118
+ const lid = String(payload.loop_id ?? "");
2119
+ if (lid === wantLoopID && payload.success === true) return;
2120
+ continue;
2121
+ }
2122
+ if (ev.type === "error") {
2123
+ const errObj = ev.error ?? {};
2124
+ throw new Error(`daemon error: ${errObj.message ?? "subscription failed"}`);
2125
+ }
1684
2126
  }
1685
- return 1 /* Normal */;
1686
- }
1687
- function isCompletionEvent(eventType) {
1688
- return eventType.endsWith(".completed") || eventType.endsWith(".failed") || eventType === EventGeneralFailed;
2127
+ throw new Error(`timeout after ${timeout}ms waiting for subscription confirmation`);
1689
2128
  }
1690
- function isSubagentProgressEvent(eventType) {
1691
- const parsed = parseNamespace(eventType);
1692
- if (!parsed || parsed.domain !== "subagent") {
1693
- return false;
2129
+ async function connectWithRetries(client, maxRetries, retryDelay) {
2130
+ const retries = maxRetries && maxRetries > 0 ? maxRetries : 40;
2131
+ const delay = retryDelay && retryDelay > 0 ? retryDelay : 250;
2132
+ let lastErr = null;
2133
+ for (let attempt = 0; attempt < retries; attempt++) {
2134
+ try {
2135
+ await client.connect();
2136
+ return;
2137
+ } catch (err) {
2138
+ lastErr = err;
2139
+ }
2140
+ await new Promise((resolve) => setTimeout(resolve, delay));
1694
2141
  }
1695
- return parsed.action === "started" || parsed.action === "completed";
2142
+ throw new Error(
2143
+ `failed to connect after ${retries} attempts: ${lastErr?.message ?? "unknown error"}`
2144
+ );
1696
2145
  }
1697
2146
 
1698
- // src/index.ts
1699
- init_client();
1700
- init_multiplexer();
2147
+ // src/command_client.ts
2148
+ var CommandClient = class {
2149
+ url;
2150
+ timeoutMs;
2151
+ config;
2152
+ constructor(url, opts) {
2153
+ this.url = url;
2154
+ this.timeoutMs = opts?.timeoutMs && opts.timeoutMs > 0 ? opts.timeoutMs : 3e4;
2155
+ this.config = opts?.config ?? defaultConfig();
2156
+ }
2157
+ async withClient(fn) {
2158
+ const client = new Client(this.url, this.config);
2159
+ try {
2160
+ await connectWithRetries(client, 5, 250);
2161
+ return await fn(client);
2162
+ } finally {
2163
+ client.close();
2164
+ }
2165
+ }
2166
+ /** Generic one-shot RPC. */
2167
+ async request(method, params = {}) {
2168
+ return this.withClient(
2169
+ (client) => client.requestResponse(method, params, void 0, this.timeoutMs)
2170
+ );
2171
+ }
2172
+ async jobCreate(goal, workspace = "") {
2173
+ const params = { goal };
2174
+ if (workspace) params.workspace = workspace;
2175
+ return this.request("job_create", params);
2176
+ }
2177
+ async jobStatus(jobId) {
2178
+ return this.request("job_status", { job_id: jobId });
2179
+ }
2180
+ async jobCancel(jobId) {
2181
+ return this.request("job_cancel", { job_id: jobId });
2182
+ }
2183
+ async cronAdd(text, priority = 0) {
2184
+ const params = { text };
2185
+ if (priority > 0) params.priority = priority;
2186
+ return this.request("cron_add", params);
2187
+ }
2188
+ async cronList(status = "") {
2189
+ const params = {};
2190
+ if (status) params.status = status;
2191
+ return this.request("cron_list", params);
2192
+ }
2193
+ };
1701
2194
 
1702
2195
  // src/helpers.ts
1703
2196
  init_config();
@@ -1779,109 +2272,79 @@ async function refreshAuthToken(client, refreshToken, timeout) {
1779
2272
  timeout ?? 15e3
1780
2273
  );
1781
2274
  }
1782
-
1783
- // src/session.ts
1784
- init_config();
1785
- init_protocol();
1786
- init_errors();
1787
- async function bootstrapLoopSession(client, resumeLoopId, config, loopNew) {
1788
- const cfg = config ?? defaultConfig();
1789
- let loopId = (resumeLoopId ?? "").trim();
1790
- if (!loopId) {
1791
- const env = newLoopNewMessage(loopNew);
1792
- const newResp = await client.requestResponse(
1793
- env.method,
1794
- env.params ?? {},
1795
- "loop_new",
1796
- cfg.loopStatusTimeout
1797
- );
1798
- loopId = String(newResp.loop_id ?? "").trim();
1799
- if (!loopId) {
1800
- throw new Error("loop_new response missing loop_id");
1801
- }
1802
- }
1803
- await client.subscribe(
1804
- "loop_events",
1805
- { loop_id: loopId, verbosity: cfg.verbosityLevel },
1806
- cfg.subscriptionTimeout
1807
- );
1808
- return loopId;
2275
+ async function fetchLoopCards(client, loopID, timeout) {
2276
+ return client.fetchLoopCards(loopID, timeout);
1809
2277
  }
1810
- async function waitDaemonReady(client, timeout) {
1811
- if (client.isConnected()) return;
1812
- const deadline = Date.now() + timeout;
1813
- while (Date.now() < deadline) {
1814
- const remaining = deadline - Date.now();
1815
- if (remaining <= 0) break;
1816
- const ev = await client.readEventWithTimeout(remaining);
1817
- if (ev === null) break;
1818
- if (ev.type === "connection_ack") {
1819
- const result = ev.result ?? {};
1820
- const state = result.readiness_state;
1821
- if (state === "ready") return;
1822
- throw new Error(`daemon not ready: state=${JSON.stringify(state ?? "unknown")}`);
1823
- }
1824
- }
1825
- throw new Error(`timeout after ${timeout}ms waiting for connection_ack (ready)`);
2278
+ async function fetchLoopMessages(client, loopID, opts) {
2279
+ return client.getLoopMessages(
2280
+ loopID,
2281
+ opts?.limit,
2282
+ opts?.offset,
2283
+ opts?.includeEvents,
2284
+ opts?.timeout
2285
+ );
1826
2286
  }
1827
- async function waitLoopStatusWithID(client, timeout) {
1828
- const deadline = Date.now() + timeout;
1829
- while (Date.now() < deadline) {
1830
- const remaining = deadline - Date.now();
1831
- if (remaining <= 0) break;
1832
- const ev = await client.readEventWithTimeout(remaining);
1833
- if (ev === null) break;
1834
- if (ev.type === "error") {
1835
- const errObj = ev.error ?? {};
1836
- throw new DaemonError(errObj.code ?? -32603, errObj.message ?? "daemon error");
2287
+ async function connectedWebsocket(wsUrl, fn, timeoutMs = 3e4) {
2288
+ const { Client: Client2 } = await Promise.resolve().then(() => (init_client(), client_exports));
2289
+ const client = new Client2(wsUrl, defaultConfig());
2290
+ const deadline = Date.now() + timeoutMs;
2291
+ try {
2292
+ await client.connect();
2293
+ while (!client.isConnected() && Date.now() < deadline) {
2294
+ await new Promise((r) => setTimeout(r, 25));
1837
2295
  }
1838
- if (ev.type === "status") {
1839
- const lid = ev.loop_id;
1840
- if (lid && lid !== "") {
1841
- return ev;
1842
- }
2296
+ if (!client.isConnected()) {
2297
+ throw new Error("Timed out waiting for daemon handshake");
1843
2298
  }
2299
+ return await fn(client);
2300
+ } finally {
2301
+ client.close();
1844
2302
  }
1845
- throw new Error(`timeout after ${timeout}ms waiting for status with loop_id`);
1846
2303
  }
1847
- async function waitSubscriptionConfirmed(client, wantLoopID, _wantVerbosity, timeout) {
1848
- const deadline = Date.now() + timeout;
1849
- while (Date.now() < deadline) {
1850
- const remaining = deadline - Date.now();
1851
- if (remaining <= 0) break;
1852
- const ev = await client.readEventWithTimeout(remaining);
1853
- if (ev === null) break;
1854
- if (ev.type === "next") {
1855
- const payload = ev.payload ?? {};
1856
- const lid = String(payload.loop_id ?? "");
1857
- if (lid === wantLoopID && payload.success === true) return;
1858
- continue;
1859
- }
1860
- if (ev.type === "error") {
1861
- const errObj = ev.error ?? {};
1862
- throw new Error(`daemon error: ${errObj.message ?? "subscription failed"}`);
2304
+ async function protocol1Rpc(wsUrl, method, params = null, opts = {}) {
2305
+ const mode = opts.mode ?? "request";
2306
+ const timeoutMs = opts.timeoutMs ?? 3e4;
2307
+ try {
2308
+ return await connectedWebsocket(
2309
+ wsUrl,
2310
+ async (client) => {
2311
+ if (mode === "notify") {
2312
+ await client.notify(method, params ?? {});
2313
+ return {};
2314
+ }
2315
+ if (mode === "subscribe") {
2316
+ const subId = await client.subscribe(
2317
+ method,
2318
+ params ?? {},
2319
+ timeoutMs
2320
+ );
2321
+ return { subscription_id: subId };
2322
+ }
2323
+ const result = await client.requestResponse(
2324
+ method,
2325
+ params ?? {},
2326
+ method,
2327
+ timeoutMs
2328
+ );
2329
+ return result && typeof result === "object" ? result : { result };
2330
+ },
2331
+ timeoutMs
2332
+ );
2333
+ } catch (exc) {
2334
+ const msg = exc instanceof Error ? exc.message : String(exc);
2335
+ if (msg.toLowerCase().includes("timed out") || msg.toLowerCase().includes("timeout")) {
2336
+ return { error: "Timed out waiting for daemon response" };
1863
2337
  }
1864
- }
1865
- throw new Error(`timeout after ${timeout}ms waiting for subscription confirmation`);
1866
- }
1867
- async function connectWithRetries(client, maxRetries, retryDelay) {
1868
- const retries = maxRetries && maxRetries > 0 ? maxRetries : 40;
1869
- const delay = retryDelay && retryDelay > 0 ? retryDelay : 250;
1870
- let lastErr = null;
1871
- for (let attempt = 0; attempt < retries; attempt++) {
1872
- try {
1873
- await client.connect();
1874
- return;
1875
- } catch (err) {
1876
- lastErr = err;
2338
+ if (msg.toLowerCase().includes("connect") || msg.toLowerCase().includes("dial")) {
2339
+ return { error: `Connection error: ${msg}` };
1877
2340
  }
1878
- await new Promise((resolve) => setTimeout(resolve, delay));
2341
+ return { error: msg };
1879
2342
  }
1880
- throw new Error(
1881
- `failed to connect after ${retries} attempts: ${lastErr?.message ?? "unknown error"}`
1882
- );
1883
2343
  }
1884
2344
 
2345
+ // src/index.ts
2346
+ init_stream_terminal();
2347
+
1885
2348
  // src/appkit/broadcaster.ts
1886
2349
  var SUBSCRIBER_QUEUE_CAP = 100;
1887
2350
  var SSEBroadcaster = class {
@@ -1985,6 +2448,7 @@ var SSEBroadcaster = class {
1985
2448
 
1986
2449
  // src/appkit/classifier.ts
1987
2450
  init_errors();
2451
+ init_events();
1988
2452
 
1989
2453
  // src/appkit/thinking_step.ts
1990
2454
  var MAX_THINKING_STEP_RUNES = 280;
@@ -2104,6 +2568,7 @@ var EventClassifier = class {
2104
2568
  deliverablePhases;
2105
2569
  minDeliverableRunes;
2106
2570
  thinkingStepEvents;
2571
+ treatStatusIdleAsComplete;
2107
2572
  constructor(cfg) {
2108
2573
  if (!cfg.deliverablePhases) {
2109
2574
  throw new Error("appkit: ClassifierConfig.deliverablePhases must not be nil");
@@ -2111,6 +2576,7 @@ var EventClassifier = class {
2111
2576
  this.deliverablePhases = cfg.deliverablePhases;
2112
2577
  this.minDeliverableRunes = cfg.minDeliverableRunes && cfg.minDeliverableRunes > 0 ? cfg.minDeliverableRunes : 8;
2113
2578
  this.thinkingStepEvents = cfg.thinkingStepEvents;
2579
+ this.treatStatusIdleAsComplete = Boolean(cfg.treatStatusIdleAsComplete);
2114
2580
  }
2115
2581
  /**
2116
2582
  * Inspects one decoded event and returns its outcome. `accumulated` is the
@@ -2127,6 +2593,13 @@ var EventClassifier = class {
2127
2593
  */
2128
2594
  isDeliverableCompletionEvent(eventType) {
2129
2595
  if (!eventType) return false;
2596
+ switch (eventType) {
2597
+ case "status.idle":
2598
+ case "idle_timeout":
2599
+ case "query_timeout":
2600
+ case "stream_closed":
2601
+ return true;
2602
+ }
2130
2603
  if (eventType === EventFinalReport) return true;
2131
2604
  if (eventType.startsWith("soothe.protocol.message.")) {
2132
2605
  const phase = eventType.slice("soothe.protocol.message.".length);
@@ -2162,16 +2635,22 @@ var EventClassifier = class {
2162
2635
  return ["", false];
2163
2636
  }
2164
2637
  /** The event→outcome mapper, ported from triarch's ProcessChatEvent. */
2165
- processChatEvent(msg, _accumulated) {
2638
+ processChatEvent(msg, accumulated) {
2166
2639
  if (!msg || typeof msg !== "object") {
2167
2640
  return { terminal: 0 /* Continue */ };
2168
2641
  }
2169
2642
  const m = msg;
2170
2643
  const typ = m.type;
2171
2644
  if (typ === "next") {
2172
- return this.classifyNextEnvelope(m);
2645
+ return this.classifyNextEnvelope(m, accumulated);
2646
+ }
2647
+ if (typ === "response" || typ === "complete" || typ === "receipt_response" || typ === "connection_ack") {
2648
+ return { terminal: 0 /* Continue */ };
2173
2649
  }
2174
- if (typ === "response" || typ === "complete" || typ === "receipt_response" || typ === "connection_ack" || typ === "status") {
2650
+ if (typ === "status") {
2651
+ if (this.treatStatusIdleAsComplete && String(m.state ?? "").trim().toLowerCase() === "idle" && this.isSubstantiveAssistantReply(accumulated)) {
2652
+ return this.deliverableResult(accumulated.trim(), "status.idle");
2653
+ }
2175
2654
  return { terminal: 0 /* Continue */ };
2176
2655
  }
2177
2656
  if (typ === "error") {
@@ -2191,10 +2670,14 @@ var EventClassifier = class {
2191
2670
  return { terminal: 0 /* Continue */ };
2192
2671
  }
2193
2672
  /** Classifies a `next` envelope by projecting its payload. */
2194
- classifyNextEnvelope(env) {
2673
+ classifyNextEnvelope(env, accumulated) {
2195
2674
  const payload = env.payload ?? {};
2196
2675
  const innerData = payload.data;
2197
2676
  if (innerData && typeof innerData === "object") {
2677
+ const innerType = innerData.type ?? "";
2678
+ if (innerType === "status") {
2679
+ return this.processChatEvent(innerData, accumulated);
2680
+ }
2198
2681
  const innerMode = innerData.mode ?? "";
2199
2682
  if (innerMode) {
2200
2683
  return this.classifyEventPayload(
@@ -2205,6 +2688,11 @@ var EventClassifier = class {
2205
2688
  }
2206
2689
  }
2207
2690
  const mode = payload.mode ?? "";
2691
+ if (mode === "status" || mode === "") {
2692
+ if (typeof payload.state === "string" || innerData && "state" in (innerData ?? {})) {
2693
+ return this.processChatEvent({ type: "status", ...innerData ?? payload }, accumulated);
2694
+ }
2695
+ }
2208
2696
  if (mode) {
2209
2697
  return this.classifyEventPayload(payload.namespace ?? null, mode, payload.data);
2210
2698
  }
@@ -2622,10 +3110,15 @@ var ConnectionPool = class {
2622
3110
  if (existing.isDisconnected() || !existing.isConnected()) {
2623
3111
  await this.release(sessionID);
2624
3112
  } else {
2625
- existing.lastUsed = Date.now();
2626
- await this.store.updateLastUsed(sessionID).catch(() => {
2627
- });
2628
- return existing;
3113
+ const idleTooLong = this.cfg.maxIdleTime > 0 && existing.lastUsed > 0 && Date.now() - existing.lastUsed > this.cfg.maxIdleTime;
3114
+ if (idleTooLong) {
3115
+ await this.release(sessionID);
3116
+ } else {
3117
+ existing.lastUsed = Date.now();
3118
+ await this.store.updateLastUsed(sessionID).catch(() => {
3119
+ });
3120
+ return existing;
3121
+ }
2629
3122
  }
2630
3123
  }
2631
3124
  const conn = this.pool.pop();
@@ -2728,6 +3221,88 @@ var ConnectionPool = class {
2728
3221
  }
2729
3222
  };
2730
3223
 
3224
+ // src/appkit/attachments.ts
3225
+ function compactDefaults(opts) {
3226
+ return {
3227
+ maxDim: opts?.maxDim && opts.maxDim > 0 ? opts.maxDim : 768,
3228
+ quality: opts?.jpegQuality && opts.jpegQuality > 0 ? opts.jpegQuality : 85
3229
+ };
3230
+ }
3231
+ var sharpLoader = null;
3232
+ async function loadSharp() {
3233
+ if (!sharpLoader) {
3234
+ sharpLoader = (async () => {
3235
+ try {
3236
+ const m = await Function('return import("sharp")')();
3237
+ return m;
3238
+ } catch {
3239
+ return null;
3240
+ }
3241
+ })();
3242
+ }
3243
+ return sharpLoader;
3244
+ }
3245
+ async function compactImageAttachment(mimeType, dataB64, opts) {
3246
+ if (!dataB64 || !mimeType.startsWith("image/")) {
3247
+ return [mimeType, dataB64];
3248
+ }
3249
+ let raw;
3250
+ try {
3251
+ raw = Buffer.from(dataB64, "base64");
3252
+ } catch {
3253
+ return [mimeType, dataB64];
3254
+ }
3255
+ if (raw.length === 0) return [mimeType, dataB64];
3256
+ const sharpMod = await loadSharp();
3257
+ if (!sharpMod) return [mimeType, dataB64];
3258
+ const { maxDim, quality } = compactDefaults(opts);
3259
+ try {
3260
+ const img = sharpMod.default(raw, { failOn: "none" });
3261
+ const meta = await img.metadata();
3262
+ const w = meta.width ?? 0;
3263
+ const h = meta.height ?? 0;
3264
+ if (w <= 0 || h <= 0 || w <= maxDim && h <= maxDim) {
3265
+ return [mimeType, dataB64];
3266
+ }
3267
+ let nw = w;
3268
+ let nh = h;
3269
+ if (w >= h) {
3270
+ if (w > maxDim) {
3271
+ nw = maxDim;
3272
+ nh = Math.max(1, Math.round(h * maxDim / w));
3273
+ }
3274
+ } else if (h > maxDim) {
3275
+ nh = maxDim;
3276
+ nw = Math.max(1, Math.round(w * maxDim / h));
3277
+ }
3278
+ const resized = img.resize(nw, nh, { fit: "fill" });
3279
+ if (mimeType === "image/png") {
3280
+ const buf2 = await resized.png().toBuffer();
3281
+ return [mimeType, buf2.toString("base64")];
3282
+ }
3283
+ const buf = await resized.jpeg({ quality }).toBuffer();
3284
+ return ["image/jpeg", buf.toString("base64")];
3285
+ } catch {
3286
+ return [mimeType, dataB64];
3287
+ }
3288
+ }
3289
+ async function compactAttachments(atts, opts) {
3290
+ if (!atts.length) return atts;
3291
+ const out = [];
3292
+ for (const att of atts) {
3293
+ const cp = { ...att };
3294
+ const mime = typeof cp.mime_type === "string" ? cp.mime_type : "";
3295
+ const data = typeof cp.data === "string" ? cp.data : "";
3296
+ if (mime && data) {
3297
+ const [outMime, outData] = await compactImageAttachment(mime, data, opts);
3298
+ cp.mime_type = outMime;
3299
+ cp.data = outData;
3300
+ }
3301
+ out.push(cp);
3302
+ }
3303
+ return out;
3304
+ }
3305
+
2731
3306
  // src/appkit/turn_runner.ts
2732
3307
  init_intent_hints();
2733
3308
  var ErrQueryTimeout = class extends Error {
@@ -2736,6 +3311,19 @@ var ErrQueryTimeout = class extends Error {
2736
3311
  this.name = "ErrQueryTimeout";
2737
3312
  }
2738
3313
  };
3314
+ var ErrIdleTimeout = class extends Error {
3315
+ constructor() {
3316
+ super("appkit: idle timeout");
3317
+ this.name = "ErrIdleTimeout";
3318
+ }
3319
+ };
3320
+ var TimeoutPolicy = /* @__PURE__ */ ((TimeoutPolicy2) => {
3321
+ TimeoutPolicy2[TimeoutPolicy2["Fail"] = 0] = "Fail";
3322
+ TimeoutPolicy2[TimeoutPolicy2["SoftComplete"] = 1] = "SoftComplete";
3323
+ return TimeoutPolicy2;
3324
+ })(TimeoutPolicy || {});
3325
+ var StreamCloseFail = 0 /* Fail */;
3326
+ var StreamCloseSoftComplete = 1 /* SoftComplete */;
2739
3327
  function inputMessageForLoop(text, loopID, attachments, opts) {
2740
3328
  const msg = { type: "loop_input", content: text };
2741
3329
  if (loopID) msg.loop_id = loopID;
@@ -2758,6 +3346,13 @@ function inputMessageForLoop(text, loopID, attachments, opts) {
2758
3346
  }
2759
3347
  return msg;
2760
3348
  }
3349
+ function idleTimeoutForTurn(cfg, hasAttachments) {
3350
+ const idle = cfg.idleTimeout ?? 0;
3351
+ if (idle <= 0) return 0;
3352
+ const floor = cfg.minIdleTimeoutWithAttachments ?? 0;
3353
+ if (hasAttachments && floor > 0 && idle < floor) return floor;
3354
+ return idle;
3355
+ }
2761
3356
  var TurnRunner = class {
2762
3357
  pool;
2763
3358
  gate;
@@ -2768,39 +3363,29 @@ var TurnRunner = class {
2768
3363
  buildInput = inputMessageForLoop;
2769
3364
  onComplete = null;
2770
3365
  onError = null;
2771
- /**
2772
- * Constructs a TurnRunner. pool, gate, classifier, and store are required;
2773
- * broadcaster may be null.
2774
- */
2775
3366
  constructor(pool, gate, classifier, store, broadcaster, cfg) {
2776
3367
  this.pool = pool;
2777
3368
  this.gate = gate;
2778
3369
  this.classifier = classifier;
2779
3370
  this.store = store;
2780
3371
  this.broadcaster = broadcaster;
2781
- this.cfg = { queryTimeout: cfg.queryTimeout > 0 ? cfg.queryTimeout : 30 * 60 * 1e3 };
3372
+ this.cfg = {
3373
+ ...cfg,
3374
+ queryTimeout: cfg.queryTimeout > 0 ? cfg.queryTimeout : 30 * 60 * 1e3
3375
+ };
2782
3376
  }
2783
- /** Overrides the loop_input payload builder. */
2784
3377
  withInputBuilder(f) {
2785
3378
  if (f) this.buildInput = f;
2786
3379
  return this;
2787
3380
  }
2788
- /** Sets a completion hook (runs inline on success). */
2789
3381
  withOnComplete(f) {
2790
3382
  this.onComplete = f;
2791
3383
  return this;
2792
3384
  }
2793
- /** Sets an error hook (runs inline on failure). */
2794
3385
  withOnError(f) {
2795
3386
  this.onError = f;
2796
3387
  return this;
2797
3388
  }
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
3389
  async execute(sessionID, message, userID, workspaceID, attachments, opts, signal) {
2805
3390
  let conn;
2806
3391
  try {
@@ -2828,13 +3413,19 @@ var TurnRunner = class {
2828
3413
  this.onError?.(sessionID, loopID, err);
2829
3414
  throw err;
2830
3415
  }
3416
+ let idleTimer = null;
3417
+ const clearIdle = () => {
3418
+ if (idleTimer) {
3419
+ clearTimeout(idleTimer);
3420
+ idleTimer = null;
3421
+ }
3422
+ };
2831
3423
  try {
2832
- const inputMsg = this.buildInput(
2833
- message,
2834
- loopID,
2835
- attachments ?? void 0,
2836
- opts ?? void 0
2837
- );
3424
+ let atts = attachments ?? void 0;
3425
+ if (this.cfg.compactAttachmentsBeforeSend && atts && atts.length > 0) {
3426
+ atts = await compactAttachments(atts, this.cfg.compactImageOpts);
3427
+ }
3428
+ const inputMsg = this.buildInput(message, loopID, atts, opts ?? void 0);
2838
3429
  try {
2839
3430
  await conn.client.sendMessage(inputMsg);
2840
3431
  } catch (err) {
@@ -2853,6 +3444,23 @@ var TurnRunner = class {
2853
3444
  }
2854
3445
  let assistantContent = "";
2855
3446
  const startedAt = Date.now();
3447
+ const idleForTurn = idleTimeoutForTurn(this.cfg, (attachments?.length ?? 0) > 0);
3448
+ let idleReject = null;
3449
+ const armIdle = () => {
3450
+ clearIdle();
3451
+ idleReject = null;
3452
+ if (idleForTurn <= 0) {
3453
+ return new Promise(() => {
3454
+ });
3455
+ }
3456
+ return new Promise((resolve) => {
3457
+ idleReject = () => resolve("idle");
3458
+ idleTimer = setTimeout(() => {
3459
+ idleReject?.();
3460
+ }, idleForTurn);
3461
+ });
3462
+ };
3463
+ let idleRace = armIdle();
2856
3464
  const abortRace = new Promise((resolve) => {
2857
3465
  const onTimeout = () => resolve("timeout");
2858
3466
  timeoutController.signal.addEventListener("abort", onTimeout, { once: true });
@@ -2866,34 +3474,71 @@ var TurnRunner = class {
2866
3474
  const next = iterator.next();
2867
3475
  const raced = await Promise.race([
2868
3476
  next.then((res2) => ({ tag: "msg", res: res2 })),
2869
- abortRace.then((tag) => ({ tag }))
3477
+ abortRace.then((tag) => ({ tag })),
3478
+ idleRace.then((tag) => ({ tag }))
2870
3479
  ]);
2871
3480
  if ("tag" in raced && raced.tag !== "msg") {
2872
3481
  if (raced.tag === "caller" || signal?.aborted) {
3482
+ clearIdle();
2873
3483
  const err = new Error("aborted");
2874
3484
  await this.persistFailed(sessionID, loopID, err);
2875
3485
  this.broadcastError(sessionID, err);
2876
3486
  this.onError?.(sessionID, loopID, err);
2877
3487
  throw err;
2878
3488
  }
3489
+ if (raced.tag === "idle") {
3490
+ clearIdle();
3491
+ await this.sendLoopCancel(new AbortController().signal, conn, loopID).catch(() => {
3492
+ });
3493
+ await this.finishTimeout(
3494
+ sessionID,
3495
+ loopID,
3496
+ assistantContent,
3497
+ startedAt,
3498
+ new ErrIdleTimeout(),
3499
+ "idle_timeout",
3500
+ this.cfg.onIdleTimeout ?? 0 /* Fail */
3501
+ );
3502
+ return;
3503
+ }
3504
+ clearIdle();
2879
3505
  await this.sendLoopCancel(new AbortController().signal, conn, loopID).catch(() => {
2880
3506
  });
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();
3507
+ await this.finishTimeout(
3508
+ sessionID,
3509
+ loopID,
3510
+ assistantContent,
3511
+ startedAt,
3512
+ new ErrQueryTimeout(),
3513
+ "query_timeout",
3514
+ this.cfg.onQueryTimeout ?? 0 /* Fail */
3515
+ );
3516
+ return;
2885
3517
  }
2886
3518
  const res = raced.res;
2887
3519
  if (res.done) {
3520
+ clearIdle();
3521
+ if ((this.cfg.onStreamClose ?? 0 /* Fail */) === 1 /* SoftComplete */ && assistantContent.trim() !== "") {
3522
+ await this.completeTurn(
3523
+ sessionID,
3524
+ loopID,
3525
+ assistantContent,
3526
+ startedAt,
3527
+ "stream_closed"
3528
+ );
3529
+ return;
3530
+ }
2888
3531
  const err = new Error("event stream closed");
2889
3532
  await this.persistFailed(sessionID, loopID, err);
2890
3533
  this.broadcastError(sessionID, err);
2891
3534
  this.onError?.(sessionID, loopID, err);
2892
3535
  throw err;
2893
3536
  }
3537
+ idleRace = armIdle();
2894
3538
  const msg = res.value;
2895
3539
  const eventResult = this.classifier.classify(msg, assistantContent);
2896
3540
  if (eventResult.err && eventResult.terminal === 2 /* FailedComplete */) {
3541
+ clearIdle();
2897
3542
  await this.persistFailed(sessionID, loopID, eventResult.err);
2898
3543
  this.broadcastError(sessionID, eventResult.err);
2899
3544
  this.onError?.(sessionID, loopID, eventResult.err);
@@ -2913,25 +3558,39 @@ var TurnRunner = class {
2913
3558
  assistantContent
2914
3559
  );
2915
3560
  if (deliverable) {
2916
- const elapsedMs = Date.now() - startedAt;
2917
- await this.persistResponse(
3561
+ clearIdle();
3562
+ await this.completeTurn(
2918
3563
  sessionID,
2919
3564
  loopID,
2920
3565
  final,
2921
3566
  startedAt,
2922
3567
  eventResult.completionEvent ?? ""
2923
3568
  );
2924
- this.broadcastComplete(sessionID, final);
2925
- this.onComplete?.(sessionID, loopID, final, eventResult.completionEvent ?? "", elapsedMs);
2926
3569
  return;
2927
3570
  }
2928
3571
  }
2929
3572
  } finally {
3573
+ clearIdle();
2930
3574
  clearTimeout(timer);
2931
3575
  this.gate.release(sessionID);
2932
3576
  }
2933
3577
  }
2934
- /** Asks the daemon to cooperatively stop the loop runner on a detached signal. */
3578
+ async finishTimeout(sessionID, loopID, content, startedAt, failErr, completionEvent, policy) {
3579
+ if (policy === 1 /* SoftComplete */ && content.trim() !== "") {
3580
+ await this.completeTurn(sessionID, loopID, content, startedAt, completionEvent);
3581
+ return;
3582
+ }
3583
+ await this.persistFailed(sessionID, loopID, failErr);
3584
+ this.broadcastError(sessionID, failErr);
3585
+ this.onError?.(sessionID, loopID, failErr);
3586
+ throw failErr;
3587
+ }
3588
+ async completeTurn(sessionID, loopID, final, startedAt, completionEvent) {
3589
+ const elapsedMs = Date.now() - startedAt;
3590
+ await this.persistResponse(sessionID, loopID, final, startedAt, completionEvent);
3591
+ this.broadcastComplete(sessionID, final);
3592
+ this.onComplete?.(sessionID, loopID, final, completionEvent, elapsedMs);
3593
+ }
2935
3594
  async sendLoopCancel(_signal, conn, loopID) {
2936
3595
  const lid = (loopID ?? "").trim();
2937
3596
  if (!conn || !lid) return;
@@ -2974,18 +3633,476 @@ var TurnRunner = class {
2974
3633
  this.broadcaster?.broadcast(sessionID, { type: "query_error", data: err.message });
2975
3634
  }
2976
3635
  };
3636
+
3637
+ // src/appkit/daemon_session.ts
3638
+ init_client();
3639
+ init_config();
3640
+ init_errors();
3641
+ init_stream_terminal();
3642
+
3643
+ // src/appkit/chunk_filter.ts
3644
+ var MSG_PAIR_LEN = 2;
3645
+ function updatesChunkIsNoop(data) {
3646
+ if (!data || typeof data !== "object") return true;
3647
+ return !("__interrupt__" in data);
3648
+ }
3649
+ function wireBody(msg) {
3650
+ for (const key of ["kwargs", "data"]) {
3651
+ const nested = msg[key];
3652
+ if (nested && typeof nested === "object") return nested;
3653
+ }
3654
+ return msg;
3655
+ }
3656
+ function dictHasToolInvocation(msg) {
3657
+ const body = wireBody(msg);
3658
+ if (body.tool_calls || body.tool_call_chunks) return true;
3659
+ for (const key of ["content", "content_blocks"]) {
3660
+ const raw = body[key];
3661
+ if (Array.isArray(raw)) {
3662
+ for (const item of raw) {
3663
+ if (item && typeof item === "object" && ["tool_call", "tool_call_chunk", "tool_use"].includes(
3664
+ String(item.type ?? "")
3665
+ )) {
3666
+ return true;
3667
+ }
3668
+ }
3669
+ }
3670
+ }
3671
+ return false;
3672
+ }
3673
+ function plainText(msg) {
3674
+ const body = wireBody(msg);
3675
+ const content = body.content ?? msg.content;
3676
+ if (typeof content === "string") return content;
3677
+ if (Array.isArray(content)) {
3678
+ const parts = [];
3679
+ for (const block of content) {
3680
+ if (typeof block === "string") parts.push(block);
3681
+ else if (block && typeof block === "object") {
3682
+ const text = block.text;
3683
+ if (typeof text === "string") parts.push(text);
3684
+ }
3685
+ }
3686
+ return parts.join("");
3687
+ }
3688
+ return "";
3689
+ }
3690
+ function messageChunkIsNonActionable(data) {
3691
+ if (!Array.isArray(data) || data.length !== MSG_PAIR_LEN) return false;
3692
+ const msg = data[0];
3693
+ if (msg === null || msg === void 0) return true;
3694
+ if (!msg || typeof msg !== "object") return false;
3695
+ const m = msg;
3696
+ const body = wireBody(m);
3697
+ const raw = String(body.type ?? m.type ?? "");
3698
+ if (raw === "tool" || raw === "ToolMessage" || raw.endsWith("ToolMessage")) return false;
3699
+ if (dictHasToolInvocation(m)) return false;
3700
+ if (body.phase || m.phase) return false;
3701
+ return !plainText(m).trim();
3702
+ }
3703
+ function shouldDropStreamChunkEarly(_namespace, mode, data) {
3704
+ if (mode === "updates") return updatesChunkIsNoop(data);
3705
+ if (mode === "messages") return messageChunkIsNonActionable(data);
3706
+ return false;
3707
+ }
3708
+
3709
+ // src/appkit/events.ts
3710
+ function unwrapNext(event) {
3711
+ if (!event || typeof event !== "object") return event;
3712
+ if (event.type !== "next") return event;
3713
+ const payload = event.payload;
3714
+ if (!payload || typeof payload !== "object") return event;
3715
+ const data = payload.data;
3716
+ return data && typeof data === "object" ? data : event;
3717
+ }
3718
+
3719
+ // src/appkit/observability.ts
3720
+ var TurnEventStats = class {
3721
+ total = 0;
3722
+ messages = 0;
3723
+ updates = 0;
3724
+ custom = 0;
3725
+ skipped = 0;
3726
+ filteredEarly = 0;
3727
+ toolCalls = 0;
3728
+ toolResults = 0;
3729
+ textChunks = 0;
3730
+ heartbeatsDropped = 0;
3731
+ postIdleDrained = 0;
3732
+ inboundDropped = 0;
3733
+ };
3734
+
3735
+ // src/appkit/daemon_session.ts
3736
+ var DEFAULT_POST_IDLE_DRAIN_MS = 500;
3737
+ var DaemonSession = class {
3738
+ wsUrl;
3739
+ workspace;
3740
+ streamDelivery;
3741
+ client;
3742
+ rpcClient;
3743
+ loopId = null;
3744
+ readBusy = false;
3745
+ rpcBusy = false;
3746
+ rpcConnected = false;
3747
+ streaming = false;
3748
+ postIdleDrainDeadlineMs;
3749
+ closed = false;
3750
+ earlyDropFn;
3751
+ statsFactory;
3752
+ config;
3753
+ turnEventStats;
3754
+ lastTurnEndState = null;
3755
+ lastTurnCancellationSeen = false;
3756
+ lastTurnErrorMessage = null;
3757
+ constructor(wsUrl, opts = {}) {
3758
+ this.wsUrl = wsUrl;
3759
+ this.workspace = opts.workspace;
3760
+ this.streamDelivery = opts.streamDelivery ?? "adaptive";
3761
+ this.config = opts.config ?? defaultConfig();
3762
+ this.client = new Client(wsUrl, this.config);
3763
+ this.rpcClient = new Client(wsUrl, this.config);
3764
+ this.postIdleDrainDeadlineMs = opts.postIdleDrainDeadlineMs && opts.postIdleDrainDeadlineMs > 0 ? opts.postIdleDrainDeadlineMs : DEFAULT_POST_IDLE_DRAIN_MS;
3765
+ this.earlyDropFn = opts.earlyDropFn ?? shouldDropStreamChunkEarly;
3766
+ this.statsFactory = opts.statsFactory ?? (() => new TurnEventStats());
3767
+ this.turnEventStats = this.statsFactory();
3768
+ }
3769
+ get streamClient() {
3770
+ return this.client;
3771
+ }
3772
+ get rpcSideClient() {
3773
+ return this.rpcClient;
3774
+ }
3775
+ get activeLoopId() {
3776
+ return this.loopId;
3777
+ }
3778
+ resolveStreamDeliveryMode() {
3779
+ const delivery = this.streamDelivery;
3780
+ if (typeof delivery === "function") return String(delivery() || "adaptive");
3781
+ return String(delivery || "adaptive");
3782
+ }
3783
+ get streamDeliveryMode() {
3784
+ return this.resolveStreamDeliveryMode();
3785
+ }
3786
+ shouldDrop(namespace, mode, data) {
3787
+ return Boolean(this.earlyDropFn(namespace, mode, data));
3788
+ }
3789
+ async connect(resumeLoopId) {
3790
+ await connectWithRetries(this.client);
3791
+ return this.bootstrapLoop(resumeLoopId ?? null);
3792
+ }
3793
+ async bootstrapLoop(resumeLoopId) {
3794
+ const loopNew = this.workspace ? { client_workspace: this.workspace, workspace: this.workspace } : void 0;
3795
+ const loopId = await bootstrapLoopSession(this.client, resumeLoopId, this.config, loopNew);
3796
+ this.loopId = loopId;
3797
+ return { type: "status", loop_id: loopId, state: "ready" };
3798
+ }
3799
+ async newLoop() {
3800
+ return this.bootstrapLoop(null);
3801
+ }
3802
+ async switchLoop(loopId) {
3803
+ return this.bootstrapLoop(loopId);
3804
+ }
3805
+ async ensureConnected() {
3806
+ if (this.client.isConnected() && !this.client.isDisconnected()) return;
3807
+ let resumeLoopId = this.loopId;
3808
+ if (this.rpcConnected) {
3809
+ this.rpcClient.close();
3810
+ this.rpcConnected = false;
3811
+ }
3812
+ try {
3813
+ await this.client.reconnect();
3814
+ } catch {
3815
+ this.client.close();
3816
+ await connectWithRetries(this.client);
3817
+ }
3818
+ if (resumeLoopId) {
3819
+ try {
3820
+ await this.client.reattachAndProbe(resumeLoopId);
3821
+ this.loopId = resumeLoopId;
3822
+ return;
3823
+ } catch (err) {
3824
+ if (!(err instanceof StaleLoopError)) throw err;
3825
+ resumeLoopId = null;
3826
+ }
3827
+ }
3828
+ await this.bootstrapLoop(resumeLoopId);
3829
+ }
3830
+ async close() {
3831
+ if (this.closed) return;
3832
+ this.closed = true;
3833
+ this.client.close();
3834
+ this.rpcClient.close();
3835
+ this.rpcConnected = false;
3836
+ }
3837
+ async detach() {
3838
+ if (!this.client.isConnected()) return;
3839
+ try {
3840
+ await this.client.notify("disconnect", {});
3841
+ } catch {
3842
+ }
3843
+ }
3844
+ async sendTurn(text, options) {
3845
+ if (!this.loopId) throw new Error("No active loop session");
3846
+ await this.client.sendInput(text, {
3847
+ loopID: this.loopId,
3848
+ autonomous: options?.autonomous,
3849
+ maxIterations: options?.maxIterations,
3850
+ subagent: options?.preferredSubagent,
3851
+ model: options?.model,
3852
+ modelParams: options?.modelParams,
3853
+ attachments: options?.attachments,
3854
+ clarificationMode: options?.clarificationMode,
3855
+ clarificationAnswer: options?.clarificationAnswer,
3856
+ intentHint: options?.intentHint
3857
+ });
3858
+ }
3859
+ async cancelActiveTurn() {
3860
+ await this.client.notify("slash_command", { cmd: "/cancel" });
3861
+ }
3862
+ async *drainStreamEventsAfterIdle(expectedLoopId) {
3863
+ const deadline = Date.now() + this.postIdleDrainDeadlineMs;
3864
+ let exp = expectedLoopId;
3865
+ while (Date.now() < deadline) {
3866
+ const event = await this.client.readEventWithTimeout(250);
3867
+ if (!event) break;
3868
+ let frame = event;
3869
+ let eventType = String(frame.type ?? "");
3870
+ if (eventType === "next") {
3871
+ frame = unwrapNext(frame) ?? frame;
3872
+ eventType = String(frame.type ?? "");
3873
+ }
3874
+ const eventLoopId = frame.loop_id;
3875
+ if (exp && typeof eventLoopId === "string" && eventLoopId && eventLoopId !== exp) {
3876
+ continue;
3877
+ }
3878
+ if (eventType === "error") {
3879
+ const errObj = frame.error ?? {};
3880
+ throw new Error(String(errObj.message || frame.message || "daemon error"));
3881
+ }
3882
+ if (eventType === "status") {
3883
+ const loopEv = frame.loop_id;
3884
+ if (typeof loopEv === "string" && loopEv) {
3885
+ this.loopId = loopEv;
3886
+ exp = loopEv;
3887
+ }
3888
+ continue;
3889
+ }
3890
+ if (eventType !== "event") continue;
3891
+ const data = frame.data;
3892
+ const namespace = Array.isArray(frame.namespace) ? frame.namespace : [];
3893
+ const mode = String(frame.mode ?? "");
3894
+ if (this.shouldDrop(namespace, mode, data)) {
3895
+ this.turnEventStats.filteredEarly += 1;
3896
+ continue;
3897
+ }
3898
+ this.turnEventStats.postIdleDrained += 1;
3899
+ yield [namespace, mode, data];
3900
+ }
3901
+ }
3902
+ async withRpcLock(fn) {
3903
+ while (this.rpcBusy) {
3904
+ await new Promise((r) => setTimeout(r, 5));
3905
+ }
3906
+ this.rpcBusy = true;
3907
+ try {
3908
+ return await fn();
3909
+ } finally {
3910
+ this.rpcBusy = false;
3911
+ }
3912
+ }
3913
+ async ensureRpcConnected() {
3914
+ if (this.rpcConnected && this.rpcClient.isConnected()) return;
3915
+ await connectWithRetries(this.rpcClient);
3916
+ this.rpcConnected = true;
3917
+ }
3918
+ async listLoops(_limit = 20) {
3919
+ return this.withRpcLock(async () => {
3920
+ await this.ensureRpcConnected();
3921
+ return this.rpcClient.listLoops(15e3);
3922
+ });
3923
+ }
3924
+ async fetchLoopCards(loopId) {
3925
+ const lid = String(loopId || "").trim();
3926
+ if (!lid) return { cards: [], seq: 0, contextTokens: 0, success: false };
3927
+ return this.withRpcLock(async () => {
3928
+ await this.ensureRpcConnected();
3929
+ try {
3930
+ const resp = await this.rpcClient.fetchLoopCards(lid, 3e4);
3931
+ const rawCards = resp.cards;
3932
+ return {
3933
+ cards: Array.isArray(rawCards) ? rawCards : [],
3934
+ seq: Number(resp.seq ?? 0),
3935
+ contextTokens: typeof resp.context_tokens === "number" && resp.context_tokens >= 0 ? resp.context_tokens : 0,
3936
+ success: true
3937
+ };
3938
+ } catch {
3939
+ return { cards: [], seq: 0, contextTokens: 0, success: false };
3940
+ }
3941
+ });
3942
+ }
3943
+ async fetchLoopHistory(loopId) {
3944
+ const lid = String(loopId || "").trim();
3945
+ if (!lid) {
3946
+ return { goals: [], liveCards: [], liveGoalIndex: null, contextTokens: 0, success: false };
3947
+ }
3948
+ return this.withRpcLock(async () => {
3949
+ await this.ensureRpcConnected();
3950
+ try {
3951
+ const resp = await this.rpcClient.fetchLoopHistory(lid, 3e4);
3952
+ const liveGoalIndex = resp.live_goal_index;
3953
+ return {
3954
+ goals: Array.isArray(resp.goals) ? resp.goals : [],
3955
+ liveCards: Array.isArray(resp.live_cards) ? resp.live_cards : [],
3956
+ liveGoalIndex: typeof liveGoalIndex === "number" ? liveGoalIndex : null,
3957
+ contextTokens: typeof resp.context_tokens === "number" && resp.context_tokens >= 0 ? resp.context_tokens : 0,
3958
+ success: Boolean(resp.success ?? true)
3959
+ };
3960
+ } catch {
3961
+ return {
3962
+ goals: [],
3963
+ liveCards: [],
3964
+ liveGoalIndex: null,
3965
+ contextTokens: 0,
3966
+ success: false
3967
+ };
3968
+ }
3969
+ });
3970
+ }
3971
+ async fetchConversationLog(loopId, opts = {}) {
3972
+ const lid = String(loopId || "").trim();
3973
+ if (!lid) return [];
3974
+ return this.withRpcLock(async () => {
3975
+ await this.ensureRpcConnected();
3976
+ const resp = await this.rpcClient.getLoopMessages(
3977
+ lid,
3978
+ opts.limit ?? 100,
3979
+ opts.offset ?? 0,
3980
+ opts.includeEvents ?? false
3981
+ );
3982
+ const raw = resp.messages;
3983
+ if (!Array.isArray(raw)) return [];
3984
+ return raw.filter((m) => !!m && typeof m === "object");
3985
+ });
3986
+ }
3987
+ async *iterTurnChunks(opts = {}) {
3988
+ this.turnEventStats = this.statsFactory();
3989
+ this.lastTurnEndState = null;
3990
+ this.lastTurnCancellationSeen = false;
3991
+ this.lastTurnErrorMessage = null;
3992
+ let queryStarted = false;
3993
+ let expectedLoopId = this.loopId;
3994
+ let streamPayloadSeen = false;
3995
+ let turnProgressSeen = false;
3996
+ this.streaming = true;
3997
+ const absoluteDeadline = opts.maxWaitMs !== void 0 && opts.maxWaitMs > 0 ? Date.now() + opts.maxWaitMs : null;
3998
+ this.client.peelStalePendingControlEvents();
3999
+ while (this.readBusy) {
4000
+ await new Promise((r) => setTimeout(r, 5));
4001
+ }
4002
+ this.readBusy = true;
4003
+ try {
4004
+ while (true) {
4005
+ if (absoluteDeadline !== null && Date.now() >= absoluteDeadline) {
4006
+ throw new Error(
4007
+ `Turn timed out after ${opts.maxWaitMs}ms (loop=${expectedLoopId ?? "?"})`
4008
+ );
4009
+ }
4010
+ const event = await this.client.readEvent();
4011
+ if (!event) {
4012
+ if (queryStarted && !this.client.isConnectionAlive()) {
4013
+ this.lastTurnEndState = "connection_lost";
4014
+ throw new Error("Daemon connection lost");
4015
+ }
4016
+ break;
4017
+ }
4018
+ let frame = event;
4019
+ let eventType = String(frame.type ?? "");
4020
+ if (eventType === "next") {
4021
+ frame = unwrapNext(frame) ?? frame;
4022
+ eventType = String(frame.type ?? "");
4023
+ }
4024
+ const eventLoopId = frame.loop_id;
4025
+ if (expectedLoopId && typeof eventLoopId === "string" && eventLoopId && eventLoopId !== expectedLoopId) {
4026
+ continue;
4027
+ }
4028
+ if (eventType === "error") {
4029
+ const errObj = frame.error ?? {};
4030
+ throw new Error(String(errObj.message || frame.message || "daemon error"));
4031
+ }
4032
+ if (eventType === "status") {
4033
+ const loopEv = frame.loop_id;
4034
+ if (typeof loopEv === "string" && loopEv) {
4035
+ this.loopId = loopEv;
4036
+ expectedLoopId = loopEv;
4037
+ }
4038
+ const state = String(frame.state ?? "");
4039
+ if (state === "running") {
4040
+ queryStarted = true;
4041
+ } else if (queryStarted && state === "stopped") {
4042
+ this.lastTurnEndState = state;
4043
+ yield* this.drainStreamEventsAfterIdle(expectedLoopId);
4044
+ break;
4045
+ } else if (queryStarted && state === "idle") {
4046
+ if (!streamPayloadSeen && !this.lastTurnCancellationSeen) continue;
4047
+ this.lastTurnEndState = state;
4048
+ yield* this.drainStreamEventsAfterIdle(expectedLoopId);
4049
+ break;
4050
+ }
4051
+ continue;
4052
+ }
4053
+ if (eventType === "command_response") {
4054
+ const content = String(frame.content ?? "");
4055
+ if (content.includes("Cancellation requested")) {
4056
+ this.lastTurnCancellationSeen = true;
4057
+ }
4058
+ continue;
4059
+ }
4060
+ if (eventType !== "event") continue;
4061
+ const data = frame.data;
4062
+ const namespace = Array.isArray(frame.namespace) ? frame.namespace : [];
4063
+ const mode = String(frame.mode ?? "");
4064
+ if (this.shouldDrop(namespace, mode, data)) {
4065
+ this.turnEventStats.filteredEarly += 1;
4066
+ continue;
4067
+ }
4068
+ if (mode === "custom" && isTurnEndCustomData(data)) {
4069
+ if (!queryStarted || !turnProgressSeen) continue;
4070
+ }
4071
+ streamPayloadSeen = true;
4072
+ if (isTurnProgressChunk(mode, data)) turnProgressSeen = true;
4073
+ yield [namespace, mode, data];
4074
+ if (mode === "custom" && isTurnEndCustomData(data)) {
4075
+ const customType = String(data.type ?? "").trim();
4076
+ this.lastTurnEndState = customType === STREAM_END ? "stream_end" : "completed";
4077
+ yield* this.drainStreamEventsAfterIdle(expectedLoopId);
4078
+ break;
4079
+ }
4080
+ }
4081
+ } catch (exc) {
4082
+ this.lastTurnErrorMessage = String(exc);
4083
+ throw exc;
4084
+ } finally {
4085
+ this.streaming = false;
4086
+ this.readBusy = false;
4087
+ }
4088
+ }
4089
+ };
2977
4090
  // Annotate the CommonJS export names for ESM import in node:
2978
4091
  0 && (module.exports = {
2979
4092
  CLIENT_VERSION,
2980
4093
  ChatEventTerminal,
2981
4094
  Client,
4095
+ CommandClient,
2982
4096
  ConnectionError,
2983
4097
  ConnectionPool,
2984
4098
  DEFAULT_CLIENT_CAPABILITIES,
2985
4099
  DEFAULT_DELIVERABLE_PHASES,
4100
+ DEFAULT_POST_IDLE_DRAIN_MS,
2986
4101
  DEFAULT_THINKING_STEP_EVENTS,
2987
4102
  DaemonError,
4103
+ DaemonSession,
2988
4104
  DisconnectCause,
4105
+ ErrIdleTimeout,
2989
4106
  ErrPoolExhausted,
2990
4107
  ErrQueryBusy,
2991
4108
  ErrQueryTimeout,
@@ -3031,26 +4148,31 @@ var TurnRunner = class {
3031
4148
  INTENT_HINT_OCR,
3032
4149
  INTENT_HINT_TEXT_COMPLETION,
3033
4150
  LOOP_ASSISTANT_OUTPUT_PHASES,
3034
- Multiplexer,
3035
4151
  PROTO_VERSION,
3036
4152
  PooledConn,
3037
4153
  QueryGate,
3038
4154
  REMOVED_INTENT_HINTS,
3039
4155
  ReconnectError,
3040
4156
  SSEBroadcaster,
4157
+ STREAM_END,
3041
4158
  StaleLoopError,
4159
+ StreamCloseFail,
4160
+ StreamCloseSoftComplete,
3042
4161
  TimeoutError,
4162
+ TimeoutPolicy,
4163
+ TurnEventStats,
3043
4164
  TurnRunner,
3044
4165
  VerbosityTier,
3045
4166
  authenticate,
3046
4167
  bootstrapLoopSession,
3047
4168
  checkDaemonStatus,
3048
4169
  classifyEventVerbosity,
4170
+ compactAttachments,
4171
+ compactImageAttachment,
3049
4172
  connectWithRetries,
4173
+ connectedWebsocket,
3050
4174
  connectionInitEnvelope,
3051
4175
  decodeMessage,
3052
- defaultBootstrapFunc,
3053
- defaultClientFactory,
3054
4176
  defaultConfig,
3055
4177
  defaultPoolConfig,
3056
4178
  disconnectCauseName,
@@ -3059,12 +4181,18 @@ var TurnRunner = class {
3059
4181
  extractSootheLoopID,
3060
4182
  extractThinkingStep,
3061
4183
  fetchConfigSection,
4184
+ fetchLoopCards,
3062
4185
  fetchLoopHistory,
4186
+ fetchLoopMessages,
3063
4187
  fetchSkillsCatalog,
4188
+ idleTimeoutForTurn,
4189
+ inboundNeedsDeliveryAck,
3064
4190
  inputMessageForLoop,
3065
4191
  isCompletionEvent,
3066
4192
  isDaemonLive,
3067
4193
  isSubagentProgressEvent,
4194
+ isTurnEndCustomData,
4195
+ isTurnProgressChunk,
3068
4196
  isValidVerbosityLevel,
3069
4197
  loadConfigFromEnv,
3070
4198
  newLoopInputMessage,
@@ -3075,6 +4203,7 @@ var TurnRunner = class {
3075
4203
  parseNamespace,
3076
4204
  pingEnvelope,
3077
4205
  pongEnvelope,
4206
+ protocol1Rpc,
3078
4207
  refreshAuthToken,
3079
4208
  requestDaemonConfigReload,
3080
4209
  requestDaemonShutdown,