@mirasoth/soothe-client 0.2.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -47
- package/dist/{chunk-AQZACDIC.js → chunk-YYUVHZ3W.js} +607 -31
- package/dist/chunk-YYUVHZ3W.js.map +1 -0
- package/dist/client-SOJZSF7C.js +7 -0
- package/dist/index.cjs +1568 -298
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +355 -136
- package/dist/index.d.ts +355 -136
- package/dist/index.js +988 -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-SOJZSF7C.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -6,17 +6,55 @@ import {
|
|
|
6
6
|
DEFAULT_DELIVERABLE_PHASES,
|
|
7
7
|
DaemonError,
|
|
8
8
|
DisconnectCause,
|
|
9
|
+
EventAutopilotGoalCompleted,
|
|
10
|
+
EventAutopilotGoalCreated,
|
|
11
|
+
EventAutopilotGoalProgress,
|
|
12
|
+
EventAutopilotGoalStatus,
|
|
13
|
+
EventAutopilotWorkerAssigned,
|
|
14
|
+
EventAutopilotWorkerUnassigned,
|
|
15
|
+
EventCardCreated,
|
|
16
|
+
EventCardReplayBegin,
|
|
17
|
+
EventCardReplayEnd,
|
|
18
|
+
EventExploreCompleted,
|
|
19
|
+
EventExploreMilestone,
|
|
20
|
+
EventExploreStarted,
|
|
21
|
+
EventExploreStepCompleted,
|
|
22
|
+
EventFinalReport,
|
|
23
|
+
EventGeneralFailed,
|
|
24
|
+
EventLoopReattachedWire,
|
|
25
|
+
EventMessageReceived,
|
|
26
|
+
EventMessageSent,
|
|
27
|
+
EventPlanCreated,
|
|
28
|
+
EventReplayComplete,
|
|
29
|
+
EventStrangeLoopCompleted,
|
|
30
|
+
EventStrangeLoopContextCompacted,
|
|
31
|
+
EventStrangeLoopPlanDecision,
|
|
32
|
+
EventStrangeLoopReasoned,
|
|
33
|
+
EventStrangeLoopStarted,
|
|
34
|
+
EventStrangeLoopStepCompleted,
|
|
35
|
+
EventStrangeLoopStepQueued,
|
|
36
|
+
EventStrangeLoopStepStarted,
|
|
37
|
+
EventStreamToolCallUpdate,
|
|
38
|
+
EventTacitusCompleted,
|
|
39
|
+
EventTacitusGatherSummary,
|
|
40
|
+
EventTacitusStarted,
|
|
41
|
+
EventToolCallUpdatesBatch,
|
|
42
|
+
EventToolCompleted,
|
|
43
|
+
EventToolError,
|
|
44
|
+
EventToolStarted,
|
|
9
45
|
INTENT_HINT_EMBED,
|
|
10
46
|
INTENT_HINT_IMAGE_TO_TEXT,
|
|
11
47
|
INTENT_HINT_OCR,
|
|
12
48
|
INTENT_HINT_TEXT_COMPLETION,
|
|
13
49
|
LOOP_ASSISTANT_OUTPUT_PHASES,
|
|
14
|
-
Multiplexer,
|
|
15
50
|
PROTO_VERSION,
|
|
16
51
|
REMOVED_INTENT_HINTS,
|
|
17
52
|
ReconnectError,
|
|
53
|
+
STREAM_END,
|
|
18
54
|
StaleLoopError,
|
|
19
55
|
TimeoutError,
|
|
56
|
+
VerbosityTier,
|
|
57
|
+
classifyEventVerbosity,
|
|
20
58
|
connectionInitEnvelope,
|
|
21
59
|
decodeMessage,
|
|
22
60
|
defaultConfig,
|
|
@@ -24,168 +62,231 @@ import {
|
|
|
24
62
|
disconnectEnvelope,
|
|
25
63
|
encodeMessage,
|
|
26
64
|
extractSootheLoopID,
|
|
65
|
+
inboundNeedsDeliveryAck,
|
|
66
|
+
isCompletionEvent,
|
|
67
|
+
isSubagentProgressEvent,
|
|
68
|
+
isTurnEndCustomData,
|
|
69
|
+
isTurnProgressChunk,
|
|
70
|
+
isValidVerbosityLevel,
|
|
27
71
|
loadConfigFromEnv,
|
|
28
72
|
newLoopInputMessage,
|
|
29
73
|
newLoopNewMessage,
|
|
30
74
|
newLoopSubscribeMessage,
|
|
31
75
|
newRequestID,
|
|
32
76
|
notificationEnvelope,
|
|
77
|
+
parseNamespace,
|
|
33
78
|
pingEnvelope,
|
|
34
79
|
pongEnvelope,
|
|
35
80
|
requestEnvelope,
|
|
81
|
+
shouldShow,
|
|
36
82
|
splitWirePayload,
|
|
37
83
|
subscribeEnvelope,
|
|
38
84
|
unsubscribeEnvelope,
|
|
39
85
|
validateLoopInputIntentHint
|
|
40
|
-
} from "./chunk-
|
|
86
|
+
} from "./chunk-YYUVHZ3W.js";
|
|
41
87
|
|
|
42
|
-
// src/
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return false;
|
|
88
|
+
// src/session.ts
|
|
89
|
+
async function bootstrapLoopSession(client, resumeLoopId, config, loopNew) {
|
|
90
|
+
const cfg = config ?? defaultConfig();
|
|
91
|
+
let loopId = (resumeLoopId ?? "").trim();
|
|
92
|
+
if (!loopId) {
|
|
93
|
+
const env = newLoopNewMessage(loopNew);
|
|
94
|
+
const newResp = await client.requestResponse(
|
|
95
|
+
env.method,
|
|
96
|
+
env.params ?? {},
|
|
97
|
+
"loop_new",
|
|
98
|
+
cfg.loopStatusTimeout
|
|
99
|
+
);
|
|
100
|
+
loopId = String(newResp.loop_id ?? "").trim();
|
|
101
|
+
if (!loopId) {
|
|
102
|
+
throw new Error("loop_new response missing loop_id");
|
|
103
|
+
}
|
|
59
104
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
105
|
+
await client.subscribe(
|
|
106
|
+
"loop_events",
|
|
107
|
+
{ loop_id: loopId, verbosity: cfg.verbosityLevel },
|
|
108
|
+
cfg.subscriptionTimeout
|
|
109
|
+
);
|
|
110
|
+
return loopId;
|
|
65
111
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
var EventCardReplayEnd = "card.replay_end";
|
|
81
|
-
var EventToolStarted = "soothe.tool.execution.started";
|
|
82
|
-
var EventToolCompleted = "soothe.tool.execution.completed";
|
|
83
|
-
var EventToolError = "soothe.tool.execution.error";
|
|
84
|
-
var EventStreamToolCallUpdate = "soothe.stream.tool_call.update";
|
|
85
|
-
var EventToolCallUpdatesBatch = "tool_call_updates_batch";
|
|
86
|
-
var EventStrangeLoopStarted = "soothe.cognition.strange_loop.started";
|
|
87
|
-
var EventStrangeLoopCompleted = "soothe.cognition.strange_loop.completed";
|
|
88
|
-
var EventStrangeLoopPlanDecision = "soothe.cognition.strange_loop.plan.decision";
|
|
89
|
-
var EventStrangeLoopReasoned = "soothe.cognition.strange_loop.reasoned";
|
|
90
|
-
var EventStrangeLoopStepStarted = "soothe.cognition.strange_loop.step.started";
|
|
91
|
-
var EventStrangeLoopStepQueued = "soothe.cognition.strange_loop.step.queued";
|
|
92
|
-
var EventStrangeLoopStepCompleted = "soothe.cognition.strange_loop.step.completed";
|
|
93
|
-
var EventStrangeLoopContextCompacted = "soothe.cognition.strange_loop.context.compacted";
|
|
94
|
-
var EventMessageReceived = "soothe.protocol.message.received";
|
|
95
|
-
var EventMessageSent = "soothe.protocol.message.sent";
|
|
96
|
-
var EventFinalReport = "soothe.output.autonomous.final_report.reported";
|
|
97
|
-
var EventAutopilotGoalStatus = "soothe.autopilot.goal.status";
|
|
98
|
-
var EventAutopilotGoalProgress = "soothe.autopilot.goal.progress";
|
|
99
|
-
var EventAutopilotGoalCreated = "soothe.autopilot.goal.created";
|
|
100
|
-
var EventAutopilotGoalCompleted = "soothe.autopilot.goal.completed";
|
|
101
|
-
var EventAutopilotWorkerAssigned = "soothe.autopilot.worker.assigned";
|
|
102
|
-
var EventAutopilotWorkerUnassigned = "soothe.autopilot.worker.unassigned";
|
|
103
|
-
var EventGeneralFailed = "soothe.error.general.failed";
|
|
104
|
-
function parseNamespace(ns) {
|
|
105
|
-
const parts = splitNamespace(ns);
|
|
106
|
-
if (parts.length < 4 || parts[0] !== "soothe") {
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
if (parts[1] === "internal") {
|
|
110
|
-
return null;
|
|
112
|
+
async function waitDaemonReady(client, timeout) {
|
|
113
|
+
if (client.isConnected()) return;
|
|
114
|
+
const deadline = Date.now() + timeout;
|
|
115
|
+
while (Date.now() < deadline) {
|
|
116
|
+
const remaining = deadline - Date.now();
|
|
117
|
+
if (remaining <= 0) break;
|
|
118
|
+
const ev = await client.readEventWithTimeout(remaining);
|
|
119
|
+
if (ev === null) break;
|
|
120
|
+
if (ev.type === "connection_ack") {
|
|
121
|
+
const result = ev.result ?? {};
|
|
122
|
+
const state = result.readiness_state;
|
|
123
|
+
if (state === "ready") return;
|
|
124
|
+
throw new Error(`daemon not ready: state=${JSON.stringify(state ?? "unknown")}`);
|
|
125
|
+
}
|
|
111
126
|
}
|
|
112
|
-
|
|
127
|
+
throw new Error(`timeout after ${timeout}ms waiting for connection_ack (ready)`);
|
|
113
128
|
}
|
|
114
|
-
function
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
async function waitLoopStatusWithID(client, timeout) {
|
|
130
|
+
const deadline = Date.now() + timeout;
|
|
131
|
+
while (Date.now() < deadline) {
|
|
132
|
+
const remaining = deadline - Date.now();
|
|
133
|
+
if (remaining <= 0) break;
|
|
134
|
+
const ev = await client.readEventWithTimeout(remaining);
|
|
135
|
+
if (ev === null) break;
|
|
136
|
+
if (ev.type === "error") {
|
|
137
|
+
const errObj = ev.error ?? {};
|
|
138
|
+
throw new DaemonError(errObj.code ?? -32603, errObj.message ?? "daemon error");
|
|
139
|
+
}
|
|
140
|
+
if (ev.type === "status") {
|
|
141
|
+
const lid = ev.loop_id;
|
|
142
|
+
if (lid && lid !== "") {
|
|
143
|
+
return ev;
|
|
144
|
+
}
|
|
121
145
|
}
|
|
122
146
|
}
|
|
123
|
-
|
|
124
|
-
return parts;
|
|
147
|
+
throw new Error(`timeout after ${timeout}ms waiting for status with loop_id`);
|
|
125
148
|
}
|
|
126
|
-
function
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
149
|
+
async function waitSubscriptionConfirmed(client, wantLoopID, _wantVerbosity, timeout) {
|
|
150
|
+
const deadline = Date.now() + timeout;
|
|
151
|
+
while (Date.now() < deadline) {
|
|
152
|
+
const remaining = deadline - Date.now();
|
|
153
|
+
if (remaining <= 0) break;
|
|
154
|
+
const ev = await client.readEventWithTimeout(remaining);
|
|
155
|
+
if (ev === null) break;
|
|
156
|
+
if (ev.type === "next") {
|
|
157
|
+
const payload = ev.payload ?? {};
|
|
158
|
+
const lid = String(payload.loop_id ?? "");
|
|
159
|
+
if (lid === wantLoopID && payload.success === true) return;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (ev.type === "error") {
|
|
163
|
+
const errObj = ev.error ?? {};
|
|
164
|
+
throw new Error(`daemon error: ${errObj.message ?? "subscription failed"}`);
|
|
165
|
+
}
|
|
130
166
|
}
|
|
131
|
-
|
|
167
|
+
throw new Error(`timeout after ${timeout}ms waiting for subscription confirmation`);
|
|
132
168
|
}
|
|
133
|
-
function
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
case "output":
|
|
146
|
-
case "error":
|
|
147
|
-
return 0 /* Quiet */;
|
|
148
|
-
default:
|
|
149
|
-
return 1 /* Normal */;
|
|
169
|
+
async function connectWithRetries(client, maxRetries, retryDelay) {
|
|
170
|
+
const retries = maxRetries && maxRetries > 0 ? maxRetries : 40;
|
|
171
|
+
const delay = retryDelay && retryDelay > 0 ? retryDelay : 250;
|
|
172
|
+
let lastErr = null;
|
|
173
|
+
for (let attempt = 0; attempt < retries; attempt++) {
|
|
174
|
+
try {
|
|
175
|
+
await client.connect();
|
|
176
|
+
return;
|
|
177
|
+
} catch (err) {
|
|
178
|
+
lastErr = err;
|
|
179
|
+
}
|
|
180
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
150
181
|
}
|
|
182
|
+
throw new Error(
|
|
183
|
+
`failed to connect after ${retries} attempts: ${lastErr?.message ?? "unknown error"}`
|
|
184
|
+
);
|
|
151
185
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
186
|
+
|
|
187
|
+
// src/command_client.ts
|
|
188
|
+
var CommandClient = class {
|
|
189
|
+
url;
|
|
190
|
+
timeoutMs;
|
|
191
|
+
config;
|
|
192
|
+
constructor(url, opts) {
|
|
193
|
+
this.url = url;
|
|
194
|
+
this.timeoutMs = opts?.timeoutMs && opts.timeoutMs > 0 ? opts.timeoutMs : 3e4;
|
|
195
|
+
this.config = opts?.config ?? defaultConfig();
|
|
161
196
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
197
|
+
async withClient(fn) {
|
|
198
|
+
const client = new Client(this.url, this.config);
|
|
199
|
+
try {
|
|
200
|
+
await connectWithRetries(client, 5, 250);
|
|
201
|
+
return await fn(client);
|
|
202
|
+
} finally {
|
|
203
|
+
client.close();
|
|
204
|
+
}
|
|
166
205
|
}
|
|
167
|
-
|
|
168
|
-
|
|
206
|
+
/** Generic one-shot RPC. */
|
|
207
|
+
async request(method, params = {}) {
|
|
208
|
+
return this.withClient(
|
|
209
|
+
(client) => client.requestResponse(method, params, void 0, this.timeoutMs)
|
|
210
|
+
);
|
|
169
211
|
}
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
function isSubagentProgressEvent(eventType) {
|
|
176
|
-
const parsed = parseNamespace(eventType);
|
|
177
|
-
if (!parsed || parsed.domain !== "subagent") {
|
|
178
|
-
return false;
|
|
212
|
+
async jobCreate(goal, workspace = "") {
|
|
213
|
+
const params = { goal };
|
|
214
|
+
if (workspace) params.workspace = workspace;
|
|
215
|
+
return this.request("job_create", params);
|
|
179
216
|
}
|
|
180
|
-
|
|
181
|
-
}
|
|
217
|
+
async jobStatus(jobId) {
|
|
218
|
+
return this.request("job_status", { job_id: jobId });
|
|
219
|
+
}
|
|
220
|
+
async jobCancel(jobId) {
|
|
221
|
+
return this.request("job_cancel", { job_id: jobId });
|
|
222
|
+
}
|
|
223
|
+
/** Return autopilot scheduler status (running / dreaming / pool). */
|
|
224
|
+
async autopilotStatus() {
|
|
225
|
+
return this.request("autopilot_status");
|
|
226
|
+
}
|
|
227
|
+
/** Submit a new autopilot goal (returns goal_id). */
|
|
228
|
+
async autopilotSubmit(description, opts) {
|
|
229
|
+
const params = {
|
|
230
|
+
description,
|
|
231
|
+
priority: opts?.priority ?? 50
|
|
232
|
+
};
|
|
233
|
+
if (opts?.workspace) params.workspace = opts.workspace;
|
|
234
|
+
return this.request("autopilot_submit", params);
|
|
235
|
+
}
|
|
236
|
+
/** List all goals (including non-root children). */
|
|
237
|
+
async autopilotListGoals() {
|
|
238
|
+
return this.request("autopilot_list_goals");
|
|
239
|
+
}
|
|
240
|
+
/** Fetch one goal by id. */
|
|
241
|
+
async autopilotGetGoal(goalId) {
|
|
242
|
+
return this.request("autopilot_get_goal", { goal_id: goalId });
|
|
243
|
+
}
|
|
244
|
+
/** Cancel a goal and its non-terminal descendants. */
|
|
245
|
+
async autopilotCancelGoal(goalId) {
|
|
246
|
+
return this.request("autopilot_cancel_goal", { goal_id: goalId });
|
|
247
|
+
}
|
|
248
|
+
/** Cancel every open (non-terminal) goal. */
|
|
249
|
+
async autopilotCancelAll() {
|
|
250
|
+
return this.request("autopilot_cancel_all");
|
|
251
|
+
}
|
|
252
|
+
/** Exit dreaming mode and resume scheduling. */
|
|
253
|
+
async autopilotWake() {
|
|
254
|
+
return this.request("autopilot_wake");
|
|
255
|
+
}
|
|
256
|
+
/** Force dreaming mode. */
|
|
257
|
+
async autopilotDream() {
|
|
258
|
+
return this.request("autopilot_dream");
|
|
259
|
+
}
|
|
260
|
+
/** Resume a suspended or blocked goal. */
|
|
261
|
+
async autopilotResume(goalId) {
|
|
262
|
+
return this.request("autopilot_resume", { goal_id: goalId });
|
|
263
|
+
}
|
|
264
|
+
/** List root goals only (jobs). Prefer job* for job control. */
|
|
265
|
+
async autopilotListJobs() {
|
|
266
|
+
return this.request("autopilot_list_jobs");
|
|
267
|
+
}
|
|
268
|
+
/** Get a root job with DAG snapshot. Prefer jobStatus / getJobDag. */
|
|
269
|
+
async autopilotGetJob(jobId) {
|
|
270
|
+
return this.request("autopilot_get_job", { job_id: jobId });
|
|
271
|
+
}
|
|
272
|
+
async cronAdd(text, priority = 0) {
|
|
273
|
+
const params = { text };
|
|
274
|
+
if (priority > 0) params.priority = priority;
|
|
275
|
+
return this.request("cron_add", params);
|
|
276
|
+
}
|
|
277
|
+
async cronList(status = "") {
|
|
278
|
+
const params = {};
|
|
279
|
+
if (status) params.status = status;
|
|
280
|
+
return this.request("cron_list", params);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
182
283
|
|
|
183
284
|
// src/helpers.ts
|
|
184
285
|
async function checkDaemonStatus(client, timeout) {
|
|
185
286
|
return client.requestResponse("daemon_status", {}, "daemon_status", timeout ?? 5e3);
|
|
186
287
|
}
|
|
187
288
|
async function isDaemonLive(wsURL, timeout) {
|
|
188
|
-
const { Client: Client2 } = await import("./client-
|
|
289
|
+
const { Client: Client2 } = await import("./client-SOJZSF7C.js");
|
|
189
290
|
const t = timeout ?? 5e3;
|
|
190
291
|
const client = new Client2(wsURL, defaultConfig());
|
|
191
292
|
try {
|
|
@@ -259,104 +360,74 @@ async function refreshAuthToken(client, refreshToken, timeout) {
|
|
|
259
360
|
timeout ?? 15e3
|
|
260
361
|
);
|
|
261
362
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
async function bootstrapLoopSession(client, resumeLoopId, config, loopNew) {
|
|
265
|
-
const cfg = config ?? defaultConfig();
|
|
266
|
-
let loopId = (resumeLoopId ?? "").trim();
|
|
267
|
-
if (!loopId) {
|
|
268
|
-
const env = newLoopNewMessage(loopNew);
|
|
269
|
-
const newResp = await client.requestResponse(
|
|
270
|
-
env.method,
|
|
271
|
-
env.params ?? {},
|
|
272
|
-
"loop_new",
|
|
273
|
-
cfg.loopStatusTimeout
|
|
274
|
-
);
|
|
275
|
-
loopId = String(newResp.loop_id ?? "").trim();
|
|
276
|
-
if (!loopId) {
|
|
277
|
-
throw new Error("loop_new response missing loop_id");
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
await client.subscribe(
|
|
281
|
-
"loop_events",
|
|
282
|
-
{ loop_id: loopId, verbosity: cfg.verbosityLevel },
|
|
283
|
-
cfg.subscriptionTimeout
|
|
284
|
-
);
|
|
285
|
-
return loopId;
|
|
363
|
+
async function fetchLoopCards(client, loopID, timeout) {
|
|
364
|
+
return client.fetchLoopCards(loopID, timeout);
|
|
286
365
|
}
|
|
287
|
-
async function
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (ev.type === "connection_ack") {
|
|
296
|
-
const result = ev.result ?? {};
|
|
297
|
-
const state = result.readiness_state;
|
|
298
|
-
if (state === "ready") return;
|
|
299
|
-
throw new Error(`daemon not ready: state=${JSON.stringify(state ?? "unknown")}`);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
throw new Error(`timeout after ${timeout}ms waiting for connection_ack (ready)`);
|
|
366
|
+
async function fetchLoopMessages(client, loopID, opts) {
|
|
367
|
+
return client.getLoopMessages(
|
|
368
|
+
loopID,
|
|
369
|
+
opts?.limit,
|
|
370
|
+
opts?.offset,
|
|
371
|
+
opts?.includeEvents,
|
|
372
|
+
opts?.timeout
|
|
373
|
+
);
|
|
303
374
|
}
|
|
304
|
-
async function
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const errObj = ev.error ?? {};
|
|
313
|
-
throw new DaemonError(errObj.code ?? -32603, errObj.message ?? "daemon error");
|
|
375
|
+
async function connectedWebsocket(wsUrl, fn, timeoutMs = 3e4) {
|
|
376
|
+
const { Client: Client2 } = await import("./client-SOJZSF7C.js");
|
|
377
|
+
const client = new Client2(wsUrl, defaultConfig());
|
|
378
|
+
const deadline = Date.now() + timeoutMs;
|
|
379
|
+
try {
|
|
380
|
+
await client.connect();
|
|
381
|
+
while (!client.isConnected() && Date.now() < deadline) {
|
|
382
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
314
383
|
}
|
|
315
|
-
if (
|
|
316
|
-
|
|
317
|
-
if (lid && lid !== "") {
|
|
318
|
-
return ev;
|
|
319
|
-
}
|
|
384
|
+
if (!client.isConnected()) {
|
|
385
|
+
throw new Error("Timed out waiting for daemon handshake");
|
|
320
386
|
}
|
|
387
|
+
return await fn(client);
|
|
388
|
+
} finally {
|
|
389
|
+
client.close();
|
|
321
390
|
}
|
|
322
|
-
throw new Error(`timeout after ${timeout}ms waiting for status with loop_id`);
|
|
323
391
|
}
|
|
324
|
-
async function
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
392
|
+
async function protocol1Rpc(wsUrl, method, params = null, opts = {}) {
|
|
393
|
+
const mode = opts.mode ?? "request";
|
|
394
|
+
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
395
|
+
try {
|
|
396
|
+
return await connectedWebsocket(
|
|
397
|
+
wsUrl,
|
|
398
|
+
async (client) => {
|
|
399
|
+
if (mode === "notify") {
|
|
400
|
+
await client.notify(method, params ?? {});
|
|
401
|
+
return {};
|
|
402
|
+
}
|
|
403
|
+
if (mode === "subscribe") {
|
|
404
|
+
const subId = await client.subscribe(
|
|
405
|
+
method,
|
|
406
|
+
params ?? {},
|
|
407
|
+
timeoutMs
|
|
408
|
+
);
|
|
409
|
+
return { subscription_id: subId };
|
|
410
|
+
}
|
|
411
|
+
const result = await client.requestResponse(
|
|
412
|
+
method,
|
|
413
|
+
params ?? {},
|
|
414
|
+
method,
|
|
415
|
+
timeoutMs
|
|
416
|
+
);
|
|
417
|
+
return result && typeof result === "object" ? result : { result };
|
|
418
|
+
},
|
|
419
|
+
timeoutMs
|
|
420
|
+
);
|
|
421
|
+
} catch (exc) {
|
|
422
|
+
const msg = exc instanceof Error ? exc.message : String(exc);
|
|
423
|
+
if (msg.toLowerCase().includes("timed out") || msg.toLowerCase().includes("timeout")) {
|
|
424
|
+
return { error: "Timed out waiting for daemon response" };
|
|
340
425
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
async function connectWithRetries(client, maxRetries, retryDelay) {
|
|
345
|
-
const retries = maxRetries && maxRetries > 0 ? maxRetries : 40;
|
|
346
|
-
const delay = retryDelay && retryDelay > 0 ? retryDelay : 250;
|
|
347
|
-
let lastErr = null;
|
|
348
|
-
for (let attempt = 0; attempt < retries; attempt++) {
|
|
349
|
-
try {
|
|
350
|
-
await client.connect();
|
|
351
|
-
return;
|
|
352
|
-
} catch (err) {
|
|
353
|
-
lastErr = err;
|
|
426
|
+
if (msg.toLowerCase().includes("connect") || msg.toLowerCase().includes("dial")) {
|
|
427
|
+
return { error: `Connection error: ${msg}` };
|
|
354
428
|
}
|
|
355
|
-
|
|
429
|
+
return { error: msg };
|
|
356
430
|
}
|
|
357
|
-
throw new Error(
|
|
358
|
-
`failed to connect after ${retries} attempts: ${lastErr?.message ?? "unknown error"}`
|
|
359
|
-
);
|
|
360
431
|
}
|
|
361
432
|
|
|
362
433
|
// src/appkit/broadcaster.ts
|
|
@@ -578,6 +649,7 @@ var EventClassifier = class {
|
|
|
578
649
|
deliverablePhases;
|
|
579
650
|
minDeliverableRunes;
|
|
580
651
|
thinkingStepEvents;
|
|
652
|
+
treatStatusIdleAsComplete;
|
|
581
653
|
constructor(cfg) {
|
|
582
654
|
if (!cfg.deliverablePhases) {
|
|
583
655
|
throw new Error("appkit: ClassifierConfig.deliverablePhases must not be nil");
|
|
@@ -585,6 +657,7 @@ var EventClassifier = class {
|
|
|
585
657
|
this.deliverablePhases = cfg.deliverablePhases;
|
|
586
658
|
this.minDeliverableRunes = cfg.minDeliverableRunes && cfg.minDeliverableRunes > 0 ? cfg.minDeliverableRunes : 8;
|
|
587
659
|
this.thinkingStepEvents = cfg.thinkingStepEvents;
|
|
660
|
+
this.treatStatusIdleAsComplete = Boolean(cfg.treatStatusIdleAsComplete);
|
|
588
661
|
}
|
|
589
662
|
/**
|
|
590
663
|
* Inspects one decoded event and returns its outcome. `accumulated` is the
|
|
@@ -601,6 +674,13 @@ var EventClassifier = class {
|
|
|
601
674
|
*/
|
|
602
675
|
isDeliverableCompletionEvent(eventType) {
|
|
603
676
|
if (!eventType) return false;
|
|
677
|
+
switch (eventType) {
|
|
678
|
+
case "status.idle":
|
|
679
|
+
case "idle_timeout":
|
|
680
|
+
case "query_timeout":
|
|
681
|
+
case "stream_closed":
|
|
682
|
+
return true;
|
|
683
|
+
}
|
|
604
684
|
if (eventType === EventFinalReport) return true;
|
|
605
685
|
if (eventType.startsWith("soothe.protocol.message.")) {
|
|
606
686
|
const phase = eventType.slice("soothe.protocol.message.".length);
|
|
@@ -636,16 +716,22 @@ var EventClassifier = class {
|
|
|
636
716
|
return ["", false];
|
|
637
717
|
}
|
|
638
718
|
/** The event→outcome mapper, ported from triarch's ProcessChatEvent. */
|
|
639
|
-
processChatEvent(msg,
|
|
719
|
+
processChatEvent(msg, accumulated) {
|
|
640
720
|
if (!msg || typeof msg !== "object") {
|
|
641
721
|
return { terminal: 0 /* Continue */ };
|
|
642
722
|
}
|
|
643
723
|
const m = msg;
|
|
644
724
|
const typ = m.type;
|
|
645
725
|
if (typ === "next") {
|
|
646
|
-
return this.classifyNextEnvelope(m);
|
|
726
|
+
return this.classifyNextEnvelope(m, accumulated);
|
|
727
|
+
}
|
|
728
|
+
if (typ === "response" || typ === "complete" || typ === "receipt_response" || typ === "connection_ack") {
|
|
729
|
+
return { terminal: 0 /* Continue */ };
|
|
647
730
|
}
|
|
648
|
-
if (typ === "
|
|
731
|
+
if (typ === "status") {
|
|
732
|
+
if (this.treatStatusIdleAsComplete && String(m.state ?? "").trim().toLowerCase() === "idle" && this.isSubstantiveAssistantReply(accumulated)) {
|
|
733
|
+
return this.deliverableResult(accumulated.trim(), "status.idle");
|
|
734
|
+
}
|
|
649
735
|
return { terminal: 0 /* Continue */ };
|
|
650
736
|
}
|
|
651
737
|
if (typ === "error") {
|
|
@@ -665,10 +751,14 @@ var EventClassifier = class {
|
|
|
665
751
|
return { terminal: 0 /* Continue */ };
|
|
666
752
|
}
|
|
667
753
|
/** Classifies a `next` envelope by projecting its payload. */
|
|
668
|
-
classifyNextEnvelope(env) {
|
|
754
|
+
classifyNextEnvelope(env, accumulated) {
|
|
669
755
|
const payload = env.payload ?? {};
|
|
670
756
|
const innerData = payload.data;
|
|
671
757
|
if (innerData && typeof innerData === "object") {
|
|
758
|
+
const innerType = innerData.type ?? "";
|
|
759
|
+
if (innerType === "status") {
|
|
760
|
+
return this.processChatEvent(innerData, accumulated);
|
|
761
|
+
}
|
|
672
762
|
const innerMode = innerData.mode ?? "";
|
|
673
763
|
if (innerMode) {
|
|
674
764
|
return this.classifyEventPayload(
|
|
@@ -679,6 +769,11 @@ var EventClassifier = class {
|
|
|
679
769
|
}
|
|
680
770
|
}
|
|
681
771
|
const mode = payload.mode ?? "";
|
|
772
|
+
if (mode === "status" || mode === "") {
|
|
773
|
+
if (typeof payload.state === "string" || innerData && "state" in (innerData ?? {})) {
|
|
774
|
+
return this.processChatEvent({ type: "status", ...innerData ?? payload }, accumulated);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
682
777
|
if (mode) {
|
|
683
778
|
return this.classifyEventPayload(payload.namespace ?? null, mode, payload.data);
|
|
684
779
|
}
|
|
@@ -1090,10 +1185,15 @@ var ConnectionPool = class {
|
|
|
1090
1185
|
if (existing.isDisconnected() || !existing.isConnected()) {
|
|
1091
1186
|
await this.release(sessionID);
|
|
1092
1187
|
} else {
|
|
1093
|
-
existing.lastUsed
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1188
|
+
const idleTooLong = this.cfg.maxIdleTime > 0 && existing.lastUsed > 0 && Date.now() - existing.lastUsed > this.cfg.maxIdleTime;
|
|
1189
|
+
if (idleTooLong) {
|
|
1190
|
+
await this.release(sessionID);
|
|
1191
|
+
} else {
|
|
1192
|
+
existing.lastUsed = Date.now();
|
|
1193
|
+
await this.store.updateLastUsed(sessionID).catch(() => {
|
|
1194
|
+
});
|
|
1195
|
+
return existing;
|
|
1196
|
+
}
|
|
1097
1197
|
}
|
|
1098
1198
|
}
|
|
1099
1199
|
const conn = this.pool.pop();
|
|
@@ -1196,6 +1296,88 @@ var ConnectionPool = class {
|
|
|
1196
1296
|
}
|
|
1197
1297
|
};
|
|
1198
1298
|
|
|
1299
|
+
// src/appkit/attachments.ts
|
|
1300
|
+
function compactDefaults(opts) {
|
|
1301
|
+
return {
|
|
1302
|
+
maxDim: opts?.maxDim && opts.maxDim > 0 ? opts.maxDim : 768,
|
|
1303
|
+
quality: opts?.jpegQuality && opts.jpegQuality > 0 ? opts.jpegQuality : 85
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
var sharpLoader = null;
|
|
1307
|
+
async function loadSharp() {
|
|
1308
|
+
if (!sharpLoader) {
|
|
1309
|
+
sharpLoader = (async () => {
|
|
1310
|
+
try {
|
|
1311
|
+
const m = await Function('return import("sharp")')();
|
|
1312
|
+
return m;
|
|
1313
|
+
} catch {
|
|
1314
|
+
return null;
|
|
1315
|
+
}
|
|
1316
|
+
})();
|
|
1317
|
+
}
|
|
1318
|
+
return sharpLoader;
|
|
1319
|
+
}
|
|
1320
|
+
async function compactImageAttachment(mimeType, dataB64, opts) {
|
|
1321
|
+
if (!dataB64 || !mimeType.startsWith("image/")) {
|
|
1322
|
+
return [mimeType, dataB64];
|
|
1323
|
+
}
|
|
1324
|
+
let raw;
|
|
1325
|
+
try {
|
|
1326
|
+
raw = Buffer.from(dataB64, "base64");
|
|
1327
|
+
} catch {
|
|
1328
|
+
return [mimeType, dataB64];
|
|
1329
|
+
}
|
|
1330
|
+
if (raw.length === 0) return [mimeType, dataB64];
|
|
1331
|
+
const sharpMod = await loadSharp();
|
|
1332
|
+
if (!sharpMod) return [mimeType, dataB64];
|
|
1333
|
+
const { maxDim, quality } = compactDefaults(opts);
|
|
1334
|
+
try {
|
|
1335
|
+
const img = sharpMod.default(raw, { failOn: "none" });
|
|
1336
|
+
const meta = await img.metadata();
|
|
1337
|
+
const w = meta.width ?? 0;
|
|
1338
|
+
const h = meta.height ?? 0;
|
|
1339
|
+
if (w <= 0 || h <= 0 || w <= maxDim && h <= maxDim) {
|
|
1340
|
+
return [mimeType, dataB64];
|
|
1341
|
+
}
|
|
1342
|
+
let nw = w;
|
|
1343
|
+
let nh = h;
|
|
1344
|
+
if (w >= h) {
|
|
1345
|
+
if (w > maxDim) {
|
|
1346
|
+
nw = maxDim;
|
|
1347
|
+
nh = Math.max(1, Math.round(h * maxDim / w));
|
|
1348
|
+
}
|
|
1349
|
+
} else if (h > maxDim) {
|
|
1350
|
+
nh = maxDim;
|
|
1351
|
+
nw = Math.max(1, Math.round(w * maxDim / h));
|
|
1352
|
+
}
|
|
1353
|
+
const resized = img.resize(nw, nh, { fit: "fill" });
|
|
1354
|
+
if (mimeType === "image/png") {
|
|
1355
|
+
const buf2 = await resized.png().toBuffer();
|
|
1356
|
+
return [mimeType, buf2.toString("base64")];
|
|
1357
|
+
}
|
|
1358
|
+
const buf = await resized.jpeg({ quality }).toBuffer();
|
|
1359
|
+
return ["image/jpeg", buf.toString("base64")];
|
|
1360
|
+
} catch {
|
|
1361
|
+
return [mimeType, dataB64];
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
async function compactAttachments(atts, opts) {
|
|
1365
|
+
if (!atts.length) return atts;
|
|
1366
|
+
const out = [];
|
|
1367
|
+
for (const att of atts) {
|
|
1368
|
+
const cp = { ...att };
|
|
1369
|
+
const mime = typeof cp.mime_type === "string" ? cp.mime_type : "";
|
|
1370
|
+
const data = typeof cp.data === "string" ? cp.data : "";
|
|
1371
|
+
if (mime && data) {
|
|
1372
|
+
const [outMime, outData] = await compactImageAttachment(mime, data, opts);
|
|
1373
|
+
cp.mime_type = outMime;
|
|
1374
|
+
cp.data = outData;
|
|
1375
|
+
}
|
|
1376
|
+
out.push(cp);
|
|
1377
|
+
}
|
|
1378
|
+
return out;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1199
1381
|
// src/appkit/turn_runner.ts
|
|
1200
1382
|
var ErrQueryTimeout = class extends Error {
|
|
1201
1383
|
constructor() {
|
|
@@ -1203,6 +1385,19 @@ var ErrQueryTimeout = class extends Error {
|
|
|
1203
1385
|
this.name = "ErrQueryTimeout";
|
|
1204
1386
|
}
|
|
1205
1387
|
};
|
|
1388
|
+
var ErrIdleTimeout = class extends Error {
|
|
1389
|
+
constructor() {
|
|
1390
|
+
super("appkit: idle timeout");
|
|
1391
|
+
this.name = "ErrIdleTimeout";
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1394
|
+
var TimeoutPolicy = /* @__PURE__ */ ((TimeoutPolicy2) => {
|
|
1395
|
+
TimeoutPolicy2[TimeoutPolicy2["Fail"] = 0] = "Fail";
|
|
1396
|
+
TimeoutPolicy2[TimeoutPolicy2["SoftComplete"] = 1] = "SoftComplete";
|
|
1397
|
+
return TimeoutPolicy2;
|
|
1398
|
+
})(TimeoutPolicy || {});
|
|
1399
|
+
var StreamCloseFail = 0 /* Fail */;
|
|
1400
|
+
var StreamCloseSoftComplete = 1 /* SoftComplete */;
|
|
1206
1401
|
function inputMessageForLoop(text, loopID, attachments, opts) {
|
|
1207
1402
|
const msg = { type: "loop_input", content: text };
|
|
1208
1403
|
if (loopID) msg.loop_id = loopID;
|
|
@@ -1225,6 +1420,13 @@ function inputMessageForLoop(text, loopID, attachments, opts) {
|
|
|
1225
1420
|
}
|
|
1226
1421
|
return msg;
|
|
1227
1422
|
}
|
|
1423
|
+
function idleTimeoutForTurn(cfg, hasAttachments) {
|
|
1424
|
+
const idle = cfg.idleTimeout ?? 0;
|
|
1425
|
+
if (idle <= 0) return 0;
|
|
1426
|
+
const floor = cfg.minIdleTimeoutWithAttachments ?? 0;
|
|
1427
|
+
if (hasAttachments && floor > 0 && idle < floor) return floor;
|
|
1428
|
+
return idle;
|
|
1429
|
+
}
|
|
1228
1430
|
var TurnRunner = class {
|
|
1229
1431
|
pool;
|
|
1230
1432
|
gate;
|
|
@@ -1235,39 +1437,29 @@ var TurnRunner = class {
|
|
|
1235
1437
|
buildInput = inputMessageForLoop;
|
|
1236
1438
|
onComplete = null;
|
|
1237
1439
|
onError = null;
|
|
1238
|
-
/**
|
|
1239
|
-
* Constructs a TurnRunner. pool, gate, classifier, and store are required;
|
|
1240
|
-
* broadcaster may be null.
|
|
1241
|
-
*/
|
|
1242
1440
|
constructor(pool, gate, classifier, store, broadcaster, cfg) {
|
|
1243
1441
|
this.pool = pool;
|
|
1244
1442
|
this.gate = gate;
|
|
1245
1443
|
this.classifier = classifier;
|
|
1246
1444
|
this.store = store;
|
|
1247
1445
|
this.broadcaster = broadcaster;
|
|
1248
|
-
this.cfg = {
|
|
1446
|
+
this.cfg = {
|
|
1447
|
+
...cfg,
|
|
1448
|
+
queryTimeout: cfg.queryTimeout > 0 ? cfg.queryTimeout : 30 * 60 * 1e3
|
|
1449
|
+
};
|
|
1249
1450
|
}
|
|
1250
|
-
/** Overrides the loop_input payload builder. */
|
|
1251
1451
|
withInputBuilder(f) {
|
|
1252
1452
|
if (f) this.buildInput = f;
|
|
1253
1453
|
return this;
|
|
1254
1454
|
}
|
|
1255
|
-
/** Sets a completion hook (runs inline on success). */
|
|
1256
1455
|
withOnComplete(f) {
|
|
1257
1456
|
this.onComplete = f;
|
|
1258
1457
|
return this;
|
|
1259
1458
|
}
|
|
1260
|
-
/** Sets an error hook (runs inline on failure). */
|
|
1261
1459
|
withOnError(f) {
|
|
1262
1460
|
this.onError = f;
|
|
1263
1461
|
return this;
|
|
1264
1462
|
}
|
|
1265
|
-
/**
|
|
1266
|
-
* Runs one query turn. The response is broadcast via the SSE broadcaster and
|
|
1267
|
-
* persisted via the SessionStore; it is not returned to the caller (SSE
|
|
1268
|
-
* subscribers receive it). Resolves on success; rejects on failure
|
|
1269
|
-
* (ErrQueryTimeout, AbortError, or a daemon/processing error).
|
|
1270
|
-
*/
|
|
1271
1463
|
async execute(sessionID, message, userID, workspaceID, attachments, opts, signal) {
|
|
1272
1464
|
let conn;
|
|
1273
1465
|
try {
|
|
@@ -1295,13 +1487,19 @@ var TurnRunner = class {
|
|
|
1295
1487
|
this.onError?.(sessionID, loopID, err);
|
|
1296
1488
|
throw err;
|
|
1297
1489
|
}
|
|
1490
|
+
let idleTimer = null;
|
|
1491
|
+
const clearIdle = () => {
|
|
1492
|
+
if (idleTimer) {
|
|
1493
|
+
clearTimeout(idleTimer);
|
|
1494
|
+
idleTimer = null;
|
|
1495
|
+
}
|
|
1496
|
+
};
|
|
1298
1497
|
try {
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
);
|
|
1498
|
+
let atts = attachments ?? void 0;
|
|
1499
|
+
if (this.cfg.compactAttachmentsBeforeSend && atts && atts.length > 0) {
|
|
1500
|
+
atts = await compactAttachments(atts, this.cfg.compactImageOpts);
|
|
1501
|
+
}
|
|
1502
|
+
const inputMsg = this.buildInput(message, loopID, atts, opts ?? void 0);
|
|
1305
1503
|
try {
|
|
1306
1504
|
await conn.client.sendMessage(inputMsg);
|
|
1307
1505
|
} catch (err) {
|
|
@@ -1320,6 +1518,23 @@ var TurnRunner = class {
|
|
|
1320
1518
|
}
|
|
1321
1519
|
let assistantContent = "";
|
|
1322
1520
|
const startedAt = Date.now();
|
|
1521
|
+
const idleForTurn = idleTimeoutForTurn(this.cfg, (attachments?.length ?? 0) > 0);
|
|
1522
|
+
let idleReject = null;
|
|
1523
|
+
const armIdle = () => {
|
|
1524
|
+
clearIdle();
|
|
1525
|
+
idleReject = null;
|
|
1526
|
+
if (idleForTurn <= 0) {
|
|
1527
|
+
return new Promise(() => {
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
return new Promise((resolve) => {
|
|
1531
|
+
idleReject = () => resolve("idle");
|
|
1532
|
+
idleTimer = setTimeout(() => {
|
|
1533
|
+
idleReject?.();
|
|
1534
|
+
}, idleForTurn);
|
|
1535
|
+
});
|
|
1536
|
+
};
|
|
1537
|
+
let idleRace = armIdle();
|
|
1323
1538
|
const abortRace = new Promise((resolve) => {
|
|
1324
1539
|
const onTimeout = () => resolve("timeout");
|
|
1325
1540
|
timeoutController.signal.addEventListener("abort", onTimeout, { once: true });
|
|
@@ -1333,34 +1548,71 @@ var TurnRunner = class {
|
|
|
1333
1548
|
const next = iterator.next();
|
|
1334
1549
|
const raced = await Promise.race([
|
|
1335
1550
|
next.then((res2) => ({ tag: "msg", res: res2 })),
|
|
1336
|
-
abortRace.then((tag) => ({ tag }))
|
|
1551
|
+
abortRace.then((tag) => ({ tag })),
|
|
1552
|
+
idleRace.then((tag) => ({ tag }))
|
|
1337
1553
|
]);
|
|
1338
1554
|
if ("tag" in raced && raced.tag !== "msg") {
|
|
1339
1555
|
if (raced.tag === "caller" || signal?.aborted) {
|
|
1556
|
+
clearIdle();
|
|
1340
1557
|
const err = new Error("aborted");
|
|
1341
1558
|
await this.persistFailed(sessionID, loopID, err);
|
|
1342
1559
|
this.broadcastError(sessionID, err);
|
|
1343
1560
|
this.onError?.(sessionID, loopID, err);
|
|
1344
1561
|
throw err;
|
|
1345
1562
|
}
|
|
1563
|
+
if (raced.tag === "idle") {
|
|
1564
|
+
clearIdle();
|
|
1565
|
+
await this.sendLoopCancel(new AbortController().signal, conn, loopID).catch(() => {
|
|
1566
|
+
});
|
|
1567
|
+
await this.finishTimeout(
|
|
1568
|
+
sessionID,
|
|
1569
|
+
loopID,
|
|
1570
|
+
assistantContent,
|
|
1571
|
+
startedAt,
|
|
1572
|
+
new ErrIdleTimeout(),
|
|
1573
|
+
"idle_timeout",
|
|
1574
|
+
this.cfg.onIdleTimeout ?? 0 /* Fail */
|
|
1575
|
+
);
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
clearIdle();
|
|
1346
1579
|
await this.sendLoopCancel(new AbortController().signal, conn, loopID).catch(() => {
|
|
1347
1580
|
});
|
|
1348
|
-
await this.
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1581
|
+
await this.finishTimeout(
|
|
1582
|
+
sessionID,
|
|
1583
|
+
loopID,
|
|
1584
|
+
assistantContent,
|
|
1585
|
+
startedAt,
|
|
1586
|
+
new ErrQueryTimeout(),
|
|
1587
|
+
"query_timeout",
|
|
1588
|
+
this.cfg.onQueryTimeout ?? 0 /* Fail */
|
|
1589
|
+
);
|
|
1590
|
+
return;
|
|
1352
1591
|
}
|
|
1353
1592
|
const res = raced.res;
|
|
1354
1593
|
if (res.done) {
|
|
1594
|
+
clearIdle();
|
|
1595
|
+
if ((this.cfg.onStreamClose ?? 0 /* Fail */) === 1 /* SoftComplete */ && assistantContent.trim() !== "") {
|
|
1596
|
+
await this.completeTurn(
|
|
1597
|
+
sessionID,
|
|
1598
|
+
loopID,
|
|
1599
|
+
assistantContent,
|
|
1600
|
+
startedAt,
|
|
1601
|
+
"stream_closed"
|
|
1602
|
+
);
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1355
1605
|
const err = new Error("event stream closed");
|
|
1356
1606
|
await this.persistFailed(sessionID, loopID, err);
|
|
1357
1607
|
this.broadcastError(sessionID, err);
|
|
1358
1608
|
this.onError?.(sessionID, loopID, err);
|
|
1359
1609
|
throw err;
|
|
1360
1610
|
}
|
|
1611
|
+
idleRace = armIdle();
|
|
1361
1612
|
const msg = res.value;
|
|
1362
1613
|
const eventResult = this.classifier.classify(msg, assistantContent);
|
|
1363
1614
|
if (eventResult.err && eventResult.terminal === 2 /* FailedComplete */) {
|
|
1615
|
+
clearIdle();
|
|
1364
1616
|
await this.persistFailed(sessionID, loopID, eventResult.err);
|
|
1365
1617
|
this.broadcastError(sessionID, eventResult.err);
|
|
1366
1618
|
this.onError?.(sessionID, loopID, eventResult.err);
|
|
@@ -1380,25 +1632,39 @@ var TurnRunner = class {
|
|
|
1380
1632
|
assistantContent
|
|
1381
1633
|
);
|
|
1382
1634
|
if (deliverable) {
|
|
1383
|
-
|
|
1384
|
-
await this.
|
|
1635
|
+
clearIdle();
|
|
1636
|
+
await this.completeTurn(
|
|
1385
1637
|
sessionID,
|
|
1386
1638
|
loopID,
|
|
1387
1639
|
final,
|
|
1388
1640
|
startedAt,
|
|
1389
1641
|
eventResult.completionEvent ?? ""
|
|
1390
1642
|
);
|
|
1391
|
-
this.broadcastComplete(sessionID, final);
|
|
1392
|
-
this.onComplete?.(sessionID, loopID, final, eventResult.completionEvent ?? "", elapsedMs);
|
|
1393
1643
|
return;
|
|
1394
1644
|
}
|
|
1395
1645
|
}
|
|
1396
1646
|
} finally {
|
|
1647
|
+
clearIdle();
|
|
1397
1648
|
clearTimeout(timer);
|
|
1398
1649
|
this.gate.release(sessionID);
|
|
1399
1650
|
}
|
|
1400
1651
|
}
|
|
1401
|
-
|
|
1652
|
+
async finishTimeout(sessionID, loopID, content, startedAt, failErr, completionEvent, policy) {
|
|
1653
|
+
if (policy === 1 /* SoftComplete */ && content.trim() !== "") {
|
|
1654
|
+
await this.completeTurn(sessionID, loopID, content, startedAt, completionEvent);
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
await this.persistFailed(sessionID, loopID, failErr);
|
|
1658
|
+
this.broadcastError(sessionID, failErr);
|
|
1659
|
+
this.onError?.(sessionID, loopID, failErr);
|
|
1660
|
+
throw failErr;
|
|
1661
|
+
}
|
|
1662
|
+
async completeTurn(sessionID, loopID, final, startedAt, completionEvent) {
|
|
1663
|
+
const elapsedMs = Date.now() - startedAt;
|
|
1664
|
+
await this.persistResponse(sessionID, loopID, final, startedAt, completionEvent);
|
|
1665
|
+
this.broadcastComplete(sessionID, final);
|
|
1666
|
+
this.onComplete?.(sessionID, loopID, final, completionEvent, elapsedMs);
|
|
1667
|
+
}
|
|
1402
1668
|
async sendLoopCancel(_signal, conn, loopID) {
|
|
1403
1669
|
const lid = (loopID ?? "").trim();
|
|
1404
1670
|
if (!conn || !lid) return;
|
|
@@ -1441,17 +1707,469 @@ var TurnRunner = class {
|
|
|
1441
1707
|
this.broadcaster?.broadcast(sessionID, { type: "query_error", data: err.message });
|
|
1442
1708
|
}
|
|
1443
1709
|
};
|
|
1710
|
+
|
|
1711
|
+
// src/appkit/chunk_filter.ts
|
|
1712
|
+
var MSG_PAIR_LEN = 2;
|
|
1713
|
+
function updatesChunkIsNoop(data) {
|
|
1714
|
+
if (!data || typeof data !== "object") return true;
|
|
1715
|
+
return !("__interrupt__" in data);
|
|
1716
|
+
}
|
|
1717
|
+
function wireBody(msg) {
|
|
1718
|
+
for (const key of ["kwargs", "data"]) {
|
|
1719
|
+
const nested = msg[key];
|
|
1720
|
+
if (nested && typeof nested === "object") return nested;
|
|
1721
|
+
}
|
|
1722
|
+
return msg;
|
|
1723
|
+
}
|
|
1724
|
+
function dictHasToolInvocation(msg) {
|
|
1725
|
+
const body = wireBody(msg);
|
|
1726
|
+
if (body.tool_calls || body.tool_call_chunks) return true;
|
|
1727
|
+
for (const key of ["content", "content_blocks"]) {
|
|
1728
|
+
const raw = body[key];
|
|
1729
|
+
if (Array.isArray(raw)) {
|
|
1730
|
+
for (const item of raw) {
|
|
1731
|
+
if (item && typeof item === "object" && ["tool_call", "tool_call_chunk", "tool_use"].includes(
|
|
1732
|
+
String(item.type ?? "")
|
|
1733
|
+
)) {
|
|
1734
|
+
return true;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
return false;
|
|
1740
|
+
}
|
|
1741
|
+
function plainText(msg) {
|
|
1742
|
+
const body = wireBody(msg);
|
|
1743
|
+
const content = body.content ?? msg.content;
|
|
1744
|
+
if (typeof content === "string") return content;
|
|
1745
|
+
if (Array.isArray(content)) {
|
|
1746
|
+
const parts = [];
|
|
1747
|
+
for (const block of content) {
|
|
1748
|
+
if (typeof block === "string") parts.push(block);
|
|
1749
|
+
else if (block && typeof block === "object") {
|
|
1750
|
+
const text = block.text;
|
|
1751
|
+
if (typeof text === "string") parts.push(text);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
return parts.join("");
|
|
1755
|
+
}
|
|
1756
|
+
return "";
|
|
1757
|
+
}
|
|
1758
|
+
function messageChunkIsNonActionable(data) {
|
|
1759
|
+
if (!Array.isArray(data) || data.length !== MSG_PAIR_LEN) return false;
|
|
1760
|
+
const msg = data[0];
|
|
1761
|
+
if (msg === null || msg === void 0) return true;
|
|
1762
|
+
if (!msg || typeof msg !== "object") return false;
|
|
1763
|
+
const m = msg;
|
|
1764
|
+
const body = wireBody(m);
|
|
1765
|
+
const raw = String(body.type ?? m.type ?? "");
|
|
1766
|
+
if (raw === "tool" || raw === "ToolMessage" || raw.endsWith("ToolMessage")) return false;
|
|
1767
|
+
if (dictHasToolInvocation(m)) return false;
|
|
1768
|
+
if (body.phase || m.phase) return false;
|
|
1769
|
+
return !plainText(m).trim();
|
|
1770
|
+
}
|
|
1771
|
+
function shouldDropStreamChunkEarly(_namespace, mode, data) {
|
|
1772
|
+
if (mode === "updates") return updatesChunkIsNoop(data);
|
|
1773
|
+
if (mode === "messages") return messageChunkIsNonActionable(data);
|
|
1774
|
+
return false;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
// src/appkit/events.ts
|
|
1778
|
+
function unwrapNext(event) {
|
|
1779
|
+
if (!event || typeof event !== "object") return event;
|
|
1780
|
+
if (event.type !== "next") return event;
|
|
1781
|
+
const payload = event.payload;
|
|
1782
|
+
if (!payload || typeof payload !== "object") return event;
|
|
1783
|
+
const data = payload.data;
|
|
1784
|
+
return data && typeof data === "object" ? data : event;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
// src/appkit/observability.ts
|
|
1788
|
+
var TurnEventStats = class {
|
|
1789
|
+
total = 0;
|
|
1790
|
+
messages = 0;
|
|
1791
|
+
updates = 0;
|
|
1792
|
+
custom = 0;
|
|
1793
|
+
skipped = 0;
|
|
1794
|
+
filteredEarly = 0;
|
|
1795
|
+
toolCalls = 0;
|
|
1796
|
+
toolResults = 0;
|
|
1797
|
+
textChunks = 0;
|
|
1798
|
+
heartbeatsDropped = 0;
|
|
1799
|
+
postIdleDrained = 0;
|
|
1800
|
+
inboundDropped = 0;
|
|
1801
|
+
};
|
|
1802
|
+
|
|
1803
|
+
// src/appkit/daemon_session.ts
|
|
1804
|
+
var DEFAULT_POST_IDLE_DRAIN_MS = 500;
|
|
1805
|
+
var DaemonSession = class {
|
|
1806
|
+
wsUrl;
|
|
1807
|
+
workspace;
|
|
1808
|
+
streamDelivery;
|
|
1809
|
+
client;
|
|
1810
|
+
rpcClient;
|
|
1811
|
+
loopId = null;
|
|
1812
|
+
readBusy = false;
|
|
1813
|
+
rpcBusy = false;
|
|
1814
|
+
rpcConnected = false;
|
|
1815
|
+
streaming = false;
|
|
1816
|
+
postIdleDrainDeadlineMs;
|
|
1817
|
+
closed = false;
|
|
1818
|
+
earlyDropFn;
|
|
1819
|
+
statsFactory;
|
|
1820
|
+
config;
|
|
1821
|
+
turnEventStats;
|
|
1822
|
+
lastTurnEndState = null;
|
|
1823
|
+
lastTurnCancellationSeen = false;
|
|
1824
|
+
lastTurnErrorMessage = null;
|
|
1825
|
+
constructor(wsUrl, opts = {}) {
|
|
1826
|
+
this.wsUrl = wsUrl;
|
|
1827
|
+
this.workspace = opts.workspace;
|
|
1828
|
+
this.streamDelivery = opts.streamDelivery ?? "adaptive";
|
|
1829
|
+
this.config = opts.config ?? defaultConfig();
|
|
1830
|
+
this.client = new Client(wsUrl, this.config);
|
|
1831
|
+
this.rpcClient = new Client(wsUrl, this.config);
|
|
1832
|
+
this.postIdleDrainDeadlineMs = opts.postIdleDrainDeadlineMs && opts.postIdleDrainDeadlineMs > 0 ? opts.postIdleDrainDeadlineMs : DEFAULT_POST_IDLE_DRAIN_MS;
|
|
1833
|
+
this.earlyDropFn = opts.earlyDropFn ?? shouldDropStreamChunkEarly;
|
|
1834
|
+
this.statsFactory = opts.statsFactory ?? (() => new TurnEventStats());
|
|
1835
|
+
this.turnEventStats = this.statsFactory();
|
|
1836
|
+
}
|
|
1837
|
+
get streamClient() {
|
|
1838
|
+
return this.client;
|
|
1839
|
+
}
|
|
1840
|
+
get rpcSideClient() {
|
|
1841
|
+
return this.rpcClient;
|
|
1842
|
+
}
|
|
1843
|
+
get activeLoopId() {
|
|
1844
|
+
return this.loopId;
|
|
1845
|
+
}
|
|
1846
|
+
resolveStreamDeliveryMode() {
|
|
1847
|
+
const delivery = this.streamDelivery;
|
|
1848
|
+
if (typeof delivery === "function") return String(delivery() || "adaptive");
|
|
1849
|
+
return String(delivery || "adaptive");
|
|
1850
|
+
}
|
|
1851
|
+
get streamDeliveryMode() {
|
|
1852
|
+
return this.resolveStreamDeliveryMode();
|
|
1853
|
+
}
|
|
1854
|
+
shouldDrop(namespace, mode, data) {
|
|
1855
|
+
return Boolean(this.earlyDropFn(namespace, mode, data));
|
|
1856
|
+
}
|
|
1857
|
+
async connect(resumeLoopId) {
|
|
1858
|
+
await connectWithRetries(this.client);
|
|
1859
|
+
return this.bootstrapLoop(resumeLoopId ?? null);
|
|
1860
|
+
}
|
|
1861
|
+
async bootstrapLoop(resumeLoopId) {
|
|
1862
|
+
const loopNew = this.workspace ? { client_workspace: this.workspace, workspace: this.workspace } : void 0;
|
|
1863
|
+
const loopId = await bootstrapLoopSession(this.client, resumeLoopId, this.config, loopNew);
|
|
1864
|
+
this.loopId = loopId;
|
|
1865
|
+
return { type: "status", loop_id: loopId, state: "ready" };
|
|
1866
|
+
}
|
|
1867
|
+
async newLoop() {
|
|
1868
|
+
return this.bootstrapLoop(null);
|
|
1869
|
+
}
|
|
1870
|
+
async switchLoop(loopId) {
|
|
1871
|
+
return this.bootstrapLoop(loopId);
|
|
1872
|
+
}
|
|
1873
|
+
async ensureConnected() {
|
|
1874
|
+
if (this.client.isConnected() && !this.client.isDisconnected()) return;
|
|
1875
|
+
let resumeLoopId = this.loopId;
|
|
1876
|
+
if (this.rpcConnected) {
|
|
1877
|
+
this.rpcClient.close();
|
|
1878
|
+
this.rpcConnected = false;
|
|
1879
|
+
}
|
|
1880
|
+
try {
|
|
1881
|
+
await this.client.reconnect();
|
|
1882
|
+
} catch {
|
|
1883
|
+
this.client.close();
|
|
1884
|
+
await connectWithRetries(this.client);
|
|
1885
|
+
}
|
|
1886
|
+
if (resumeLoopId) {
|
|
1887
|
+
try {
|
|
1888
|
+
await this.client.reattachAndProbe(resumeLoopId);
|
|
1889
|
+
this.loopId = resumeLoopId;
|
|
1890
|
+
return;
|
|
1891
|
+
} catch (err) {
|
|
1892
|
+
if (!(err instanceof StaleLoopError)) throw err;
|
|
1893
|
+
resumeLoopId = null;
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
await this.bootstrapLoop(resumeLoopId);
|
|
1897
|
+
}
|
|
1898
|
+
async close() {
|
|
1899
|
+
if (this.closed) return;
|
|
1900
|
+
this.closed = true;
|
|
1901
|
+
this.client.close();
|
|
1902
|
+
this.rpcClient.close();
|
|
1903
|
+
this.rpcConnected = false;
|
|
1904
|
+
}
|
|
1905
|
+
async detach() {
|
|
1906
|
+
if (!this.client.isConnected()) return;
|
|
1907
|
+
try {
|
|
1908
|
+
await this.client.notify("disconnect", {});
|
|
1909
|
+
} catch {
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
async sendTurn(text, options) {
|
|
1913
|
+
if (!this.loopId) throw new Error("No active loop session");
|
|
1914
|
+
await this.client.sendInput(text, {
|
|
1915
|
+
loopID: this.loopId,
|
|
1916
|
+
autonomous: options?.autonomous,
|
|
1917
|
+
maxIterations: options?.maxIterations,
|
|
1918
|
+
subagent: options?.preferredSubagent,
|
|
1919
|
+
model: options?.model,
|
|
1920
|
+
modelParams: options?.modelParams,
|
|
1921
|
+
attachments: options?.attachments,
|
|
1922
|
+
clarificationMode: options?.clarificationMode,
|
|
1923
|
+
clarificationAnswer: options?.clarificationAnswer,
|
|
1924
|
+
intentHint: options?.intentHint
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
async cancelActiveTurn() {
|
|
1928
|
+
await this.client.notify("slash_command", { cmd: "/cancel" });
|
|
1929
|
+
}
|
|
1930
|
+
async *drainStreamEventsAfterIdle(expectedLoopId) {
|
|
1931
|
+
const deadline = Date.now() + this.postIdleDrainDeadlineMs;
|
|
1932
|
+
let exp = expectedLoopId;
|
|
1933
|
+
while (Date.now() < deadline) {
|
|
1934
|
+
const event = await this.client.readEventWithTimeout(250);
|
|
1935
|
+
if (!event) break;
|
|
1936
|
+
let frame = event;
|
|
1937
|
+
let eventType = String(frame.type ?? "");
|
|
1938
|
+
if (eventType === "next") {
|
|
1939
|
+
frame = unwrapNext(frame) ?? frame;
|
|
1940
|
+
eventType = String(frame.type ?? "");
|
|
1941
|
+
}
|
|
1942
|
+
const eventLoopId = frame.loop_id;
|
|
1943
|
+
if (exp && typeof eventLoopId === "string" && eventLoopId && eventLoopId !== exp) {
|
|
1944
|
+
continue;
|
|
1945
|
+
}
|
|
1946
|
+
if (eventType === "error") {
|
|
1947
|
+
const errObj = frame.error ?? {};
|
|
1948
|
+
throw new Error(String(errObj.message || frame.message || "daemon error"));
|
|
1949
|
+
}
|
|
1950
|
+
if (eventType === "status") {
|
|
1951
|
+
const loopEv = frame.loop_id;
|
|
1952
|
+
if (typeof loopEv === "string" && loopEv) {
|
|
1953
|
+
this.loopId = loopEv;
|
|
1954
|
+
exp = loopEv;
|
|
1955
|
+
}
|
|
1956
|
+
continue;
|
|
1957
|
+
}
|
|
1958
|
+
if (eventType !== "event") continue;
|
|
1959
|
+
const data = frame.data;
|
|
1960
|
+
const namespace = Array.isArray(frame.namespace) ? frame.namespace : [];
|
|
1961
|
+
const mode = String(frame.mode ?? "");
|
|
1962
|
+
if (this.shouldDrop(namespace, mode, data)) {
|
|
1963
|
+
this.turnEventStats.filteredEarly += 1;
|
|
1964
|
+
continue;
|
|
1965
|
+
}
|
|
1966
|
+
this.turnEventStats.postIdleDrained += 1;
|
|
1967
|
+
yield [namespace, mode, data];
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
async withRpcLock(fn) {
|
|
1971
|
+
while (this.rpcBusy) {
|
|
1972
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
1973
|
+
}
|
|
1974
|
+
this.rpcBusy = true;
|
|
1975
|
+
try {
|
|
1976
|
+
return await fn();
|
|
1977
|
+
} finally {
|
|
1978
|
+
this.rpcBusy = false;
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
async ensureRpcConnected() {
|
|
1982
|
+
if (this.rpcConnected && this.rpcClient.isConnected()) return;
|
|
1983
|
+
await connectWithRetries(this.rpcClient);
|
|
1984
|
+
this.rpcConnected = true;
|
|
1985
|
+
}
|
|
1986
|
+
async listLoops(_limit = 20) {
|
|
1987
|
+
return this.withRpcLock(async () => {
|
|
1988
|
+
await this.ensureRpcConnected();
|
|
1989
|
+
return this.rpcClient.listLoops(15e3);
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
async fetchLoopCards(loopId) {
|
|
1993
|
+
const lid = String(loopId || "").trim();
|
|
1994
|
+
if (!lid) return { cards: [], seq: 0, contextTokens: 0, success: false };
|
|
1995
|
+
return this.withRpcLock(async () => {
|
|
1996
|
+
await this.ensureRpcConnected();
|
|
1997
|
+
try {
|
|
1998
|
+
const resp = await this.rpcClient.fetchLoopCards(lid, 3e4);
|
|
1999
|
+
const rawCards = resp.cards;
|
|
2000
|
+
return {
|
|
2001
|
+
cards: Array.isArray(rawCards) ? rawCards : [],
|
|
2002
|
+
seq: Number(resp.seq ?? 0),
|
|
2003
|
+
contextTokens: typeof resp.context_tokens === "number" && resp.context_tokens >= 0 ? resp.context_tokens : 0,
|
|
2004
|
+
success: true
|
|
2005
|
+
};
|
|
2006
|
+
} catch {
|
|
2007
|
+
return { cards: [], seq: 0, contextTokens: 0, success: false };
|
|
2008
|
+
}
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
async fetchLoopHistory(loopId) {
|
|
2012
|
+
const lid = String(loopId || "").trim();
|
|
2013
|
+
if (!lid) {
|
|
2014
|
+
return { goals: [], liveCards: [], liveGoalIndex: null, contextTokens: 0, success: false };
|
|
2015
|
+
}
|
|
2016
|
+
return this.withRpcLock(async () => {
|
|
2017
|
+
await this.ensureRpcConnected();
|
|
2018
|
+
try {
|
|
2019
|
+
const resp = await this.rpcClient.fetchLoopHistory(lid, 3e4);
|
|
2020
|
+
const liveGoalIndex = resp.live_goal_index;
|
|
2021
|
+
return {
|
|
2022
|
+
goals: Array.isArray(resp.goals) ? resp.goals : [],
|
|
2023
|
+
liveCards: Array.isArray(resp.live_cards) ? resp.live_cards : [],
|
|
2024
|
+
liveGoalIndex: typeof liveGoalIndex === "number" ? liveGoalIndex : null,
|
|
2025
|
+
contextTokens: typeof resp.context_tokens === "number" && resp.context_tokens >= 0 ? resp.context_tokens : 0,
|
|
2026
|
+
success: Boolean(resp.success ?? true)
|
|
2027
|
+
};
|
|
2028
|
+
} catch {
|
|
2029
|
+
return {
|
|
2030
|
+
goals: [],
|
|
2031
|
+
liveCards: [],
|
|
2032
|
+
liveGoalIndex: null,
|
|
2033
|
+
contextTokens: 0,
|
|
2034
|
+
success: false
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
async fetchConversationLog(loopId, opts = {}) {
|
|
2040
|
+
const lid = String(loopId || "").trim();
|
|
2041
|
+
if (!lid) return [];
|
|
2042
|
+
return this.withRpcLock(async () => {
|
|
2043
|
+
await this.ensureRpcConnected();
|
|
2044
|
+
const resp = await this.rpcClient.getLoopMessages(
|
|
2045
|
+
lid,
|
|
2046
|
+
opts.limit ?? 100,
|
|
2047
|
+
opts.offset ?? 0,
|
|
2048
|
+
opts.includeEvents ?? false
|
|
2049
|
+
);
|
|
2050
|
+
const raw = resp.messages;
|
|
2051
|
+
if (!Array.isArray(raw)) return [];
|
|
2052
|
+
return raw.filter((m) => !!m && typeof m === "object");
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
2055
|
+
async *iterTurnChunks(opts = {}) {
|
|
2056
|
+
this.turnEventStats = this.statsFactory();
|
|
2057
|
+
this.lastTurnEndState = null;
|
|
2058
|
+
this.lastTurnCancellationSeen = false;
|
|
2059
|
+
this.lastTurnErrorMessage = null;
|
|
2060
|
+
let queryStarted = false;
|
|
2061
|
+
let expectedLoopId = this.loopId;
|
|
2062
|
+
let streamPayloadSeen = false;
|
|
2063
|
+
let turnProgressSeen = false;
|
|
2064
|
+
this.streaming = true;
|
|
2065
|
+
const absoluteDeadline = opts.maxWaitMs !== void 0 && opts.maxWaitMs > 0 ? Date.now() + opts.maxWaitMs : null;
|
|
2066
|
+
this.client.peelStalePendingControlEvents();
|
|
2067
|
+
while (this.readBusy) {
|
|
2068
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
2069
|
+
}
|
|
2070
|
+
this.readBusy = true;
|
|
2071
|
+
try {
|
|
2072
|
+
while (true) {
|
|
2073
|
+
if (absoluteDeadline !== null && Date.now() >= absoluteDeadline) {
|
|
2074
|
+
throw new Error(
|
|
2075
|
+
`Turn timed out after ${opts.maxWaitMs}ms (loop=${expectedLoopId ?? "?"})`
|
|
2076
|
+
);
|
|
2077
|
+
}
|
|
2078
|
+
const event = await this.client.readEvent();
|
|
2079
|
+
if (!event) {
|
|
2080
|
+
if (queryStarted && !this.client.isConnectionAlive()) {
|
|
2081
|
+
this.lastTurnEndState = "connection_lost";
|
|
2082
|
+
throw new Error("Daemon connection lost");
|
|
2083
|
+
}
|
|
2084
|
+
break;
|
|
2085
|
+
}
|
|
2086
|
+
let frame = event;
|
|
2087
|
+
let eventType = String(frame.type ?? "");
|
|
2088
|
+
if (eventType === "next") {
|
|
2089
|
+
frame = unwrapNext(frame) ?? frame;
|
|
2090
|
+
eventType = String(frame.type ?? "");
|
|
2091
|
+
}
|
|
2092
|
+
const eventLoopId = frame.loop_id;
|
|
2093
|
+
if (expectedLoopId && typeof eventLoopId === "string" && eventLoopId && eventLoopId !== expectedLoopId) {
|
|
2094
|
+
continue;
|
|
2095
|
+
}
|
|
2096
|
+
if (eventType === "error") {
|
|
2097
|
+
const errObj = frame.error ?? {};
|
|
2098
|
+
throw new Error(String(errObj.message || frame.message || "daemon error"));
|
|
2099
|
+
}
|
|
2100
|
+
if (eventType === "status") {
|
|
2101
|
+
const loopEv = frame.loop_id;
|
|
2102
|
+
if (typeof loopEv === "string" && loopEv) {
|
|
2103
|
+
this.loopId = loopEv;
|
|
2104
|
+
expectedLoopId = loopEv;
|
|
2105
|
+
}
|
|
2106
|
+
const state = String(frame.state ?? "");
|
|
2107
|
+
if (state === "running") {
|
|
2108
|
+
queryStarted = true;
|
|
2109
|
+
} else if (queryStarted && state === "stopped") {
|
|
2110
|
+
this.lastTurnEndState = state;
|
|
2111
|
+
yield* this.drainStreamEventsAfterIdle(expectedLoopId);
|
|
2112
|
+
break;
|
|
2113
|
+
} else if (queryStarted && state === "idle") {
|
|
2114
|
+
if (!streamPayloadSeen && !this.lastTurnCancellationSeen) continue;
|
|
2115
|
+
this.lastTurnEndState = state;
|
|
2116
|
+
yield* this.drainStreamEventsAfterIdle(expectedLoopId);
|
|
2117
|
+
break;
|
|
2118
|
+
}
|
|
2119
|
+
continue;
|
|
2120
|
+
}
|
|
2121
|
+
if (eventType === "command_response") {
|
|
2122
|
+
const content = String(frame.content ?? "");
|
|
2123
|
+
if (content.includes("Cancellation requested")) {
|
|
2124
|
+
this.lastTurnCancellationSeen = true;
|
|
2125
|
+
}
|
|
2126
|
+
continue;
|
|
2127
|
+
}
|
|
2128
|
+
if (eventType !== "event") continue;
|
|
2129
|
+
const data = frame.data;
|
|
2130
|
+
const namespace = Array.isArray(frame.namespace) ? frame.namespace : [];
|
|
2131
|
+
const mode = String(frame.mode ?? "");
|
|
2132
|
+
if (this.shouldDrop(namespace, mode, data)) {
|
|
2133
|
+
this.turnEventStats.filteredEarly += 1;
|
|
2134
|
+
continue;
|
|
2135
|
+
}
|
|
2136
|
+
if (mode === "custom" && isTurnEndCustomData(data)) {
|
|
2137
|
+
if (!queryStarted || !turnProgressSeen) continue;
|
|
2138
|
+
}
|
|
2139
|
+
streamPayloadSeen = true;
|
|
2140
|
+
if (isTurnProgressChunk(mode, data)) turnProgressSeen = true;
|
|
2141
|
+
yield [namespace, mode, data];
|
|
2142
|
+
if (mode === "custom" && isTurnEndCustomData(data)) {
|
|
2143
|
+
const customType = String(data.type ?? "").trim();
|
|
2144
|
+
this.lastTurnEndState = customType === STREAM_END ? "stream_end" : "completed";
|
|
2145
|
+
yield* this.drainStreamEventsAfterIdle(expectedLoopId);
|
|
2146
|
+
break;
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
} catch (exc) {
|
|
2150
|
+
this.lastTurnErrorMessage = String(exc);
|
|
2151
|
+
throw exc;
|
|
2152
|
+
} finally {
|
|
2153
|
+
this.streaming = false;
|
|
2154
|
+
this.readBusy = false;
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
};
|
|
1444
2158
|
export {
|
|
1445
2159
|
CLIENT_VERSION,
|
|
1446
2160
|
ChatEventTerminal,
|
|
1447
2161
|
Client,
|
|
2162
|
+
CommandClient,
|
|
1448
2163
|
ConnectionError,
|
|
1449
2164
|
ConnectionPool,
|
|
1450
2165
|
DEFAULT_CLIENT_CAPABILITIES,
|
|
1451
2166
|
DEFAULT_DELIVERABLE_PHASES,
|
|
2167
|
+
DEFAULT_POST_IDLE_DRAIN_MS,
|
|
1452
2168
|
DEFAULT_THINKING_STEP_EVENTS,
|
|
1453
2169
|
DaemonError,
|
|
2170
|
+
DaemonSession,
|
|
1454
2171
|
DisconnectCause,
|
|
2172
|
+
ErrIdleTimeout,
|
|
1455
2173
|
ErrPoolExhausted,
|
|
1456
2174
|
ErrQueryBusy,
|
|
1457
2175
|
ErrQueryTimeout,
|
|
@@ -1497,26 +2215,31 @@ export {
|
|
|
1497
2215
|
INTENT_HINT_OCR,
|
|
1498
2216
|
INTENT_HINT_TEXT_COMPLETION,
|
|
1499
2217
|
LOOP_ASSISTANT_OUTPUT_PHASES,
|
|
1500
|
-
Multiplexer,
|
|
1501
2218
|
PROTO_VERSION,
|
|
1502
2219
|
PooledConn,
|
|
1503
2220
|
QueryGate,
|
|
1504
2221
|
REMOVED_INTENT_HINTS,
|
|
1505
2222
|
ReconnectError,
|
|
1506
2223
|
SSEBroadcaster,
|
|
2224
|
+
STREAM_END,
|
|
1507
2225
|
StaleLoopError,
|
|
2226
|
+
StreamCloseFail,
|
|
2227
|
+
StreamCloseSoftComplete,
|
|
1508
2228
|
TimeoutError,
|
|
2229
|
+
TimeoutPolicy,
|
|
2230
|
+
TurnEventStats,
|
|
1509
2231
|
TurnRunner,
|
|
1510
2232
|
VerbosityTier,
|
|
1511
2233
|
authenticate,
|
|
1512
2234
|
bootstrapLoopSession,
|
|
1513
2235
|
checkDaemonStatus,
|
|
1514
2236
|
classifyEventVerbosity,
|
|
2237
|
+
compactAttachments,
|
|
2238
|
+
compactImageAttachment,
|
|
1515
2239
|
connectWithRetries,
|
|
2240
|
+
connectedWebsocket,
|
|
1516
2241
|
connectionInitEnvelope,
|
|
1517
2242
|
decodeMessage,
|
|
1518
|
-
defaultBootstrapFunc,
|
|
1519
|
-
defaultClientFactory,
|
|
1520
2243
|
defaultConfig,
|
|
1521
2244
|
defaultPoolConfig,
|
|
1522
2245
|
disconnectCauseName,
|
|
@@ -1525,12 +2248,18 @@ export {
|
|
|
1525
2248
|
extractSootheLoopID,
|
|
1526
2249
|
extractThinkingStep,
|
|
1527
2250
|
fetchConfigSection,
|
|
2251
|
+
fetchLoopCards,
|
|
1528
2252
|
fetchLoopHistory,
|
|
2253
|
+
fetchLoopMessages,
|
|
1529
2254
|
fetchSkillsCatalog,
|
|
2255
|
+
idleTimeoutForTurn,
|
|
2256
|
+
inboundNeedsDeliveryAck,
|
|
1530
2257
|
inputMessageForLoop,
|
|
1531
2258
|
isCompletionEvent,
|
|
1532
2259
|
isDaemonLive,
|
|
1533
2260
|
isSubagentProgressEvent,
|
|
2261
|
+
isTurnEndCustomData,
|
|
2262
|
+
isTurnProgressChunk,
|
|
1534
2263
|
isValidVerbosityLevel,
|
|
1535
2264
|
loadConfigFromEnv,
|
|
1536
2265
|
newLoopInputMessage,
|
|
@@ -1541,6 +2270,7 @@ export {
|
|
|
1541
2270
|
parseNamespace,
|
|
1542
2271
|
pingEnvelope,
|
|
1543
2272
|
pongEnvelope,
|
|
2273
|
+
protocol1Rpc,
|
|
1544
2274
|
refreshAuthToken,
|
|
1545
2275
|
requestDaemonConfigReload,
|
|
1546
2276
|
requestDaemonShutdown,
|