@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/README.md +59 -43
- package/dist/{chunk-AQZACDIC.js → chunk-U6RMINYV.js} +489 -5
- package/dist/chunk-U6RMINYV.js.map +1 -0
- package/dist/client-UNPC32NQ.js +7 -0
- package/dist/index.cjs +1402 -273
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +268 -99
- package/dist/index.d.ts +268 -99
- package/dist/index.js +939 -258
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
- package/dist/chunk-AQZACDIC.js.map +0 -1
- package/dist/client-CB6WKQYW.js +0 -7
- /package/dist/{client-CB6WKQYW.js.map → client-UNPC32NQ.js.map} +0 -0
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.
|
|
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.
|
|
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.
|
|
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/
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
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
|
-
|
|
1639
|
-
|
|
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
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
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
|
|
1668
|
-
const
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
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
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
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
|
-
|
|
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
|
|
1691
|
-
const
|
|
1692
|
-
|
|
1693
|
-
|
|
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
|
-
|
|
2142
|
+
throw new Error(
|
|
2143
|
+
`failed to connect after ${retries} attempts: ${lastErr?.message ?? "unknown error"}`
|
|
2144
|
+
);
|
|
1696
2145
|
}
|
|
1697
2146
|
|
|
1698
|
-
// src/
|
|
1699
|
-
|
|
1700
|
-
|
|
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
|
-
|
|
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
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
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
|
|
1828
|
-
const
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
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 (
|
|
1839
|
-
|
|
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
|
|
1848
|
-
const
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 === "
|
|
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
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
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 = {
|
|
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
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
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.
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
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
|
-
|
|
2917
|
-
await this.
|
|
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
|
-
|
|
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,
|