@ganglion/xacpx-relay 0.14.1 → 0.14.2-beta.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.
Files changed (77) hide show
  1. package/dist/cli.js +227 -17
  2. package/dist/http/app.d.ts +3 -0
  3. package/dist/http/compact-history.d.ts +2 -0
  4. package/dist/relay-web/assets/DashboardView-C4uZxFJc.js +458 -0
  5. package/dist/relay-web/assets/{DashboardView-B2jBgxiL.css → DashboardView-CRYuoJ2T.css} +1 -1
  6. package/dist/relay-web/assets/{InviteView-CXCceyjs.js → InviteView-DJtV1g3a.js} +1 -1
  7. package/dist/relay-web/assets/{LoginView-s6HkroR1.js → LoginView-DrsAFowP.js} +1 -1
  8. package/dist/relay-web/assets/{SettingsView-a0OmqD6M.js → SettingsView-B-hRDWwa.js} +1 -1
  9. package/dist/relay-web/assets/{abnfDiagram-VRR7QNED-2JIaFFTg.js → abnfDiagram-VRR7QNED-DvXIiGiP.js} +1 -1
  10. package/dist/relay-web/assets/{arc-33J3JAHf.js → arc-lGeGPRGC.js} +1 -1
  11. package/dist/relay-web/assets/{architectureDiagram-ZJ3FMSHR-BchSqVyS.js → architectureDiagram-ZJ3FMSHR-BlfrbJZ7.js} +1 -1
  12. package/dist/relay-web/assets/{blockDiagram-677ZJIJ3-CNeeN8w_.js → blockDiagram-677ZJIJ3-D9CpR2tq.js} +1 -1
  13. package/dist/relay-web/assets/{c4Diagram-LMCZKHZV-DpHV_2Bl.js → c4Diagram-LMCZKHZV-D5gJNUFs.js} +1 -1
  14. package/dist/relay-web/assets/channel-BgqBTryA.js +1 -0
  15. package/dist/relay-web/assets/{chunk-2Q5K7J3B-CTZoYiXU.js → chunk-2Q5K7J3B-CEe3zyzr.js} +1 -1
  16. package/dist/relay-web/assets/{chunk-32BRIVSS-HaVMgCVO.js → chunk-32BRIVSS-DPfCCn5M.js} +1 -1
  17. package/dist/relay-web/assets/{chunk-5VM5RSS4-ojMow50b.js → chunk-5VM5RSS4-CFDfXqUR.js} +1 -1
  18. package/dist/relay-web/assets/{chunk-EX3LRPZG-BB9v3Fyx.js → chunk-EX3LRPZG-BJ8FZX61.js} +1 -1
  19. package/dist/relay-web/assets/{chunk-JWPE2WC7-DpPVEV0e.js → chunk-JWPE2WC7-R0jCcwG_.js} +1 -1
  20. package/dist/relay-web/assets/{chunk-MOJQB5TN-BVTnyTR0.js → chunk-MOJQB5TN-_QHvguKZ.js} +1 -1
  21. package/dist/relay-web/assets/{chunk-RYQCIY6F-CPUenDRh.js → chunk-RYQCIY6F-C2f2OGXu.js} +1 -1
  22. package/dist/relay-web/assets/{chunk-V7JOEXUC-D5wr2ziH.js → chunk-V7JOEXUC-CZ8cvGbJ.js} +1 -1
  23. package/dist/relay-web/assets/{chunk-VR4S4FIN-CM-9QhEh.js → chunk-VR4S4FIN-BdqWtJgq.js} +1 -1
  24. package/dist/relay-web/assets/{chunk-XXDRQBXY-DYTAeiTM.js → chunk-XXDRQBXY-Cw710t3a.js} +1 -1
  25. package/dist/relay-web/assets/classDiagram-OUVF2IWQ-DwhIEwUq.js +1 -0
  26. package/dist/relay-web/assets/classDiagram-v2-EOCWNBFH-DwhIEwUq.js +1 -0
  27. package/dist/relay-web/assets/{cose-bilkent-JH36ORCC-B5ht-Nkg.js → cose-bilkent-JH36ORCC-lVLW5WEW.js} +1 -1
  28. package/dist/relay-web/assets/{cynefin-VYW2F7L2-i7N2S7w1.js → cynefin-VYW2F7L2-DzXb1Qg3.js} +1 -1
  29. package/dist/relay-web/assets/{cynefinDiagram-TSTJHNR4-CFZRrysn.js → cynefinDiagram-TSTJHNR4-DorSrk5l.js} +1 -1
  30. package/dist/relay-web/assets/{dagre-VKFMJZFB-C00r7gwi.js → dagre-VKFMJZFB-C7KRVdBQ.js} +1 -1
  31. package/dist/relay-web/assets/{diagram-FQU43EPY-B3HmYhcj.js → diagram-FQU43EPY-DlpxPgGg.js} +1 -1
  32. package/dist/relay-web/assets/{diagram-G47NLZAW-DmRsVi1P.js → diagram-G47NLZAW-DpyBwPlZ.js} +1 -1
  33. package/dist/relay-web/assets/{diagram-NH7WQ7WH-CVzGQZCx.js → diagram-NH7WQ7WH-BtT1PHdr.js} +1 -1
  34. package/dist/relay-web/assets/{diagram-OA4YK3LP-BdVMJv3M.js → diagram-OA4YK3LP-Jw40hyJ4.js} +1 -1
  35. package/dist/relay-web/assets/{diagram-WEI45ONY-DUcNdB4v.js → diagram-WEI45ONY-BVHCnHzc.js} +1 -1
  36. package/dist/relay-web/assets/{ebnfDiagram-CCIWWBDH-CPz6Kmbm.js → ebnfDiagram-CCIWWBDH-B8tIY0DT.js} +1 -1
  37. package/dist/relay-web/assets/{erDiagram-Q63AITRT-VeMZYbaf.js → erDiagram-Q63AITRT-Bbb7pMuu.js} +1 -1
  38. package/dist/relay-web/assets/{flowDiagram-23GEKE2U-DHHV5pKm.js → flowDiagram-23GEKE2U-C6O-k1Lh.js} +1 -1
  39. package/dist/relay-web/assets/{ganttDiagram-NO4QXBWP-XuH2e6HD.js → ganttDiagram-NO4QXBWP-BvMFkuHz.js} +1 -1
  40. package/dist/relay-web/assets/{gitGraphDiagram-IHSO6WYX-ovsf1Fl5.js → gitGraphDiagram-IHSO6WYX-CPtPnpx1.js} +1 -1
  41. package/dist/relay-web/assets/{index-Kpr5-jgf.js → index-jjx4H84v.js} +2 -2
  42. package/dist/relay-web/assets/{infoDiagram-FWYZ7A6U-DScoKb0G.js → infoDiagram-FWYZ7A6U-DCj8dx4B.js} +1 -1
  43. package/dist/relay-web/assets/{ishikawaDiagram-FXEZZL3T-BBxwHVZf.js → ishikawaDiagram-FXEZZL3T-Dphi71w0.js} +1 -1
  44. package/dist/relay-web/assets/{journeyDiagram-5HDEW3XC-CxXhjgjC.js → journeyDiagram-5HDEW3XC-Dte5L42r.js} +1 -1
  45. package/dist/relay-web/assets/{kanban-definition-HUTT4EX6-DrNv9cZH.js → kanban-definition-HUTT4EX6-Vm5E-3-k.js} +1 -1
  46. package/dist/relay-web/assets/{linear-CkusLJGE.js → linear-BytQ8H5b.js} +1 -1
  47. package/dist/relay-web/assets/{mermaid.core-CZRtox5e.js → mermaid.core-DWa8K3k_.js} +4 -4
  48. package/dist/relay-web/assets/{mindmap-definition-LN4V7U3C-B1PXNCdk.js → mindmap-definition-LN4V7U3C-BcgOdq0O.js} +1 -1
  49. package/dist/relay-web/assets/{pegDiagram-2B236MQR-6XPhI4ao.js → pegDiagram-2B236MQR-6wFGkTj3.js} +1 -1
  50. package/dist/relay-web/assets/{pieDiagram-ENE6RG2P-CskGGVZw.js → pieDiagram-ENE6RG2P-DbeoojG3.js} +1 -1
  51. package/dist/relay-web/assets/{quadrantDiagram-ABIIQ3AL-DZRB7SqC.js → quadrantDiagram-ABIIQ3AL-L8lhatCO.js} +1 -1
  52. package/dist/relay-web/assets/{railroadDiagram-RFXS5EU6-CTAxd2yc.js → railroadDiagram-RFXS5EU6-CmNsEkKG.js} +1 -1
  53. package/dist/relay-web/assets/{requirementDiagram-TGXJPOKE-BF5gXh18.js → requirementDiagram-TGXJPOKE-CuDqGM8H.js} +1 -1
  54. package/dist/relay-web/assets/{sankeyDiagram-HTMAVEWB-CM8ZxfPM.js → sankeyDiagram-HTMAVEWB-Cpmd-8x3.js} +1 -1
  55. package/dist/relay-web/assets/{sequenceDiagram-DBY2YBRQ-QjI-fgd1.js → sequenceDiagram-DBY2YBRQ-ipwgpvyZ.js} +1 -1
  56. package/dist/relay-web/assets/{sizeCapture-X5ZJPWSS-05mn0JnJ.js → sizeCapture-X5ZJPWSS-ILRFTwii.js} +1 -1
  57. package/dist/relay-web/assets/{stateDiagram-2N3HPSRC-BNQ-XmgM.js → stateDiagram-2N3HPSRC-CHmuDF7F.js} +1 -1
  58. package/dist/relay-web/assets/stateDiagram-v2-6OUMAXLB-BMsDRO4U.js +1 -0
  59. package/dist/relay-web/assets/{swimlanes-5IMT3BWC-B0on9_MK.js → swimlanes-5IMT3BWC-DZhSWujN.js} +2 -2
  60. package/dist/relay-web/assets/swimlanesDiagram-G3AALYLV-C6GaSutc.js +8 -0
  61. package/dist/relay-web/assets/{theme-COCM1hBm.js → theme-DS8opD1d.js} +1 -1
  62. package/dist/relay-web/assets/{timeline-definition-FHXFAJF6-So8J-054.js → timeline-definition-FHXFAJF6-C2n1uG0w.js} +1 -1
  63. package/dist/relay-web/assets/{vennDiagram-L72KCM5P-B_senTb4.js → vennDiagram-L72KCM5P-Dotrpr6i.js} +1 -1
  64. package/dist/relay-web/assets/{wardleyDiagram-EHGQE667-ApGsv91F.js → wardleyDiagram-EHGQE667-B8ZSYG_L.js} +1 -1
  65. package/dist/relay-web/assets/{xychartDiagram-FW5EYKEG-BpdYYkKu.js → xychartDiagram-FW5EYKEG-BlayRcZO.js} +1 -1
  66. package/dist/relay-web/index.html +1 -1
  67. package/dist/relay-web/sw.js +1 -1
  68. package/dist/server.d.ts +2 -0
  69. package/dist/stores/messages.d.ts +3 -1
  70. package/dist/stores/turn-slot-anchors.d.ts +47 -0
  71. package/package.json +2 -2
  72. package/dist/relay-web/assets/DashboardView-CgzIgCQ5.js +0 -458
  73. package/dist/relay-web/assets/channel-CGmHIR5G.js +0 -1
  74. package/dist/relay-web/assets/classDiagram-OUVF2IWQ-BRju-dhQ.js +0 -1
  75. package/dist/relay-web/assets/classDiagram-v2-EOCWNBFH-BRju-dhQ.js +0 -1
  76. package/dist/relay-web/assets/stateDiagram-v2-6OUMAXLB-BSYqyg2P.js +0 -1
  77. package/dist/relay-web/assets/swimlanesDiagram-G3AALYLV-CZ6U0BMI.js +0 -8
package/dist/cli.js CHANGED
@@ -7397,7 +7397,10 @@ function initSchema(db) {
7397
7397
  queue_item_id TEXT,
7398
7398
  queue_fallback INTEGER NOT NULL DEFAULT 0,
7399
7399
  origin_queue_item_id TEXT,
7400
- prompt_request_id TEXT
7400
+ prompt_request_id TEXT,
7401
+ started_at INTEGER,
7402
+ slot_after_id INTEGER,
7403
+ started_after_seq INTEGER
7401
7404
  );
7402
7405
  CREATE INDEX IF NOT EXISTS idx_messages_session ON messages (instance_id, session_alias, id);
7403
7406
  CREATE TABLE IF NOT EXISTS recovery_receipts (
@@ -7406,6 +7409,16 @@ function initSchema(db) {
7406
7409
  created_at TEXT NOT NULL,
7407
7410
  PRIMARY KEY (instance_id, recovery_id)
7408
7411
  );
7412
+ CREATE TABLE IF NOT EXISTS turn_slot_anchors (
7413
+ instance_id TEXT NOT NULL,
7414
+ session_alias TEXT NOT NULL,
7415
+ recovery_id TEXT NOT NULL DEFAULT '',
7416
+ slot_after_id INTEGER NOT NULL,
7417
+ started_at INTEGER,
7418
+ started_after_seq INTEGER,
7419
+ PRIMARY KEY (instance_id, recovery_id)
7420
+ );
7421
+ CREATE INDEX IF NOT EXISTS idx_turn_slot_anchors_session ON turn_slot_anchors (instance_id, session_alias);
7409
7422
  CREATE TABLE IF NOT EXISTS pending_completion_routes (
7410
7423
  request_message_id TEXT PRIMARY KEY,
7411
7424
  account_id TEXT NOT NULL,
@@ -7449,6 +7462,15 @@ function initSchema(db) {
7449
7462
  if (!messageCols.some((c) => c.name === "prompt_request_id")) {
7450
7463
  db.exec("ALTER TABLE messages ADD COLUMN prompt_request_id TEXT");
7451
7464
  }
7465
+ if (!messageCols.some((c) => c.name === "started_at")) {
7466
+ db.exec("ALTER TABLE messages ADD COLUMN started_at INTEGER");
7467
+ }
7468
+ if (!messageCols.some((c) => c.name === "slot_after_id")) {
7469
+ db.exec("ALTER TABLE messages ADD COLUMN slot_after_id INTEGER");
7470
+ }
7471
+ if (!messageCols.some((c) => c.name === "started_after_seq")) {
7472
+ db.exec("ALTER TABLE messages ADD COLUMN started_after_seq INTEGER");
7473
+ }
7452
7474
  const instanceCols = db.all("PRAGMA table_info(instances)");
7453
7475
  if (!instanceCols.some((c) => c.name === "capabilities_json")) {
7454
7476
  db.exec("ALTER TABLE instances ADD COLUMN capabilities_json TEXT");
@@ -7597,6 +7619,7 @@ class AccountStore {
7597
7619
  this.db.exec("BEGIN");
7598
7620
  try {
7599
7621
  this.db.run("DELETE FROM messages WHERE instance_id IN (SELECT id FROM instances WHERE account_id = ?)", [accountId]);
7622
+ this.db.run("DELETE FROM turn_slot_anchors WHERE instance_id IN (SELECT id FROM instances WHERE account_id = ?)", [accountId]);
7600
7623
  this.db.run("DELETE FROM instances WHERE account_id = ?", [accountId]);
7601
7624
  this.db.run("DELETE FROM pairing_tokens WHERE account_id = ?", [accountId]);
7602
7625
  this.db.run("DELETE FROM web_sessions WHERE account_id = ?", [accountId]);
@@ -7761,6 +7784,7 @@ class InstanceStore {
7761
7784
  remove(instanceId, accountId) {
7762
7785
  if (!this.getOwned(instanceId, accountId))
7763
7786
  return false;
7787
+ this.db.run("DELETE FROM turn_slot_anchors WHERE instance_id = ?", [instanceId]);
7764
7788
  this.db.run("DELETE FROM instances WHERE id = ?", [instanceId]);
7765
7789
  return true;
7766
7790
  }
@@ -7809,6 +7833,9 @@ function toDto(r) {
7809
7833
  text: r.text,
7810
7834
  createdAt: r.created_at,
7811
7835
  ...r.queue_item_id ? { queueItemId: r.queue_item_id } : {},
7836
+ ...typeof r.started_at === "number" ? { startedAt: r.started_at } : {},
7837
+ ...typeof r.slot_after_id === "number" ? { slotAfterId: r.slot_after_id } : {},
7838
+ ...typeof r.started_after_seq === "number" ? { startedAfterSeq: r.started_after_seq } : {},
7812
7839
  ...r.structured ? { structured: JSON.parse(r.structured) } : {},
7813
7840
  ...r.attachments ? { attachments: JSON.parse(r.attachments) } : {}
7814
7841
  };
@@ -7821,8 +7848,8 @@ class MessageStore {
7821
7848
  this.db = db;
7822
7849
  this.now = now;
7823
7850
  }
7824
- append(instanceId, sessionAlias, direction, text, structured, attachments, promptRequestId, createdAt) {
7825
- this.db.run("INSERT INTO messages (instance_id, session_alias, direction, text, created_at, structured, attachments, prompt_request_id) VALUES (?,?,?,?,?,?,?,?)", [
7851
+ append(instanceId, sessionAlias, direction, text, structured, attachments, promptRequestId, createdAt, startedAt, slotAfterId, startedAfterSeq) {
7852
+ this.db.run("INSERT INTO messages (instance_id, session_alias, direction, text, created_at, structured, attachments, prompt_request_id, started_at, slot_after_id, started_after_seq) VALUES (?,?,?,?,?,?,?,?,?,?,?)", [
7826
7853
  instanceId,
7827
7854
  sessionAlias,
7828
7855
  direction,
@@ -7830,7 +7857,10 @@ class MessageStore {
7830
7857
  createdAt ?? this.now().toISOString(),
7831
7858
  structured ? JSON.stringify(structured) : null,
7832
7859
  attachments && attachments.length > 0 ? JSON.stringify(attachments) : null,
7833
- promptRequestId ?? null
7860
+ promptRequestId ?? null,
7861
+ startedAt ?? null,
7862
+ slotAfterId ?? null,
7863
+ startedAfterSeq ?? null
7834
7864
  ]);
7835
7865
  return this.db.get("SELECT last_insert_rowid() AS id").id;
7836
7866
  }
@@ -7938,7 +7968,7 @@ class MessageStore {
7938
7968
  listBySession(accountId, instanceId, sessionAlias, opts = {}) {
7939
7969
  const limit = opts.limit ?? 100;
7940
7970
  const before = opts.before ?? null;
7941
- const rows = this.db.all(`SELECT m.id, m.instance_id, m.session_alias, m.direction, m.text, m.created_at, m.structured, m.attachments, m.queue_item_id
7971
+ const rows = this.db.all(`SELECT m.id, m.instance_id, m.session_alias, m.direction, m.text, m.created_at, m.structured, m.attachments, m.queue_item_id, m.started_at, m.slot_after_id, m.started_after_seq
7942
7972
  FROM messages m JOIN instances i ON i.id = m.instance_id
7943
7973
  WHERE i.account_id = ? AND m.instance_id = ? AND m.session_alias = ?
7944
7974
  AND (? IS NULL OR m.id < ?)
@@ -7951,11 +7981,14 @@ class MessageStore {
7951
7981
  };
7952
7982
  }
7953
7983
  getById(accountId, instanceId, sessionAlias, id) {
7954
- const row = this.db.get(`SELECT m.id, m.instance_id, m.session_alias, m.direction, m.text, m.created_at, m.structured, m.attachments, m.queue_item_id
7984
+ const row = this.db.get(`SELECT m.id, m.instance_id, m.session_alias, m.direction, m.text, m.created_at, m.structured, m.attachments, m.queue_item_id, m.started_at, m.slot_after_id, m.started_after_seq
7955
7985
  FROM messages m JOIN instances i ON i.id = m.instance_id
7956
7986
  WHERE i.account_id = ? AND m.instance_id = ? AND m.session_alias = ? AND m.id = ?`, [accountId, instanceId, sessionAlias, id]);
7957
7987
  return row ? toDto(row) : null;
7958
7988
  }
7989
+ lastId(instanceId, sessionAlias) {
7990
+ return this.db.get("SELECT id FROM messages WHERE instance_id = ? AND session_alias = ? ORDER BY id DESC LIMIT 1", [instanceId, sessionAlias])?.id;
7991
+ }
7959
7992
  deleteBySession(instanceId, sessionAlias) {
7960
7993
  const before = this.db.get("SELECT COUNT(*) AS n FROM messages WHERE instance_id = ? AND session_alias = ?", [instanceId, sessionAlias]);
7961
7994
  this.db.run("DELETE FROM messages WHERE instance_id = ? AND session_alias = ?", [instanceId, sessionAlias]);
@@ -8065,6 +8098,75 @@ class RecoveryReceiptStore {
8065
8098
  }
8066
8099
  }
8067
8100
 
8101
+ // packages/relay/src/stores/turn-slot-anchors.ts
8102
+ var LEGACY_RECOVERY_ID_PREFIX = "\x00legacy:";
8103
+ function canonicalRecoveryId(sessionAlias, recoveryId) {
8104
+ return typeof recoveryId === "string" && recoveryId.length > 0 ? recoveryId : `${LEGACY_RECOVERY_ID_PREFIX}${sessionAlias}`;
8105
+ }
8106
+
8107
+ class TurnSlotAnchorStore {
8108
+ db;
8109
+ constructor(db) {
8110
+ this.db = db;
8111
+ }
8112
+ put(row) {
8113
+ this.db.run(`INSERT INTO turn_slot_anchors (
8114
+ instance_id, session_alias, recovery_id, slot_after_id, started_at, started_after_seq
8115
+ ) VALUES (?,?,?,?,?,?)
8116
+ ON CONFLICT(instance_id, recovery_id) DO UPDATE SET
8117
+ session_alias = excluded.session_alias,
8118
+ slot_after_id = excluded.slot_after_id,
8119
+ started_at = excluded.started_at,
8120
+ started_after_seq = excluded.started_after_seq`, [
8121
+ row.instanceId,
8122
+ row.sessionAlias,
8123
+ row.recoveryId,
8124
+ row.slotAfterId,
8125
+ row.startedAt ?? null,
8126
+ row.startedAfterSeq ?? null
8127
+ ]);
8128
+ }
8129
+ get(instanceId, recoveryId) {
8130
+ return this.toAnchor(this.db.get(`SELECT instance_id, session_alias, recovery_id, slot_after_id, started_at, started_after_seq
8131
+ FROM turn_slot_anchors WHERE instance_id = ? AND recovery_id = ?`, [instanceId, recoveryId]));
8132
+ }
8133
+ take(instanceId, sessionAlias, recoveryId) {
8134
+ const id = canonicalRecoveryId(sessionAlias, recoveryId);
8135
+ const row = this.get(instanceId, id);
8136
+ if (!row)
8137
+ return;
8138
+ this.db.run("DELETE FROM turn_slot_anchors WHERE instance_id = ? AND recovery_id = ?", [row.instanceId, row.recoveryId]);
8139
+ return row;
8140
+ }
8141
+ retain(instanceId, keepRecoveryIds) {
8142
+ if (keepRecoveryIds.size === 0) {
8143
+ this.db.run("DELETE FROM turn_slot_anchors WHERE instance_id = ?", [instanceId]);
8144
+ return;
8145
+ }
8146
+ const ids = [...keepRecoveryIds];
8147
+ const placeholders = ids.map(() => "?").join(",");
8148
+ this.db.run(`DELETE FROM turn_slot_anchors WHERE instance_id = ? AND recovery_id NOT IN (${placeholders})`, [instanceId, ...ids]);
8149
+ }
8150
+ deleteBySession(instanceId, sessionAlias) {
8151
+ this.db.run("DELETE FROM turn_slot_anchors WHERE instance_id = ? AND session_alias = ?", [instanceId, sessionAlias]);
8152
+ }
8153
+ deleteByInstance(instanceId) {
8154
+ this.db.run("DELETE FROM turn_slot_anchors WHERE instance_id = ?", [instanceId]);
8155
+ }
8156
+ toAnchor(row) {
8157
+ if (!row)
8158
+ return;
8159
+ return {
8160
+ instanceId: row.instance_id,
8161
+ sessionAlias: row.session_alias,
8162
+ recoveryId: row.recovery_id,
8163
+ slotAfterId: row.slot_after_id,
8164
+ ...typeof row.started_at === "number" ? { startedAt: row.started_at } : {},
8165
+ ...typeof row.started_after_seq === "number" ? { startedAfterSeq: row.started_after_seq } : {}
8166
+ };
8167
+ }
8168
+ }
8169
+
8068
8170
  // packages/relay/src/gateway/instance-gateway.ts
8069
8171
  import {
8070
8172
  MSG,
@@ -10107,7 +10209,10 @@ function createApp(deps) {
10107
10209
  });
10108
10210
  app.delete("/api/instances/:id", (c) => {
10109
10211
  const account = c.get("account");
10110
- const removed = deps.instances.remove(c.req.param("id"), account.id);
10212
+ const instanceId = c.req.param("id");
10213
+ const removed = deps.instances.remove(instanceId, account.id);
10214
+ if (removed)
10215
+ deps.turnSlotAnchors?.deleteByInstance(instanceId);
10111
10216
  return removed ? c.json({ ok: true }) : c.json({ error: "not-found" }, 404);
10112
10217
  });
10113
10218
  app.get("/api/agent-directory", (c) => {
@@ -10286,6 +10391,7 @@ function createApp(deps) {
10286
10391
  }
10287
10392
  if (removeInput && !isErrorPayload2(result)) {
10288
10393
  deps.messages.deleteBySession(instance.id, removeInput.alias);
10394
+ deps.turnSlotAnchors?.deleteBySession(instance.id, removeInput.alias);
10289
10395
  }
10290
10396
  if (body.type === MSG3.commandExecute) {
10291
10397
  const p = payload;
@@ -10523,6 +10629,7 @@ async function createRelayRuntime(dbPath, options = {}) {
10523
10629
  }
10524
10630
  };
10525
10631
  const recoveryReceipts = new RecoveryReceiptStore(db);
10632
+ const slotAnchors = new TurnSlotAnchorStore(db);
10526
10633
  const sessionUsage = new Map;
10527
10634
  const listSessionUsage = (instanceId) => {
10528
10635
  const prefix = `${instanceId}\x00`;
@@ -10556,7 +10663,8 @@ async function createRelayRuntime(dbPath, options = {}) {
10556
10663
  sessionAlias: k.slice(prefix.length),
10557
10664
  parts: a.parts,
10558
10665
  status: a.text ? "streaming" : "working",
10559
- startedAt: a.startedAt
10666
+ startedAt: a.startedAt,
10667
+ ...typeof a.slotAfterId === "number" ? { slotAfterId: a.slotAfterId } : {}
10560
10668
  });
10561
10669
  }
10562
10670
  return out;
@@ -10668,6 +10776,48 @@ async function createRelayRuntime(dbPath, options = {}) {
10668
10776
  messages.append(instanceId, sessionAlias, "in", opts.prompt, opts.scheduled ? { scheduled: opts.scheduled } : undefined);
10669
10777
  }
10670
10778
  };
10779
+ const rememberAnchor = (sessionAlias, slot, recoveryId) => {
10780
+ slotAnchors.put({
10781
+ instanceId,
10782
+ sessionAlias,
10783
+ recoveryId: canonicalRecoveryId(sessionAlias, recoveryId),
10784
+ slotAfterId: slot.slotAfterId,
10785
+ ...slot.startedAt !== undefined ? { startedAt: slot.startedAt } : {},
10786
+ ...slot.startedAfterSeq !== undefined ? { startedAfterSeq: slot.startedAfterSeq } : {}
10787
+ });
10788
+ };
10789
+ const snapshotSlotAfterReconcile = (sessionAlias, extras = {}) => ({
10790
+ slotAfterId: messages.lastId(instanceId, sessionAlias) ?? 0,
10791
+ ...extras.startedAt !== undefined ? { startedAt: extras.startedAt } : {},
10792
+ ...extras.startedAfterSeq !== undefined ? { startedAfterSeq: extras.startedAfterSeq } : {}
10793
+ });
10794
+ const resolveFinishSlot = (sessionAlias, opts) => {
10795
+ const stored = slotAnchors.take(instanceId, sessionAlias, opts.recoveryId);
10796
+ if (stored) {
10797
+ return {
10798
+ slotAfterId: stored.slotAfterId,
10799
+ startedAt: stored.startedAt ?? opts.startedAt,
10800
+ startedAfterSeq: stored.startedAfterSeq ?? opts.startedAfterSeq
10801
+ };
10802
+ }
10803
+ return snapshotSlotAfterReconcile(sessionAlias, { startedAt: opts.startedAt, startedAfterSeq: opts.startedAfterSeq });
10804
+ };
10805
+ const restoreRunningSlot = (sessionAlias, turn) => {
10806
+ const stored = slotAnchors.get(instanceId, canonicalRecoveryId(sessionAlias, turn.recoveryId));
10807
+ if (stored) {
10808
+ return {
10809
+ slotAfterId: stored.slotAfterId,
10810
+ startedAt: stored.startedAt ?? turn.startedAt,
10811
+ startedAfterSeq: stored.startedAfterSeq ?? turn.startedAfterSeq
10812
+ };
10813
+ }
10814
+ const slot = snapshotSlotAfterReconcile(sessionAlias, { startedAt: turn.startedAt, startedAfterSeq: turn.startedAfterSeq });
10815
+ rememberAnchor(sessionAlias, slot, turn.recoveryId);
10816
+ return slot;
10817
+ };
10818
+ const appendAssistantOut = (sessionAlias, text, structured, slot) => {
10819
+ messages.append(instanceId, sessionAlias, "out", text, structured, undefined, undefined, undefined, slot.startedAt, slot.slotAfterId, slot.startedAfterSeq);
10820
+ };
10671
10821
  try {
10672
10822
  if (envelope.type === MSG4.instanceEvent) {
10673
10823
  const raw = envelope.payload?.event;
@@ -10682,7 +10832,7 @@ async function createRelayRuntime(dbPath, options = {}) {
10682
10832
  const e = raw;
10683
10833
  return e.type === "tool-event" ? { ...e, step: capToolStep(e.step) } : e;
10684
10834
  })();
10685
- webGateway.broadcast(accountId, { kind: "control-event", instanceId, event });
10835
+ let outbound = event;
10686
10836
  if (event.type === "turn-started") {
10687
10837
  const k = key(instanceId, event.sessionAlias);
10688
10838
  let notification;
@@ -10699,21 +10849,35 @@ async function createRelayRuntime(dbPath, options = {}) {
10699
10849
  }
10700
10850
  }
10701
10851
  try {
10702
- reconcileInboundPrompt(event.sessionAlias, { prompt: event.prompt, queueItemId: event.queueItemId, scheduled: event.scheduled, promptRequestId: event.promptRequestId }, true);
10852
+ let slot;
10853
+ db.transaction(() => {
10854
+ reconcileInboundPrompt(event.sessionAlias, { prompt: event.prompt, queueItemId: event.queueItemId, scheduled: event.scheduled, promptRequestId: event.promptRequestId }, true);
10855
+ slot = snapshotSlotAfterReconcile(event.sessionAlias, {
10856
+ startedAt: nowMs(),
10857
+ startedAfterSeq: event.startedAfterSeq
10858
+ });
10859
+ rememberAnchor(event.sessionAlias, slot, event.recoveryId);
10860
+ });
10861
+ const startedAt = slot.startedAt ?? nowMs();
10703
10862
  turnBuffers.set(k, {
10704
10863
  text: "",
10705
10864
  steps: new Map,
10706
10865
  reasoning: "",
10707
10866
  parts: [],
10708
- startedAt: nowMs(),
10867
+ startedAt,
10868
+ slotAfterId: slot.slotAfterId,
10869
+ ...slot.startedAfterSeq !== undefined ? { startedAfterSeq: slot.startedAfterSeq } : {},
10709
10870
  ...notification ? { notification } : {}
10710
10871
  });
10872
+ outbound = { ...event, slotAfterId: slot.slotAfterId };
10711
10873
  } catch (err) {
10712
10874
  turnBuffers.delete(k);
10713
10875
  gateway.disconnect(instanceId);
10714
10876
  throw err;
10715
10877
  }
10716
- } else if (event.type === "turn-output") {
10878
+ }
10879
+ webGateway.broadcast(accountId, { kind: "control-event", instanceId, event: outbound });
10880
+ if (event.type === "turn-output") {
10717
10881
  const a = turnBuffers.get(key(instanceId, event.sessionAlias));
10718
10882
  if (a) {
10719
10883
  a.text += event.chunk;
@@ -10738,10 +10902,19 @@ async function createRelayRuntime(dbPath, options = {}) {
10738
10902
  const promptRequestId = a?.notification?.promptRequestId;
10739
10903
  const flush = () => {
10740
10904
  if (!a) {
10905
+ if (event.silent && (event.text === undefined || event.text === "")) {
10906
+ slotAnchors.take(instanceId, event.sessionAlias, event.recoveryId);
10907
+ return;
10908
+ }
10909
+ const slot = resolveFinishSlot(event.sessionAlias, {
10910
+ recoveryId: event.recoveryId,
10911
+ startedAfterSeq: event.startedAfterSeq,
10912
+ startedAt: event.startedAt
10913
+ });
10741
10914
  if (event.text !== undefined) {
10742
- messages.append(instanceId, event.sessionAlias, "out", event.text);
10915
+ appendAssistantOut(event.sessionAlias, event.text, undefined, slot);
10743
10916
  } else if (!event.ok && event.errorMessage !== undefined) {
10744
- messages.append(instanceId, event.sessionAlias, "out", event.errorMessage);
10917
+ appendAssistantOut(event.sessionAlias, event.errorMessage, undefined, slot);
10745
10918
  } else {
10746
10919
  logger.warn("relay.event.turn_finished_without_content", "turn finished with no buffered content", {
10747
10920
  instanceId,
@@ -10754,9 +10927,20 @@ async function createRelayRuntime(dbPath, options = {}) {
10754
10927
  const hasReasoning = a.reasoning.trim().length > 0;
10755
10928
  const hasStructured = steps.length > 0 || hasReasoning;
10756
10929
  const text = a.text || (!event.ok && event.errorMessage !== undefined ? event.errorMessage : a.text);
10930
+ if (event.silent && !hasStructured && text === "") {
10931
+ slotAnchors.take(instanceId, event.sessionAlias, event.recoveryId);
10932
+ return;
10933
+ }
10757
10934
  if (hasStructured || text !== "" || event.ok) {
10758
10935
  const structured = hasStructured ? { toolSteps: steps, ...hasReasoning ? { reasoning: a.reasoning } : {}, ...a.parts.length ? { parts: a.parts } : {}, ...a.truncated ? { truncated: true } : {} } : a.truncated ? { truncated: true } : undefined;
10759
- messages.append(instanceId, event.sessionAlias, "out", text, structured);
10936
+ slotAnchors.take(instanceId, event.sessionAlias, event.recoveryId);
10937
+ appendAssistantOut(event.sessionAlias, text, structured, {
10938
+ slotAfterId: a.slotAfterId,
10939
+ startedAt: a.startedAt,
10940
+ startedAfterSeq: a.startedAfterSeq
10941
+ });
10942
+ } else {
10943
+ slotAnchors.take(instanceId, event.sessionAlias, event.recoveryId);
10760
10944
  }
10761
10945
  };
10762
10946
  const recoveryId = event.recoveryId;
@@ -10879,7 +11063,14 @@ async function createRelayRuntime(dbPath, options = {}) {
10879
11063
  }, false);
10880
11064
  }
10881
11065
  if (text !== undefined && !alreadyPersisted) {
10882
- messages.append(instanceId, finished.sessionAlias, "out", text, finished.truncated ? { truncated: true } : undefined);
11066
+ const slot = resolveFinishSlot(finished.sessionAlias, {
11067
+ recoveryId: finished.recoveryId,
11068
+ startedAfterSeq: finished.startedAfterSeq,
11069
+ startedAt: finished.startedAt
11070
+ });
11071
+ appendAssistantOut(finished.sessionAlias, text, finished.truncated ? { truncated: true } : undefined, slot);
11072
+ } else {
11073
+ slotAnchors.take(instanceId, finished.sessionAlias, finished.recoveryId);
10883
11074
  }
10884
11075
  };
10885
11076
  if (recoveryId) {
@@ -10963,6 +11154,17 @@ async function createRelayRuntime(dbPath, options = {}) {
10963
11154
  reasoning,
10964
11155
  parts,
10965
11156
  startedAt: turn.startedAt,
11157
+ ...(() => {
11158
+ const slot = restoreRunningSlot(turn.sessionAlias, {
11159
+ recoveryId: turn.recoveryId,
11160
+ startedAfterSeq: turn.startedAfterSeq,
11161
+ startedAt: turn.startedAt
11162
+ });
11163
+ return {
11164
+ slotAfterId: slot.slotAfterId,
11165
+ ...slot.startedAfterSeq !== undefined ? { startedAfterSeq: slot.startedAfterSeq } : {}
11166
+ };
11167
+ })(),
10966
11168
  ...turn.truncated ? { truncated: true } : {},
10967
11169
  ...notification ? { notification } : {}
10968
11170
  });
@@ -10973,6 +11175,12 @@ async function createRelayRuntime(dbPath, options = {}) {
10973
11175
  for (const entry of sync.commands) {
10974
11176
  sessionCommands.set(key(instanceId, entry.sessionAlias), entry.commands);
10975
11177
  }
11178
+ const keepAnchors = new Set;
11179
+ for (const turn of sync.turns)
11180
+ keepAnchors.add(canonicalRecoveryId(turn.sessionAlias, turn.recoveryId));
11181
+ for (const finished of sync.finishedOffline)
11182
+ keepAnchors.add(canonicalRecoveryId(finished.sessionAlias, finished.recoveryId));
11183
+ slotAnchors.retain(instanceId, keepAnchors);
10976
11184
  webGateway.broadcast(accountId, { kind: "state-snapshot", instanceId, ...stateSnapshot(instanceId) });
10977
11185
  } catch (err) {
10978
11186
  gateway.disconnect(instanceId);
@@ -11022,7 +11230,8 @@ async function createRelayRuntime(dbPath, options = {}) {
11022
11230
  },
11023
11231
  onWebPromptSessionCleared: (instanceId, sessionAlias) => {
11024
11232
  clearPendingForSession(instanceId, sessionAlias);
11025
- }
11233
+ },
11234
+ turnSlotAnchors: slotAnchors
11026
11235
  });
11027
11236
  const completionRouteSweepTimer = setInterval(() => gateway.sweepExpiredCompletionRoutes(), 60 * 60000);
11028
11237
  completionRouteSweepTimer.unref?.();
@@ -11032,6 +11241,7 @@ async function createRelayRuntime(dbPath, options = {}) {
11032
11241
  instances,
11033
11242
  messages,
11034
11243
  recoveryReceipts,
11244
+ slotAnchors,
11035
11245
  pushSubscriptions,
11036
11246
  pushNotifier,
11037
11247
  gateway,
@@ -3,6 +3,7 @@ import { type LiveTurnSnapshotDto, type PublishedAgentEndpointDto, type SessionC
3
3
  import type { AccountRow, AccountStore } from "../stores/accounts.js";
4
4
  import type { InstanceStore } from "../stores/instances.js";
5
5
  import type { MessageStore } from "../stores/messages.js";
6
+ import type { TurnSlotAnchorStore } from "../stores/turn-slot-anchors.js";
6
7
  import type { PushSubscriptionStore } from "../stores/push-subscriptions.js";
7
8
  import type { RelayLogger } from "../logging.js";
8
9
  import { type UpdateCheck } from "../version.js";
@@ -17,6 +18,8 @@ export interface AppDeps {
17
18
  instances: InstanceStore;
18
19
  gateway: GatewayForApp;
19
20
  messages: MessageStore;
21
+ /** Live-slot anchors; cleared when a session or instance is deleted. */
22
+ turnSlotAnchors?: TurnSlotAnchorStore;
20
23
  /** Snapshot the in-flight turns for an instance (for the active-turns endpoint). */
21
24
  activeTurns?: (instanceId: string) => LiveTurnSnapshotDto[];
22
25
  /** Snapshot the latest per-session context-usage for an instance (for the active-turns endpoint). */
@@ -6,5 +6,7 @@ export declare const COMPACT_DETAIL_PREVIEW = 240;
6
6
  * `parts` already carries them, and strip bulky tool bodies (diffs, command output,
7
7
  * file previews). Collapsed cards keep titles/status plus a short subagent snippet.
8
8
  * The full row is available from `GET .../messages/:id`.
9
+ * Top-level fields such as `startedAt`, `slotAfterId`, and `startedAfterSeq` are
10
+ * copied through unchanged — compact must not drop the durable slot anchor.
9
11
  */
10
12
  export declare function compactHistoryMessage(row: MessageRecordDto): MessageRecordDto;