@mirasoth/soothe-client 0.5.4 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -84,7 +84,7 @@ import {
84
84
  subscribeEnvelope,
85
85
  unsubscribeEnvelope,
86
86
  validateLoopInputIntentHint
87
- } from "./chunk-B5FIL7O4.js";
87
+ } from "./chunk-56Z7SUGW.js";
88
88
 
89
89
  // src/session.ts
90
90
  async function bootstrapLoopSession(client, resumeLoopId, config, loopNew) {
@@ -270,6 +270,10 @@ var CommandClient = class {
270
270
  async autopilotGetJob(jobId) {
271
271
  return this.request("autopilot_get_job", { job_id: jobId });
272
272
  }
273
+ /** Jobs → goals → loops snapshot for CLI top. */
274
+ async autopilotTop(includeTerminal = false) {
275
+ return this.request("autopilot_top", { include_terminal: includeTerminal });
276
+ }
273
277
  async cronAdd(text, priority = 0) {
274
278
  const params = { text };
275
279
  if (priority > 0) params.priority = priority;
@@ -287,7 +291,7 @@ async function checkDaemonStatus(client, timeout) {
287
291
  return client.requestResponse("daemon_status", {}, "daemon_status", timeout ?? 5e3);
288
292
  }
289
293
  async function isDaemonLive(wsURL, timeout) {
290
- const { Client: Client2 } = await import("./client-BOND6P6S.js");
294
+ const { Client: Client2 } = await import("./client-CUYIRMFW.js");
291
295
  const t = timeout ?? 5e3;
292
296
  const client = new Client2(wsURL, defaultConfig());
293
297
  try {
@@ -361,9 +365,6 @@ async function refreshAuthToken(client, refreshToken, timeout) {
361
365
  timeout ?? 15e3
362
366
  );
363
367
  }
364
- async function fetchLoopCards(client, loopID, timeout) {
365
- return client.fetchLoopCards(loopID, timeout);
366
- }
367
368
  async function fetchLoopMessages(client, loopID, opts) {
368
369
  return client.getLoopMessages(
369
370
  loopID,
@@ -374,7 +375,7 @@ async function fetchLoopMessages(client, loopID, opts) {
374
375
  );
375
376
  }
376
377
  async function connectedWebsocket(wsUrl, fn, timeoutMs = 3e4) {
377
- const { Client: Client2 } = await import("./client-BOND6P6S.js");
378
+ const { Client: Client2 } = await import("./client-CUYIRMFW.js");
378
379
  const client = new Client2(wsUrl, defaultConfig());
379
380
  const deadline = Date.now() + timeoutMs;
380
381
  try {
@@ -431,6 +432,59 @@ async function protocol1Rpc(wsUrl, method, params = null, opts = {}) {
431
432
  }
432
433
  }
433
434
 
435
+ // src/turn_boundary.ts
436
+ function formatTurnId(loopId, generation) {
437
+ const lid = String(loopId || "").trim();
438
+ const gen = Number(generation);
439
+ if (!lid || !Number.isFinite(gen) || gen <= 0) return "";
440
+ return `${lid}:${Math.trunc(gen)}`;
441
+ }
442
+ function parseTurnGeneration(turnId) {
443
+ const raw = String(turnId || "").trim();
444
+ if (!raw || !raw.includes(":")) return null;
445
+ const suffix = raw.split(":").pop() ?? "";
446
+ const gen = Number.parseInt(suffix, 10);
447
+ return Number.isFinite(gen) && gen > 0 ? gen : null;
448
+ }
449
+ function frameTurnId(frame) {
450
+ if (!frame || typeof frame !== "object") return null;
451
+ const tid = frame.turn_id;
452
+ if (typeof tid === "string" && tid.trim()) return tid.trim();
453
+ const data = frame.data;
454
+ if (data && typeof data === "object") {
455
+ const inner = data.turn_id;
456
+ if (typeof inner === "string" && inner.trim()) return inner.trim();
457
+ }
458
+ return null;
459
+ }
460
+ function frameSeq(frame) {
461
+ if (!frame || typeof frame !== "object") return null;
462
+ const raw = frame.seq;
463
+ if (typeof raw === "boolean") return null;
464
+ if (typeof raw === "number" && Number.isFinite(raw) && raw >= 0 && Number.isInteger(raw)) {
465
+ return raw;
466
+ }
467
+ return null;
468
+ }
469
+ function turnIdsMatch(expected, candidate) {
470
+ const exp = String(expected || "").trim();
471
+ const cand = String(candidate || "").trim();
472
+ return Boolean(exp) && Boolean(cand) && exp === cand;
473
+ }
474
+ function isTurnTerminalAllowed(opts) {
475
+ if (!opts.queryStarted || !opts.turnProgressSeen) return false;
476
+ return turnIdsMatch(opts.expectedTurnId, opts.frameTurnId);
477
+ }
478
+ function isIdleTerminalAllowed(opts) {
479
+ if (!opts.queryStarted || !String(opts.expectedTurnId || "").trim()) return false;
480
+ const cand = String(opts.frameTurnId || "").trim();
481
+ if (cand) {
482
+ if (!turnIdsMatch(opts.expectedTurnId, cand)) return false;
483
+ return Boolean(opts.turnProgressSeen || opts.cancellationSeen);
484
+ }
485
+ return Boolean(opts.cancellationSeen);
486
+ }
487
+
434
488
  // src/appkit/broadcaster.ts
435
489
  var SUBSCRIBER_QUEUE_CAP = 100;
436
490
  var SSEBroadcaster = class {
@@ -1387,8 +1441,9 @@ var TURN_END_IDLE = "status.idle";
1387
1441
  var TURN_END_STOPPED = "status.stopped";
1388
1442
  var TurnLifecycleGate = class {
1389
1443
  sawRunning = false;
1390
- sawStreamPayload = false;
1391
1444
  sawTurnProgress = false;
1445
+ expectedTurnId = null;
1446
+ cancellationSeen = false;
1392
1447
  observe(msg) {
1393
1448
  const frame = normalizeFrame(msg);
1394
1449
  if (!frame) return;
@@ -1396,22 +1451,43 @@ var TurnLifecycleGate = class {
1396
1451
  if (typ === "status") {
1397
1452
  if (String(frame.state ?? "").trim().toLowerCase() === "running") {
1398
1453
  this.sawRunning = true;
1454
+ const statusTurn = frameTurnId(frame);
1455
+ if (statusTurn) {
1456
+ const newGen = parseTurnGeneration(statusTurn);
1457
+ const oldGen = parseTurnGeneration(this.expectedTurnId);
1458
+ if (this.expectedTurnId === null || newGen !== null && (oldGen === null || newGen >= oldGen)) {
1459
+ if (this.expectedTurnId && statusTurn !== this.expectedTurnId) {
1460
+ this.sawTurnProgress = false;
1461
+ }
1462
+ this.expectedTurnId = statusTurn;
1463
+ }
1464
+ }
1399
1465
  }
1400
1466
  return;
1401
1467
  }
1402
1468
  if (typ === "event") {
1403
- this.sawStreamPayload = true;
1404
1469
  const mode = String(frame.mode ?? "");
1405
1470
  if (isTurnProgressChunk(mode, frame.data)) {
1406
1471
  this.sawTurnProgress = true;
1407
1472
  }
1408
1473
  }
1409
1474
  }
1410
- allowStreamEnd() {
1411
- return this.sawRunning && this.sawTurnProgress;
1475
+ allowStreamEnd(frameTurn) {
1476
+ return isTurnTerminalAllowed({
1477
+ expectedTurnId: this.expectedTurnId,
1478
+ frameTurnId: frameTurn,
1479
+ queryStarted: this.sawRunning,
1480
+ turnProgressSeen: this.sawTurnProgress
1481
+ });
1412
1482
  }
1413
- allowIdleComplete() {
1414
- return this.sawRunning && this.sawStreamPayload;
1483
+ allowIdleComplete(frameTurn) {
1484
+ return isIdleTerminalAllowed({
1485
+ expectedTurnId: this.expectedTurnId,
1486
+ frameTurnId: frameTurn,
1487
+ queryStarted: this.sawRunning,
1488
+ turnProgressSeen: this.sawTurnProgress,
1489
+ cancellationSeen: this.cancellationSeen
1490
+ });
1415
1491
  }
1416
1492
  };
1417
1493
  var TurnBoundary = class {
@@ -1426,17 +1502,23 @@ var TurnBoundary = class {
1426
1502
  const typ = String(frame.type ?? "");
1427
1503
  if (typ === "status") {
1428
1504
  const state = String(frame.state ?? "").trim().toLowerCase();
1505
+ const frameTurn = frameTurnId(frame);
1429
1506
  if (state === "stopped" && this.gate.sawRunning) {
1507
+ if (this.gate.expectedTurnId && !turnIdsMatch(this.gate.expectedTurnId, frameTurn)) {
1508
+ return [false, ""];
1509
+ }
1430
1510
  return this.mark(TURN_END_STOPPED);
1431
1511
  }
1432
- if (state === "idle" && this.gate.allowIdleComplete()) {
1512
+ if (state === "idle" && this.gate.allowIdleComplete(frameTurn)) {
1433
1513
  return this.mark(TURN_END_IDLE);
1434
1514
  }
1435
1515
  return [false, ""];
1436
1516
  }
1437
1517
  if (typ === "event") {
1438
1518
  const mode = String(frame.mode ?? "");
1439
- if (mode === "custom" && isTurnEndCustomData(frame.data) && this.gate.allowStreamEnd()) {
1519
+ const data = frame.data;
1520
+ const dataTurn = frameTurnId(data) || frameTurnId(frame);
1521
+ if (mode === "custom" && isTurnEndCustomData(data) && this.gate.allowStreamEnd(dataTurn)) {
1440
1522
  return this.mark(TURN_END_STREAM_END);
1441
1523
  }
1442
1524
  }
@@ -1462,20 +1544,26 @@ function normalizeFrame(msg) {
1462
1544
  return inner;
1463
1545
  }
1464
1546
  if (inner && typeof inner === "object" && inner.mode) {
1465
- return {
1547
+ const out = {
1466
1548
  type: "event",
1467
1549
  mode: inner.mode,
1468
1550
  data: inner.data,
1469
1551
  namespace: inner.namespace ?? payload.namespace
1470
1552
  };
1553
+ const tid = inner.turn_id ?? payload.turn_id ?? m.turn_id;
1554
+ if (tid) out.turn_id = tid;
1555
+ return out;
1471
1556
  }
1472
1557
  if (payload.mode) {
1473
- return {
1558
+ const out = {
1474
1559
  type: "event",
1475
1560
  mode: payload.mode,
1476
1561
  data: payload.data,
1477
1562
  namespace: payload.namespace
1478
1563
  };
1564
+ const tid = payload.turn_id ?? m.turn_id;
1565
+ if (tid) out.turn_id = tid;
1566
+ return out;
1479
1567
  }
1480
1568
  return null;
1481
1569
  }
@@ -2113,25 +2201,6 @@ var DaemonSession = class {
2113
2201
  return this.rpcClient.listLoops(15e3);
2114
2202
  });
2115
2203
  }
2116
- async fetchLoopCards(loopId) {
2117
- const lid = String(loopId || "").trim();
2118
- if (!lid) return { cards: [], seq: 0, contextTokens: 0, success: false };
2119
- return this.withRpcLock(async () => {
2120
- await this.ensureRpcConnected();
2121
- try {
2122
- const resp = await this.rpcClient.fetchLoopCards(lid, 3e4);
2123
- const rawCards = resp.cards;
2124
- return {
2125
- cards: Array.isArray(rawCards) ? rawCards : [],
2126
- seq: Number(resp.seq ?? 0),
2127
- contextTokens: typeof resp.context_tokens === "number" && resp.context_tokens >= 0 ? resp.context_tokens : 0,
2128
- success: true
2129
- };
2130
- } catch {
2131
- return { cards: [], seq: 0, contextTokens: 0, success: false };
2132
- }
2133
- });
2134
- }
2135
2204
  async fetchLoopHistory(loopId) {
2136
2205
  const lid = String(loopId || "").trim();
2137
2206
  if (!lid) {
@@ -2183,7 +2252,7 @@ var DaemonSession = class {
2183
2252
  this.lastTurnErrorMessage = null;
2184
2253
  let queryStarted = false;
2185
2254
  let expectedLoopId = this.loopId;
2186
- let streamPayloadSeen = false;
2255
+ let expectedTurnId = null;
2187
2256
  let turnProgressSeen = false;
2188
2257
  this.streaming = true;
2189
2258
  const absoluteDeadline = opts.maxWaitMs !== void 0 && opts.maxWaitMs > 0 ? Date.now() + opts.maxWaitMs : null;
@@ -2217,6 +2286,17 @@ var DaemonSession = class {
2217
2286
  if (expectedLoopId && typeof eventLoopId === "string" && eventLoopId && eventLoopId !== expectedLoopId) {
2218
2287
  continue;
2219
2288
  }
2289
+ const evTurnId = frameTurnId(frame);
2290
+ const statusState = eventType === "status" ? String(frame.state ?? "") : "";
2291
+ const isRunningStatus = statusState === "running";
2292
+ const isTerminalStatus = statusState === "idle" || statusState === "stopped";
2293
+ if (expectedTurnId && (eventType === "event" || eventType === "status") && !isRunningStatus) {
2294
+ if (isTerminalStatus) {
2295
+ if (evTurnId && !turnIdsMatch(expectedTurnId, evTurnId)) continue;
2296
+ } else if (!turnIdsMatch(expectedTurnId, evTurnId)) {
2297
+ continue;
2298
+ }
2299
+ }
2220
2300
  if (eventType === "error") {
2221
2301
  const errObj = frame.error ?? {};
2222
2302
  throw new Error(String(errObj.message || frame.message || "daemon error"));
@@ -2227,16 +2307,37 @@ var DaemonSession = class {
2227
2307
  this.loopId = loopEv;
2228
2308
  expectedLoopId = loopEv;
2229
2309
  }
2230
- const state = String(frame.state ?? "");
2231
- if (state === "running") {
2310
+ if (statusState === "running") {
2232
2311
  queryStarted = true;
2233
- } else if (queryStarted && state === "stopped") {
2234
- this.lastTurnEndState = state;
2312
+ const statusTurn = frameTurnId(frame);
2313
+ if (statusTurn) {
2314
+ const newGen = parseTurnGeneration(statusTurn);
2315
+ const oldGen = parseTurnGeneration(expectedTurnId);
2316
+ if (expectedTurnId === null || newGen !== null && (oldGen === null || newGen >= oldGen)) {
2317
+ if (expectedTurnId && statusTurn !== expectedTurnId) {
2318
+ turnProgressSeen = false;
2319
+ }
2320
+ expectedTurnId = statusTurn;
2321
+ }
2322
+ }
2323
+ } else if (queryStarted && statusState === "stopped") {
2324
+ const stopTurn = frameTurnId(frame);
2325
+ if (expectedTurnId && !turnIdsMatch(expectedTurnId, stopTurn)) continue;
2326
+ this.lastTurnEndState = statusState;
2235
2327
  yield* this.drainStreamEventsAfterIdle(expectedLoopId);
2236
2328
  break;
2237
- } else if (queryStarted && state === "idle") {
2238
- if (!streamPayloadSeen && !this.lastTurnCancellationSeen) continue;
2239
- this.lastTurnEndState = state;
2329
+ } else if (queryStarted && statusState === "idle") {
2330
+ const idleTurn = frameTurnId(frame);
2331
+ if (!isIdleTerminalAllowed({
2332
+ expectedTurnId,
2333
+ frameTurnId: idleTurn,
2334
+ queryStarted,
2335
+ turnProgressSeen,
2336
+ cancellationSeen: this.lastTurnCancellationSeen
2337
+ })) {
2338
+ continue;
2339
+ }
2340
+ this.lastTurnEndState = statusState;
2240
2341
  yield* this.drainStreamEventsAfterIdle(expectedLoopId);
2241
2342
  break;
2242
2343
  }
@@ -2258,9 +2359,16 @@ var DaemonSession = class {
2258
2359
  continue;
2259
2360
  }
2260
2361
  if (mode === "custom" && isTurnEndCustomData(data)) {
2261
- if (!queryStarted || !turnProgressSeen) continue;
2362
+ const dataTurn = frameTurnId(data) || evTurnId;
2363
+ if (!isTurnTerminalAllowed({
2364
+ expectedTurnId,
2365
+ frameTurnId: dataTurn,
2366
+ queryStarted,
2367
+ turnProgressSeen
2368
+ })) {
2369
+ continue;
2370
+ }
2262
2371
  }
2263
- streamPayloadSeen = true;
2264
2372
  if (isTurnProgressChunk(mode, data)) turnProgressSeen = true;
2265
2373
  yield [namespace, mode, data];
2266
2374
  if (mode === "custom" && isTurnEndCustomData(data)) {
@@ -2456,19 +2564,23 @@ export {
2456
2564
  extractSootheLoopID,
2457
2565
  extractThinkingStep,
2458
2566
  fetchConfigSection,
2459
- fetchLoopCards,
2460
2567
  fetchLoopHistory,
2461
2568
  fetchLoopMessages,
2462
2569
  fetchSkillsCatalog,
2570
+ formatTurnId,
2571
+ frameSeq,
2572
+ frameTurnId,
2463
2573
  idleTimeoutForTurn,
2464
2574
  inboundNeedsDeliveryAck,
2465
2575
  inputMessageForLoop,
2466
2576
  isCompletionEvent,
2467
2577
  isDaemonLive,
2468
2578
  isDaemonTurnEndEvent,
2579
+ isIdleTerminalAllowed,
2469
2580
  isSubagentProgressEvent,
2470
2581
  isTurnEndCustomData,
2471
2582
  isTurnProgressChunk,
2583
+ isTurnTerminalAllowed,
2472
2584
  isValidVerbosityLevel,
2473
2585
  loadConfigFromEnv,
2474
2586
  newLoopInputMessage,
@@ -2478,6 +2590,7 @@ export {
2478
2590
  notificationEnvelope,
2479
2591
  parseCardCustomPayload,
2480
2592
  parseNamespace,
2593
+ parseTurnGeneration,
2481
2594
  pingEnvelope,
2482
2595
  pongEnvelope,
2483
2596
  protocol1Rpc,
@@ -2488,6 +2601,7 @@ export {
2488
2601
  shouldShow,
2489
2602
  splitWirePayload,
2490
2603
  subscribeEnvelope,
2604
+ turnIdsMatch,
2491
2605
  unsubscribeEnvelope,
2492
2606
  validateLoopInputIntentHint,
2493
2607
  waitDaemonReady,