@moxt-ai/mobius 0.0.147 → 0.0.150

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 (41) hide show
  1. package/dist/cli.js +731 -197
  2. package/dist/cli.js.map +4 -4
  3. package/dist/daemon-active-turn.d.ts +15 -2
  4. package/dist/daemon-active-turn.d.ts.map +1 -1
  5. package/dist/daemon-active-turn.js +63 -25
  6. package/dist/daemon-active-turn.js.map +1 -1
  7. package/dist/daemon-command-handler.js +3 -3
  8. package/dist/daemon-history-commands.d.ts +4 -7
  9. package/dist/daemon-history-commands.d.ts.map +1 -1
  10. package/dist/daemon-history-commands.js.map +1 -1
  11. package/dist/daemon-prompt-executor.d.ts +1 -1
  12. package/dist/daemon-prompt-executor.d.ts.map +1 -1
  13. package/dist/daemon-prompt-executor.js +1 -3
  14. package/dist/daemon-prompt-executor.js.map +1 -1
  15. package/dist/daemon-turn-registry.d.ts +18 -56
  16. package/dist/daemon-turn-registry.d.ts.map +1 -1
  17. package/dist/daemon-turn-registry.js +40 -1
  18. package/dist/daemon-turn-registry.js.map +1 -1
  19. package/dist/index.js +730 -196
  20. package/dist/index.js.map +4 -4
  21. package/dist/native-sessions/live-history-page.d.ts +4 -7
  22. package/dist/native-sessions/live-history-page.d.ts.map +1 -1
  23. package/dist/native-sessions/live-history-page.js +163 -107
  24. package/dist/native-sessions/live-history-page.js.map +1 -1
  25. package/dist/relay-connection/run-relay-connection.d.ts.map +1 -1
  26. package/dist/relay-connection/run-relay-connection.js +15 -2
  27. package/dist/relay-connection/run-relay-connection.js.map +1 -1
  28. package/dist/relay-connection/session-event-outbox.d.ts +19 -0
  29. package/dist/relay-connection/session-event-outbox.d.ts.map +1 -0
  30. package/dist/relay-connection/session-event-outbox.js +228 -0
  31. package/dist/relay-connection/session-event-outbox.js.map +1 -0
  32. package/dist/relay-connection/session-event-publisher.d.ts +10 -4
  33. package/dist/relay-connection/session-event-publisher.d.ts.map +1 -1
  34. package/dist/relay-connection/session-event-publisher.js +140 -32
  35. package/dist/relay-connection/session-event-publisher.js.map +1 -1
  36. package/dist/run-daemon.d.ts.map +1 -1
  37. package/dist/run-daemon.js +3 -1
  38. package/dist/run-daemon.js.map +1 -1
  39. package/dist/runtime.js +736 -202
  40. package/dist/runtime.js.map +4 -4
  41. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -3381,7 +3381,7 @@ var program = new Command();
3381
3381
  // package.json
3382
3382
  var package_default = {
3383
3383
  name: "@moxt-ai/mobius",
3384
- version: "0.0.147",
3384
+ version: "0.0.150",
3385
3385
  description: "Mobius CLI for pairing and running local AI agent services.",
3386
3386
  type: "module",
3387
3387
  bin: {
@@ -29330,9 +29330,17 @@ function decodeSessionQueuedEvent(encoded) {
29330
29330
  }
29331
29331
  function decodeRuntimeLivenessReadyEvent(encoded) {
29332
29332
  const value = parseJson2(encoded);
29333
- assertExactKeys(value, ["kind"]);
29334
29333
  assertKind(value, RUNTIME_LIVENESS_READY_KIND);
29335
- return { kind: RUNTIME_LIVENESS_READY_KIND };
29334
+ if (value["sessionEventDelivery"] === void 0) {
29335
+ assertExactKeys(value, ["kind"]);
29336
+ return { kind: RUNTIME_LIVENESS_READY_KIND };
29337
+ }
29338
+ assertExactKeys(value, ["kind", "sessionEventDelivery"]);
29339
+ const sessionEventDelivery = readString2(value, "sessionEventDelivery", 32);
29340
+ if (sessionEventDelivery !== "reliable-v1") {
29341
+ throw new ProtocolMessageError("Unsupported Session event delivery mode");
29342
+ }
29343
+ return { kind: RUNTIME_LIVENESS_READY_KIND, sessionEventDelivery };
29336
29344
  }
29337
29345
  function decodeRuntimeHeartbeatEvent(encoded) {
29338
29346
  const value = parseJson2(encoded);
@@ -29624,18 +29632,21 @@ var activeTurnMessageSchema = external_exports.discriminatedUnion("role", [
29624
29632
  activeTurnAssistantMessageSchema
29625
29633
  ]);
29626
29634
  var activeTurnSnapshotSchema = external_exports.strictObject({
29627
- activities: external_exports.array(activeTurnActivitySchema).readonly(),
29628
29635
  commandId: identifierStringSchema,
29629
- content: external_exports.array(displayContentSchema).readonly(),
29630
- prompt: external_exports.string().max(1e5),
29631
- text: external_exports.string(),
29632
- truncated: external_exports.boolean(),
29633
29636
  epoch: identifierStringSchema,
29637
+ hasOlder: external_exports.boolean(),
29634
29638
  interaction: external_exports.discriminatedUnion("kind", [sessionPermissionRequestedEventSchema, sessionElicitationRequestedEventSchema]).nullable(),
29635
29639
  messages: external_exports.array(activeTurnMessageSchema).max(SESSION_HISTORY_MESSAGE_LIMIT).readonly(),
29636
29640
  sequence: external_exports.number().int().nonnegative(),
29637
- state: external_exports.literal("running")
29638
- }).readonly();
29641
+ state: external_exports.literal("running"),
29642
+ truncated: external_exports.boolean()
29643
+ }).readonly().refine(
29644
+ (snapshot) => snapshot.messages.reduce(
29645
+ (total, message) => total + message.text.length + (message.role === "assistant" ? JSON.stringify(message.activities).length + JSON.stringify(message.content).length : 0),
29646
+ 0
29647
+ ) <= SESSION_HISTORY_CHARACTER_LIMIT,
29648
+ "Active Turn snapshot content is too large"
29649
+ );
29639
29650
  var identity = {
29640
29651
  ...envelope,
29641
29652
  kind: external_exports.literal(NATIVE_SESSION_REQUEST_KIND),
@@ -29779,27 +29790,61 @@ var encodeNativeSessionCancel = (value) => JSON.stringify(nativeSessionCancelSch
29779
29790
 
29780
29791
  // ../protocol/src/session-events.ts
29781
29792
  var SESSION_EVENT_KIND = "session.event";
29782
- var SESSION_EVENT_COMMITTED_KIND = "session.event.committed";
29793
+ var SESSION_EVENT_ACK_KIND = "session.event.ack";
29794
+ var SESSION_EVENT_DEFERRED_KIND = "session.event.deferred";
29795
+ var SESSION_EVENT_REJECTED_KIND = "session.event.rejected";
29796
+ var SessionEventRejectionCode = {
29797
+ IdempotencyConflict: "idempotency_conflict",
29798
+ InvalidEvent: "invalid_event",
29799
+ RecordingRejected: "recording_rejected"
29800
+ };
29783
29801
  var MAX_RECORDED_EVENT_LENGTH = 8 * 1024 * 1024;
29784
- var eventEnvelopeSchema = external_exports.strictObject({
29802
+ var ephemeralEventEnvelopeSchema = external_exports.strictObject({
29785
29803
  kind: external_exports.literal(SESSION_EVENT_KIND),
29786
29804
  payload: external_exports.string().min(2).max(MAX_RECORDED_EVENT_LENGTH),
29787
29805
  sessionId: identifierStringSchema
29788
29806
  }).readonly();
29789
- var committedEventSchema = external_exports.strictObject({
29790
- kind: external_exports.literal(SESSION_EVENT_COMMITTED_KIND),
29807
+ var reliableDeliverySchema = external_exports.strictObject({
29808
+ eventId: identifierStringSchema,
29809
+ kind: external_exports.literal("reliable"),
29810
+ sourceSeq: external_exports.string().regex(/^(?:0|[1-9][0-9]*)$/u).max(40),
29811
+ streamId: identifierStringSchema
29812
+ }).readonly();
29813
+ var reliableEventEnvelopeSchema = external_exports.strictObject({
29814
+ delivery: reliableDeliverySchema,
29815
+ kind: external_exports.literal(SESSION_EVENT_KIND),
29791
29816
  payload: external_exports.string().min(2).max(MAX_RECORDED_EVENT_LENGTH),
29792
29817
  sessionId: identifierStringSchema
29793
29818
  }).readonly();
29819
+ var eventEnvelopeSchema = external_exports.union([ephemeralEventEnvelopeSchema, reliableEventEnvelopeSchema]).readonly();
29820
+ var acknowledgementOutcomeSchema = external_exports.discriminatedUnion("kind", [
29821
+ external_exports.strictObject({ kind: external_exports.literal("relayed") }).readonly(),
29822
+ external_exports.strictObject({
29823
+ kind: external_exports.literal("recorded"),
29824
+ providerId: identifierStringSchema,
29825
+ recordingEpoch: identifierStringSchema
29826
+ }).readonly()
29827
+ ]);
29828
+ var eventAcknowledgementSchema = external_exports.strictObject({
29829
+ eventId: identifierStringSchema,
29830
+ kind: external_exports.literal(SESSION_EVENT_ACK_KIND),
29831
+ outcome: acknowledgementOutcomeSchema,
29832
+ sessionId: identifierStringSchema
29833
+ }).readonly();
29834
+ var eventDeferredSchema = external_exports.strictObject({
29835
+ eventId: identifierStringSchema,
29836
+ kind: external_exports.literal(SESSION_EVENT_DEFERRED_KIND),
29837
+ retryAfterMilliseconds: external_exports.number().int().min(100).max(6e4),
29838
+ sessionId: identifierStringSchema
29839
+ }).readonly();
29840
+ var eventRejectedSchema = external_exports.strictObject({
29841
+ code: external_exports.enum(SessionEventRejectionCode),
29842
+ eventId: identifierStringSchema,
29843
+ kind: external_exports.literal(SESSION_EVENT_REJECTED_KIND),
29844
+ sessionId: identifierStringSchema
29845
+ }).readonly();
29794
29846
  var SESSION_QUEUE_CHANGED_KIND = "session.queue.changed";
29795
29847
  var sessionQueueChangedSchema = external_exports.strictObject({ kind: external_exports.literal(SESSION_QUEUE_CHANGED_KIND), sessionId: identifierStringSchema }).readonly();
29796
- var SESSION_TURN_UPDATED_KIND = "session.turn.updated";
29797
- var sessionTurnUpdatedSchema = external_exports.strictObject({
29798
- kind: external_exports.literal(SESSION_TURN_UPDATED_KIND),
29799
- sessionId: identifierStringSchema,
29800
- turn: activeTurnSnapshotSchema
29801
- }).readonly();
29802
- var decodeSessionTurnUpdatedEvent = (encoded) => decode5(encoded, sessionTurnUpdatedSchema);
29803
29848
  function decode5(encoded, schema) {
29804
29849
  let value;
29805
29850
  try {
@@ -29826,11 +29871,15 @@ var encode4 = (message, schema) => {
29826
29871
  };
29827
29872
  var decodeSessionEventEnvelope = (encoded) => decode5(encoded, eventEnvelopeSchema);
29828
29873
  var encodeSessionEventEnvelope = (message) => encode4(message, eventEnvelopeSchema);
29874
+ var decodeSessionEventAcknowledgement = (encoded) => decode5(encoded, eventAcknowledgementSchema);
29875
+ var encodeSessionEventAcknowledgement = (message) => encode4(message, eventAcknowledgementSchema);
29876
+ var decodeSessionEventDeferred = (encoded) => decode5(encoded, eventDeferredSchema);
29877
+ var encodeSessionEventDeferred = (message) => encode4(message, eventDeferredSchema);
29878
+ var decodeSessionEventRejected = (encoded) => decode5(encoded, eventRejectedSchema);
29879
+ var encodeSessionEventRejected = (message) => encode4(message, eventRejectedSchema);
29829
29880
  function decodeRecordedSessionEvent(encoded) {
29830
29881
  const kind = decodeMessageKind(encoded);
29831
29882
  switch (kind) {
29832
- case SESSION_TURN_UPDATED_KIND:
29833
- return decodeSessionTurnUpdatedEvent(encoded);
29834
29883
  case SESSION_QUEUE_CHANGED_KIND:
29835
29884
  return decode5(encoded, sessionQueueChangedSchema);
29836
29885
  case AGENT_ACTIVITY_KIND:
@@ -29999,6 +30048,21 @@ var serviceToDaemonSignatures = {
29999
30048
  encodeSessionElicitationRespondCommand,
30000
30049
  decodeSessionElicitationRespondCommand
30001
30050
  ),
30051
+ [SESSION_EVENT_ACK_KIND]: defineMessageSignature(
30052
+ SESSION_EVENT_ACK_KIND,
30053
+ encodeSessionEventAcknowledgement,
30054
+ decodeSessionEventAcknowledgement
30055
+ ),
30056
+ [SESSION_EVENT_DEFERRED_KIND]: defineMessageSignature(
30057
+ SESSION_EVENT_DEFERRED_KIND,
30058
+ encodeSessionEventDeferred,
30059
+ decodeSessionEventDeferred
30060
+ ),
30061
+ [SESSION_EVENT_REJECTED_KIND]: defineMessageSignature(
30062
+ SESSION_EVENT_REJECTED_KIND,
30063
+ encodeSessionEventRejected,
30064
+ decodeSessionEventRejected
30065
+ ),
30002
30066
  [SESSION_LIVE_HISTORY_LOAD_KIND]: defineMessageSignature(
30003
30067
  SESSION_LIVE_HISTORY_LOAD_KIND,
30004
30068
  encodeSessionLiveHistoryLoad,
@@ -31057,7 +31121,7 @@ var AuditStore = class {
31057
31121
  };
31058
31122
 
31059
31123
  // src/run-daemon.ts
31060
- import { createHash as createHash6, randomUUID as randomUUID7 } from "node:crypto";
31124
+ import { createHash as createHash6, randomUUID as randomUUID8 } from "node:crypto";
31061
31125
  import { join as join18 } from "node:path";
31062
31126
  import { styleText } from "node:util";
31063
31127
 
@@ -42207,6 +42271,28 @@ var DaemonInstallationObserver = class {
42207
42271
 
42208
42272
  // src/daemon-active-turn.ts
42209
42273
  import { randomUUID as randomUUID2 } from "node:crypto";
42274
+ function activeMessageCharacters(message) {
42275
+ return message.text.length + (message.role === "assistant" ? JSON.stringify(message.activities).length + JSON.stringify(message.content).length : 0);
42276
+ }
42277
+ var ACTIVE_TURN_TEXT_CHUNK_LIMIT = Math.floor(SESSION_HISTORY_CHARACTER_LIMIT / 2);
42278
+ function boundedRecoveryMessages(messages) {
42279
+ const selected = [];
42280
+ let remainingCharacters = SESSION_HISTORY_CHARACTER_LIMIT;
42281
+ for (let index = messages.length - 1; index >= 0 && selected.length < SESSION_HISTORY_MESSAGE_LIMIT; index -= 1) {
42282
+ const message = messages[index];
42283
+ if (message === void 0) {
42284
+ break;
42285
+ }
42286
+ const characters = activeMessageCharacters(message);
42287
+ if (characters <= remainingCharacters) {
42288
+ selected.unshift(message);
42289
+ remainingCharacters -= characters;
42290
+ continue;
42291
+ }
42292
+ break;
42293
+ }
42294
+ return { hasOlder: selected.length !== messages.length, messages: selected };
42295
+ }
42210
42296
  var ActiveDaemonTurn = class {
42211
42297
  attemptId;
42212
42298
  command;
@@ -42214,9 +42300,7 @@ var ActiveDaemonTurn = class {
42214
42300
  #finished = Promise.withResolvers();
42215
42301
  #cancellationRequested = false;
42216
42302
  #dispatch;
42217
- #text = "";
42218
42303
  #activities = /* @__PURE__ */ new Map();
42219
- #content = /* @__PURE__ */ new Map();
42220
42304
  #timeline;
42221
42305
  #sequence = 0;
42222
42306
  #interaction = null;
@@ -42263,9 +42347,17 @@ var ActiveDaemonTurn = class {
42263
42347
  effectiveDisposition = () => this.#dispatch.kind === "promoted" ? SessionPromptDisposition.Steer : this.command.disposition;
42264
42348
  recordEvent = (event) => {
42265
42349
  if (event.kind === AGENT_MESSAGE_DELTA_KIND) {
42266
- this.#text += event.text;
42350
+ const sequence = this.advance();
42267
42351
  this.appendMessageText(event.messageId, event.text);
42268
- return this.updated();
42352
+ return {
42353
+ commandId: this.command.commandId,
42354
+ epoch: this.#epoch,
42355
+ kind: AGENT_MESSAGE_DELTA_KIND,
42356
+ messageId: event.messageId,
42357
+ sequence,
42358
+ sessionId: this.command.sessionId,
42359
+ text: event.text
42360
+ };
42269
42361
  }
42270
42362
  if (event.sessionId !== this.command.sessionId || "commandId" in event && event.commandId !== this.command.commandId) {
42271
42363
  return event;
@@ -42312,7 +42404,6 @@ var ActiveDaemonTurn = class {
42312
42404
  toolKind: previous?.toolKind ?? ""
42313
42405
  });
42314
42406
  } else if (event.kind === AGENT_CONTENT_KIND) {
42315
- this.#content.set(event.contentId, event.content);
42316
42407
  const target = this.#timeline.findLastIndex(
42317
42408
  (entry) => entry.kind === "message" && entry.sourceMessageId === event.messageId
42318
42409
  );
@@ -42338,7 +42429,7 @@ var ActiveDaemonTurn = class {
42338
42429
  this.#interaction = null;
42339
42430
  }
42340
42431
  if (event.kind === AGENT_ACTIVITY_KIND || event.kind === AGENT_ACTIVITY_NARRATION_KIND || event.kind === AGENT_CONTENT_KIND) {
42341
- return this.updated();
42432
+ this.advance();
42342
42433
  }
42343
42434
  return event;
42344
42435
  };
@@ -42347,7 +42438,7 @@ var ActiveDaemonTurn = class {
42347
42438
  while (remaining.length > 0) {
42348
42439
  const latest = this.#timeline.at(-1);
42349
42440
  if (latest?.kind === "message" && latest.sourceMessageId === messageId) {
42350
- const available = SESSION_HISTORY_CHARACTER_LIMIT - latest.text.length;
42441
+ const available = ACTIVE_TURN_TEXT_CHUNK_LIMIT - latest.text.length;
42351
42442
  if (available > 0) {
42352
42443
  this.#timeline = [
42353
42444
  ...this.#timeline.slice(0, -1),
@@ -42364,21 +42455,22 @@ var ActiveDaemonTurn = class {
42364
42455
  id: decodeIdentifier(randomUUID2()),
42365
42456
  kind: "message",
42366
42457
  sourceMessageId: messageId,
42367
- text: remaining.slice(0, SESSION_HISTORY_CHARACTER_LIMIT)
42458
+ text: remaining.slice(0, ACTIVE_TURN_TEXT_CHUNK_LIMIT)
42368
42459
  }
42369
42460
  ];
42370
- remaining = remaining.slice(SESSION_HISTORY_CHARACTER_LIMIT);
42461
+ remaining = remaining.slice(ACTIVE_TURN_TEXT_CHUNK_LIMIT);
42371
42462
  }
42372
42463
  }
42373
42464
  recordSteeringPrompt = (command) => {
42374
42465
  if (this.#dispatch.kind !== "running" || command.sessionId !== this.command.sessionId || command.targetCommandId !== this.command.commandId) {
42375
- return null;
42466
+ return false;
42376
42467
  }
42377
42468
  this.#timeline = [
42378
42469
  ...this.#timeline,
42379
42470
  { id: decodeIdentifier(command.commandId), images: command.images, kind: "user", text: command.prompt }
42380
42471
  ];
42381
- return this.updated();
42472
+ this.advance();
42473
+ return true;
42382
42474
  };
42383
42475
  timelineMessages() {
42384
42476
  return this.#timeline.flatMap((entry) => {
@@ -42400,32 +42492,39 @@ var ActiveDaemonTurn = class {
42400
42492
  return activity2 === void 0 ? [] : [{ activities: [activity2], content: [], id: entry.id, role: "assistant", text: "" }];
42401
42493
  });
42402
42494
  }
42403
- updated = () => {
42404
- this.#sequence += 1;
42405
- const turn = this.snapshot();
42406
- if (turn === null) {
42407
- throw new Error("Only a running turn can publish a snapshot");
42495
+ advance = () => {
42496
+ if (this.#dispatch.kind !== "running") {
42497
+ throw new Error("Only a running turn can advance its snapshot");
42408
42498
  }
42409
- return { kind: "session.turn.updated", sessionId: this.command.sessionId, turn };
42499
+ this.#sequence += 1;
42500
+ return this.#sequence;
42410
42501
  };
42411
42502
  snapshot = () => {
42412
42503
  if (this.#dispatch.kind !== "running") {
42413
42504
  return null;
42414
42505
  }
42415
- const completeMessages = this.timelineMessages();
42416
- const messages = completeMessages.slice(-SESSION_HISTORY_MESSAGE_LIMIT);
42506
+ const bounded2 = boundedRecoveryMessages(this.timelineMessages());
42417
42507
  return {
42418
- activities: [...this.#activities.values()].sort((left, right) => left.sequence - right.sequence),
42419
42508
  commandId: decodeIdentifier(this.command.commandId),
42420
- content: [...this.#content.values()],
42421
- prompt: this.command.prompt,
42422
- text: this.#text,
42423
- truncated: messages.length !== completeMessages.length,
42424
42509
  epoch: this.#epoch,
42510
+ hasOlder: bounded2.hasOlder,
42425
42511
  interaction: this.#interaction,
42426
- messages,
42512
+ messages: bounded2.messages,
42427
42513
  sequence: this.#sequence,
42428
- state: "running"
42514
+ state: "running",
42515
+ truncated: false
42516
+ };
42517
+ };
42518
+ recovery = () => {
42519
+ if (this.#dispatch.kind !== "running") {
42520
+ return null;
42521
+ }
42522
+ return {
42523
+ commandId: decodeIdentifier(this.command.commandId),
42524
+ epoch: this.#epoch,
42525
+ messages: this.timelineMessages(),
42526
+ state: "running",
42527
+ truncated: false
42429
42528
  };
42430
42529
  };
42431
42530
  matches = (cancellation) => cancellation.commandId === this.command.commandId && cancellation.sessionId === this.command.sessionId;
@@ -42451,6 +42550,7 @@ var ActiveDaemonTurn = class {
42451
42550
  };
42452
42551
 
42453
42552
  // src/daemon-turn-registry.ts
42553
+ var COMPLETED_TURN_RECOVERY_LIMIT = 32;
42454
42554
  var waitForTurnCompletion = (turn, waitSignal) => {
42455
42555
  waitSignal.throwIfAborted();
42456
42556
  return new Promise((resolve9, reject) => {
@@ -42477,6 +42577,7 @@ var waitForTurnCompletion = (turn, waitSignal) => {
42477
42577
  };
42478
42578
  var DaemonTurnRegistry = class {
42479
42579
  #turns = /* @__PURE__ */ new Map();
42580
+ #completed = /* @__PURE__ */ new Map();
42480
42581
  #operations;
42481
42582
  constructor(operations) {
42482
42583
  this.#operations = operations;
@@ -42488,7 +42589,22 @@ var DaemonTurnRegistry = class {
42488
42589
  };
42489
42590
  get = (commandId) => this.#turns.get(commandId);
42490
42591
  finish = (turn) => {
42592
+ const recovery = turn.recovery();
42491
42593
  this.#turns.delete(turn.command.commandId);
42594
+ if (recovery !== null) {
42595
+ this.#completed.delete(turn.command.sessionId);
42596
+ this.#completed.set(turn.command.sessionId, {
42597
+ command: turn.command,
42598
+ recovery: { ...recovery, state: "completed" }
42599
+ });
42600
+ while (this.#completed.size > COMPLETED_TURN_RECOVERY_LIMIT) {
42601
+ const oldestSessionId = this.#completed.keys().next().value;
42602
+ if (oldestSessionId === void 0) {
42603
+ break;
42604
+ }
42605
+ this.#completed.delete(oldestSessionId);
42606
+ }
42607
+ }
42492
42608
  turn.finish();
42493
42609
  };
42494
42610
  recordEvent = (event) => {
@@ -42509,7 +42625,7 @@ var DaemonTurnRegistry = class {
42509
42625
  };
42510
42626
  recordSteeringPrompt = (command) => {
42511
42627
  const target = this.#turns.get(command.targetCommandId);
42512
- return target?.recordSteeringPrompt(command) ?? null;
42628
+ return target?.recordSteeringPrompt(command) ?? false;
42513
42629
  };
42514
42630
  executionSnapshot = async (matches) => {
42515
42631
  const matching = [];
@@ -42540,12 +42656,30 @@ var DaemonTurnRegistry = class {
42540
42656
  }
42541
42657
  return null;
42542
42658
  };
42659
+ recovery = (matches) => {
42660
+ for (const turn of this.#turns.values()) {
42661
+ if (!matches(turn.command)) {
42662
+ continue;
42663
+ }
42664
+ const recovery = turn.recovery();
42665
+ if (recovery !== null) {
42666
+ return recovery;
42667
+ }
42668
+ }
42669
+ for (const completed of this.#completed.values()) {
42670
+ if (matches(completed.command)) {
42671
+ return completed.recovery;
42672
+ }
42673
+ }
42674
+ return null;
42675
+ };
42543
42676
  close = async () => {
42544
42677
  const turns = [...this.#turns.values()];
42545
42678
  for (const turn of turns) {
42546
42679
  turn.abortDaemon();
42547
42680
  }
42548
42681
  await Promise.all(turns.map((turn) => turn.completion));
42682
+ this.#completed.clear();
42549
42683
  };
42550
42684
  admitAndWait = async (turn, waitSignal, onQueued) => {
42551
42685
  const command = turn.command;
@@ -44853,10 +44987,10 @@ async function resolveSessionDirectories(directories, bindingId, directoryId2, a
44853
44987
  }
44854
44988
 
44855
44989
  // src/native-sessions/live-history-page.ts
44856
- function liveHistoryCursor(sessionId, endIndex) {
44857
- return Buffer.from(JSON.stringify({ endIndex, sessionId }), "utf8").toString("base64url");
44990
+ function encodeLiveHistoryCursor(cursor) {
44991
+ return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
44858
44992
  }
44859
- function liveHistoryEndIndex(cursor, sessionId) {
44993
+ function decodeLiveHistoryCursor(cursor, sessionId) {
44860
44994
  try {
44861
44995
  if (!/^[A-Za-z0-9_-]{1,512}$/u.test(cursor)) {
44862
44996
  throw new Error("Invalid cursor alphabet");
@@ -44866,14 +45000,32 @@ function liveHistoryEndIndex(cursor, sessionId) {
44866
45000
  throw new Error("Invalid cursor shape");
44867
45001
  }
44868
45002
  const record2 = Object.fromEntries(Object.entries(value));
44869
- if (Object.keys(record2).sort().join(",") !== "endIndex,sessionId" || record2["sessionId"] !== sessionId || typeof record2["endIndex"] !== "number" || !Number.isSafeInteger(record2["endIndex"]) || record2["endIndex"] < 0) {
45003
+ const keys = Object.keys(record2).sort().join(",");
45004
+ const legacyNative = keys === "endIndex,sessionId";
45005
+ const kind = legacyNative ? "native" : record2["kind"];
45006
+ if (keys !== "endIndex,kind,sessionId" && keys !== "endIndex,epoch,kind,sessionId" && !legacyNative || record2["sessionId"] !== sessionId || kind !== "active" && kind !== "native" || typeof record2["endIndex"] !== "number" || !Number.isSafeInteger(record2["endIndex"]) || record2["endIndex"] < 0) {
44870
45007
  throw new Error("Invalid cursor target");
44871
45008
  }
44872
- return record2["endIndex"];
45009
+ if (kind === "active") {
45010
+ if (keys !== "endIndex,epoch,kind,sessionId" || typeof record2["epoch"] !== "string") {
45011
+ throw new Error("Invalid active cursor target");
45012
+ }
45013
+ return { endIndex: record2["endIndex"], epoch: record2["epoch"], kind, sessionId };
45014
+ }
45015
+ if (keys !== "endIndex,kind,sessionId" && !legacyNative) {
45016
+ throw new Error("Invalid native cursor target");
45017
+ }
45018
+ return { endIndex: record2["endIndex"], kind, sessionId };
44873
45019
  } catch (cause) {
44874
45020
  throw new AcpExecutionError("The native Session history cursor is invalid", { cause }, "invalid_cursor");
44875
45021
  }
44876
45022
  }
45023
+ function activeHistoryCursor(sessionId, epoch, endIndex) {
45024
+ return encodeLiveHistoryCursor({ endIndex, epoch, kind: "active", sessionId });
45025
+ }
45026
+ function nativeHistoryCursor(sessionId, endIndex) {
45027
+ return encodeLiveHistoryCursor({ endIndex, kind: "native", sessionId });
45028
+ }
44877
45029
  function liveHistoryCoveredTurnRanges(history) {
44878
45030
  const ranges = [];
44879
45031
  let historyIndex = history.messages.length;
@@ -44894,139 +45046,180 @@ function liveHistoryCoveredTurnRanges(history) {
44894
45046
  function historyMessageCharacters(message) {
44895
45047
  return message.text.length + (message.role === "assistant" ? JSON.stringify(message.activities).length + JSON.stringify(message.content).length : 0);
44896
45048
  }
44897
- function boundedActiveMessages(messages, messageLimit, characterLimit) {
45049
+ function isNativeHistoryImageMimeType(mimeType) {
45050
+ return mimeType === "image/png" || mimeType === "image/jpeg" || mimeType === "image/gif" || mimeType === "image/webp";
45051
+ }
45052
+ function selectMessagesBefore(messages, endIndex, messageLimit, characterLimit) {
44898
45053
  const selected = [];
44899
45054
  let remainingCharacters = characterLimit;
44900
- for (let index = messages.length - 1; index >= 0 && selected.length < messageLimit; index -= 1) {
45055
+ let startIndex = endIndex;
45056
+ for (let index = endIndex - 1; index >= 0 && selected.length < messageLimit; index -= 1) {
44901
45057
  const message = messages[index];
44902
45058
  if (message === void 0) {
44903
45059
  break;
44904
45060
  }
44905
45061
  const characters = historyMessageCharacters(message);
44906
- if (characters <= remainingCharacters) {
44907
- selected.unshift(message);
44908
- remainingCharacters -= characters;
44909
- continue;
44910
- }
44911
- const structuredCharacters = characters - message.text.length;
44912
- if (selected.length === 0 && structuredCharacters <= remainingCharacters) {
44913
- const textCharacters = remainingCharacters - structuredCharacters;
44914
- selected.unshift({ ...message, text: textCharacters === 0 ? "" : message.text.slice(-textCharacters) });
44915
- remainingCharacters = 0;
45062
+ if (characters > remainingCharacters) {
45063
+ break;
44916
45064
  }
44917
- break;
45065
+ selected.unshift(message);
45066
+ remainingCharacters -= characters;
45067
+ startIndex = index;
44918
45068
  }
44919
- return {
44920
- characters: characterLimit - remainingCharacters,
44921
- messages: selected,
44922
- truncated: selected.length !== messages.length
44923
- };
45069
+ if (selected.length === 0 && endIndex > 0) {
45070
+ throw new AcpExecutionError(
45071
+ "A native Session history item exceeds the page budget",
45072
+ {},
45073
+ "history_item_too_large"
45074
+ );
45075
+ }
45076
+ return { characters: characterLimit - remainingCharacters, messages: selected, startIndex };
44924
45077
  }
44925
45078
  function nativeActiveMessage(message) {
44926
- return message.role === "user" ? { role: "user", text: message.text } : {
44927
- activities: message.activities,
44928
- content: message.content,
44929
- role: "assistant",
44930
- text: message.text
44931
- };
45079
+ if (message.role === "assistant") {
45080
+ return {
45081
+ activities: message.activities,
45082
+ content: message.content,
45083
+ role: "assistant",
45084
+ text: message.text
45085
+ };
45086
+ }
45087
+ const images = (message.images ?? []).flatMap((image) => {
45088
+ if (image.data.length < 4 || image.data.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/u.test(image.data) || !isNativeHistoryImageMimeType(image.mimeType)) {
45089
+ return [];
45090
+ }
45091
+ return [{ data: image.data, mimeType: image.mimeType, name: image.name }];
45092
+ });
45093
+ return { ...images.length === 0 ? {} : { images }, role: "user", text: message.text };
45094
+ }
45095
+ function nativeHistoryCoversCompletedRecovery(history, recovery) {
45096
+ const root = recovery.messages.find((message) => message.role === "user" && message.id === recovery.commandId);
45097
+ if (root === void 0) {
45098
+ return false;
45099
+ }
45100
+ const rootIndex = history.messages.findLastIndex(
45101
+ (message) => message.role === "user" && message.text === root.text
45102
+ );
45103
+ if (rootIndex < 0) {
45104
+ return false;
45105
+ }
45106
+ const nativeAssistant = history.messages.slice(rootIndex + 1).filter((message) => message.role === "assistant");
45107
+ if (nativeAssistant.length === 0) {
45108
+ return false;
45109
+ }
45110
+ const recoveryAssistant = recovery.messages.filter((message) => message.role === "assistant");
45111
+ const recoveryText = recoveryAssistant.map((message) => message.text).join("");
45112
+ const nativeText = nativeAssistant.map((message) => message.text).join("");
45113
+ if (recoveryText.length > 0 && !nativeText.includes(recoveryText)) {
45114
+ return false;
45115
+ }
45116
+ const nativeContent = new Set(
45117
+ nativeAssistant.flatMap((message) => message.content).map((content) => JSON.stringify(content))
45118
+ );
45119
+ return recoveryAssistant.flatMap((message) => message.content).every((content) => nativeContent.has(JSON.stringify(content)));
44932
45120
  }
44933
- function paginateAcpSessionHistory(history, sessionId, cursor, limit) {
45121
+ function historyBeforeActiveTurn(history, activeTurn) {
44934
45122
  if (history.status === "unavailable") {
44935
- if (cursor !== null) {
44936
- throw new AcpExecutionError("Unavailable native Session history has no cursor", {}, "invalid_cursor");
44937
- }
44938
- return { cause: history.cause, nextCursor: null, status: "unavailable" };
45123
+ return history;
44939
45124
  }
44940
- const endIndex = cursor === null ? history.messages.length : liveHistoryEndIndex(cursor, sessionId);
45125
+ const activeRoot = activeTurn.messages.find(
45126
+ (message) => message.role === "user" && message.id === activeTurn.commandId
45127
+ );
45128
+ const matchingActiveUserIndex = activeRoot === void 0 ? -1 : activeTurn.state === "completed" || history.messages.at(-1)?.role === "user" && history.messages.at(-1)?.text === activeRoot.text || history.coveredCommandIds.includes(activeTurn.commandId) ? history.messages.findLastIndex((message) => message.role === "user" && message.text === activeRoot.text) : -1;
45129
+ const activeUserIndex = matchingActiveUserIndex >= 0 ? matchingActiveUserIndex : liveHistoryCoveredTurnRanges(history).find((range) => range.commandId === activeTurn.commandId)?.userIndex ?? -1;
45130
+ if (activeUserIndex < 0) {
45131
+ return history;
45132
+ }
45133
+ return {
45134
+ ...history,
45135
+ coveredCommandIds: history.coveredCommandIds.filter((id) => id !== activeTurn.commandId),
45136
+ coveredMessageIds: history.coveredMessageIds.filter(
45137
+ (id) => id !== `${activeTurn.commandId}:user` && id !== `${activeTurn.commandId}:agent`
45138
+ ),
45139
+ messages: history.messages.slice(0, activeUserIndex)
45140
+ };
45141
+ }
45142
+ function paginateAvailableNativeHistory(history, sessionId, endIndex, limit, characterLimit) {
44941
45143
  if (endIndex > history.messages.length) {
44942
45144
  throw new AcpExecutionError("The native Session history cursor is invalid", {}, "invalid_cursor");
44943
45145
  }
44944
- const startIndex = Math.max(0, endIndex - limit);
45146
+ const selected = selectMessagesBefore(history.messages, endIndex, limit, characterLimit);
44945
45147
  const coveredMessageIds = new Set(history.coveredMessageIds);
44946
45148
  const ranges = liveHistoryCoveredTurnRanges(history);
44947
45149
  return {
44948
- coveredCommandIds: ranges.filter((range) => range.userIndex >= startIndex && range.assistantEndIndex <= endIndex).map((range) => range.commandId),
45150
+ coveredCommandIds: ranges.filter((range) => range.userIndex >= selected.startIndex && range.assistantEndIndex <= endIndex).map((range) => range.commandId),
44949
45151
  coveredMessageIds: ranges.flatMap((range) => [
44950
- ...range.userIndex >= startIndex && range.userIndex < endIndex && coveredMessageIds.has(`${range.commandId}:user`) ? [`${range.commandId}:user`] : [],
44951
- ...range.assistantStartIndex >= startIndex && range.assistantEndIndex <= endIndex && coveredMessageIds.has(`${range.commandId}:agent`) ? [`${range.commandId}:agent`] : []
45152
+ ...range.userIndex >= selected.startIndex && range.userIndex < endIndex && coveredMessageIds.has(`${range.commandId}:user`) ? [`${range.commandId}:user`] : [],
45153
+ ...range.assistantStartIndex >= selected.startIndex && range.assistantEndIndex <= endIndex && coveredMessageIds.has(`${range.commandId}:agent`) ? [`${range.commandId}:agent`] : []
44952
45154
  ]),
44953
- messages: history.messages.slice(startIndex, endIndex),
44954
- nextCursor: startIndex > 0 ? liveHistoryCursor(sessionId, startIndex) : null,
45155
+ messages: selected.messages,
45156
+ nextCursor: selected.startIndex > 0 ? nativeHistoryCursor(sessionId, selected.startIndex) : null,
44955
45157
  status: "available",
44956
45158
  truncated: history.truncated
44957
45159
  };
44958
45160
  }
44959
- function paginateLiveSessionHistory(history, activeTurn, sessionId, cursor, limit) {
44960
- if (activeTurn === null || cursor !== null) {
44961
- return paginateAcpSessionHistory(history, sessionId, cursor, limit);
45161
+ function paginateNativeHistory(history, sessionId, cursor, limit) {
45162
+ if (cursor?.kind === "active") {
45163
+ throw new AcpExecutionError("The native Session history cursor is invalid", {}, "invalid_cursor");
44962
45164
  }
44963
45165
  if (history.status === "unavailable") {
44964
- const bounded3 = boundedActiveMessages(
44965
- activeTurn.messages.map(nativeActiveMessage),
44966
- limit,
44967
- SESSION_HISTORY_CHARACTER_LIMIT
44968
- );
44969
- return {
44970
- coveredCommandIds: [],
44971
- coveredMessageIds: [],
44972
- messages: bounded3.messages,
44973
- nextCursor: null,
44974
- status: "available",
44975
- truncated: activeTurn.truncated || bounded3.truncated
44976
- };
45166
+ if (cursor !== null) {
45167
+ throw new AcpExecutionError("Unavailable native Session history has no cursor", {}, "invalid_cursor");
45168
+ }
45169
+ return { cause: history.cause, nextCursor: null, status: "unavailable" };
44977
45170
  }
44978
- const activeRoot = activeTurn.messages.find(
44979
- (message) => message.role === "user" && message.id === activeTurn.commandId
45171
+ return paginateAvailableNativeHistory(
45172
+ history,
45173
+ sessionId,
45174
+ cursor?.endIndex ?? history.messages.length,
45175
+ limit,
45176
+ SESSION_HISTORY_CHARACTER_LIMIT
44980
45177
  );
44981
- const coveredActiveRoot = history.coveredCommandIds.includes(activeTurn.commandId);
44982
- const matchingActiveUserIndex = coveredActiveRoot ? history.messages.findLastIndex(
44983
- (message) => message.role === "user" && message.text === (activeRoot?.text ?? activeTurn.prompt)
44984
- ) : -1;
44985
- const activeUserIndex = matchingActiveUserIndex >= 0 ? matchingActiveUserIndex : liveHistoryCoveredTurnRanges(history).find((range) => range.commandId === activeTurn.commandId)?.userIndex ?? -1;
44986
- if (activeUserIndex >= 0) {
44987
- history = {
44988
- ...history,
44989
- coveredCommandIds: history.coveredCommandIds.filter((id) => id !== activeTurn.commandId),
44990
- coveredMessageIds: history.coveredMessageIds.filter(
44991
- (id) => id !== `${activeTurn.commandId}:user` && id !== `${activeTurn.commandId}:agent`
44992
- ),
44993
- messages: history.messages.slice(0, activeUserIndex + 1)
44994
- };
44995
- }
44996
- const latestNativeMessage = history.messages.at(-1);
44997
- const firstActiveMessage = activeTurn.messages[0];
44998
- const preserveNativeRoot = activeUserIndex >= 0 || firstActiveMessage?.role === "user" && latestNativeMessage?.role === "user" && latestNativeMessage.text === firstActiveMessage.text;
44999
- const activeMessages = activeTurn.messages.slice(preserveNativeRoot ? 1 : 0).map(nativeActiveMessage);
45000
- const bounded2 = boundedActiveMessages(activeMessages, limit, SESSION_HISTORY_CHARACTER_LIMIT);
45001
- let historicalMessageCount = 0;
45002
- let remainingCharacters = SESSION_HISTORY_CHARACTER_LIMIT - bounded2.characters;
45003
- const historicalMessageLimit = Math.max(0, limit - bounded2.messages.length);
45004
- for (let index = history.messages.length - 1; index >= 0 && historicalMessageCount < historicalMessageLimit; index -= 1) {
45005
- const message = history.messages[index];
45006
- if (message === void 0) {
45007
- break;
45008
- }
45009
- const characters = historyMessageCharacters(message);
45010
- if (characters > remainingCharacters) {
45011
- break;
45012
- }
45013
- historicalMessageCount += 1;
45014
- remainingCharacters -= characters;
45178
+ }
45179
+ function paginateActiveTurn(history, activeTurn, sessionId, endIndex, limit) {
45180
+ if (endIndex > activeTurn.messages.length) {
45181
+ throw new AcpExecutionError("The native Session history cursor is invalid", {}, "invalid_cursor");
45015
45182
  }
45016
- const page = historicalMessageCount === 0 ? {
45017
- coveredCommandIds: [],
45018
- coveredMessageIds: [],
45019
- messages: [],
45020
- nextCursor: history.messages.length === 0 ? null : liveHistoryCursor(sessionId, history.messages.length),
45021
- status: "available",
45022
- truncated: history.truncated
45023
- } : paginateAcpSessionHistory(history, sessionId, null, historicalMessageCount);
45183
+ const activeMessages = activeTurn.messages.map(nativeActiveMessage);
45184
+ const active = selectMessagesBefore(activeMessages, endIndex, limit, SESSION_HISTORY_CHARACTER_LIMIT);
45185
+ const historicalLimit = limit - active.messages.length;
45186
+ const historicalCharacterLimit = SESSION_HISTORY_CHARACTER_LIMIT - active.characters;
45187
+ const pendingHistoricalCursor = history.status === "available" && history.messages.length > 0 ? nativeHistoryCursor(sessionId, history.messages.length) : null;
45188
+ const latestHistoricalMessage = history.status === "available" ? history.messages.at(-1) : void 0;
45189
+ const historicalPageFits = latestHistoricalMessage === void 0 || historyMessageCharacters(latestHistoricalMessage) <= historicalCharacterLimit;
45190
+ const historical = active.startIndex === 0 && history.status === "available" && historicalLimit > 0 && historicalPageFits ? paginateAvailableNativeHistory(
45191
+ history,
45192
+ sessionId,
45193
+ history.messages.length,
45194
+ historicalLimit,
45195
+ historicalCharacterLimit
45196
+ ) : null;
45024
45197
  return {
45025
- ...page,
45026
- messages: [...page.messages, ...bounded2.messages],
45027
- truncated: page.truncated || activeTurn.truncated || bounded2.truncated
45198
+ coveredCommandIds: historical?.coveredCommandIds ?? [],
45199
+ coveredMessageIds: [
45200
+ ...historical?.coveredMessageIds ?? [],
45201
+ ...active.startIndex === 0 ? [`${activeTurn.commandId}:user`, `${activeTurn.commandId}:agent`] : []
45202
+ ],
45203
+ messages: [...historical?.messages ?? [], ...active.messages],
45204
+ nextCursor: active.startIndex > 0 ? activeHistoryCursor(sessionId, activeTurn.epoch, active.startIndex) : historical === null ? pendingHistoricalCursor : historical.nextCursor,
45205
+ status: "available",
45206
+ truncated: activeTurn.truncated || (historical?.truncated ?? false)
45028
45207
  };
45029
45208
  }
45209
+ function paginateLiveSessionHistory(history, activeTurn, sessionId, encodedCursor, limit) {
45210
+ const cursor = encodedCursor === null ? null : decodeLiveHistoryCursor(encodedCursor, sessionId);
45211
+ if (activeTurn === null || activeTurn.state === "completed" && history.status === "available" && nativeHistoryCoversCompletedRecovery(history, activeTurn)) {
45212
+ return paginateNativeHistory(history, sessionId, cursor, limit);
45213
+ }
45214
+ const historical = historyBeforeActiveTurn(history, activeTurn);
45215
+ if (cursor?.kind === "native") {
45216
+ return paginateNativeHistory(historical, sessionId, cursor, limit);
45217
+ }
45218
+ if (cursor !== null && cursor.epoch !== activeTurn.epoch) {
45219
+ throw new AcpExecutionError("The active Turn history cursor is stale", {}, "stale_cursor");
45220
+ }
45221
+ return paginateActiveTurn(historical, activeTurn, sessionId, cursor?.endIndex ?? activeTurn.messages.length, limit);
45222
+ }
45030
45223
  function mergeLiveSessionHistory(history, activeTurn, sessionId) {
45031
45224
  const page = paginateLiveSessionHistory(history, activeTurn, sessionId, null, 1e3);
45032
45225
  if (page.status === "unavailable") {
@@ -45037,7 +45230,7 @@ function mergeLiveSessionHistory(history, activeTurn, sessionId) {
45037
45230
  coveredMessageIds: page.coveredMessageIds,
45038
45231
  messages: page.messages,
45039
45232
  status: "available",
45040
- truncated: page.truncated
45233
+ truncated: page.truncated || page.nextCursor !== null
45041
45234
  };
45042
45235
  }
45043
45236
 
@@ -45556,15 +45749,13 @@ async function executePrompt({
45556
45749
  });
45557
45750
  operations.transitionExecution(command.commandId, "running");
45558
45751
  if (availability.disposition === SessionPromptDisposition.Steer) {
45559
- const updated = recordSteeringPrompt();
45560
- if (updated === null) {
45752
+ if (!recordSteeringPrompt()) {
45561
45753
  throw new AcpExecutionError(
45562
45754
  "The steering target is unavailable",
45563
45755
  {},
45564
45756
  "prompt_queue_target_unavailable"
45565
45757
  );
45566
45758
  }
45567
- publisher.publish(command.sessionId, updated);
45568
45759
  }
45569
45760
  const stream = activateAttempt();
45570
45761
  publisher.publish(command.sessionId, {
@@ -45952,7 +46143,7 @@ function createDaemonCommandHandler({
45952
46143
  resolveEnvironment: resolveAgentCatalogEnvironment
45953
46144
  });
45954
46145
  const queueChanged = (sessionId) => publisher.publish(sessionId, { kind: "session.queue.changed", sessionId });
45955
- const activeTurnSnapshot = (sessionId) => activeTurns.snapshot((command) => command.sessionId === sessionId);
46146
+ const activeTurnRecovery = (sessionId) => activeTurns.recovery((command) => command.sessionId === sessionId);
45956
46147
  const nativeRequestHandler = createNativeSessionRequestHandler({
45957
46148
  audit,
45958
46149
  diagnostics,
@@ -45973,7 +46164,7 @@ function createDaemonCommandHandler({
45973
46164
  directories,
45974
46165
  audit,
45975
46166
  sessions,
45976
- activeTurnSnapshot,
46167
+ activeTurnRecovery,
45977
46168
  signal
45978
46169
  );
45979
46170
  return;
@@ -45987,7 +46178,7 @@ function createDaemonCommandHandler({
45987
46178
  directories,
45988
46179
  audit,
45989
46180
  sessions,
45990
- activeTurnSnapshot,
46181
+ activeTurnRecovery,
45991
46182
  signal
45992
46183
  );
45993
46184
  return;
@@ -46479,6 +46670,7 @@ async function runRelayConnection({
46479
46670
  }) {
46480
46671
  const relayUrl = new URL(config2.relayUrl);
46481
46672
  relayUrl.searchParams.set("liveness", "1");
46673
+ relayUrl.searchParams.set("sessionEventDelivery", "reliable-v1");
46482
46674
  const socket = await connectRelay(relayUrl, signal);
46483
46675
  diagnostics.connectionOpened(connectionEpoch);
46484
46676
  const connectionController = new AbortController();
@@ -46598,6 +46790,10 @@ async function runRelayConnection({
46598
46790
  const message = decodeServiceToDaemonMessage(encoded);
46599
46791
  const kind = message.kind;
46600
46792
  if (kind === RUNTIME_LIVENESS_READY_KIND) {
46793
+ publisher.connect(
46794
+ socket,
46795
+ message.sessionEventDelivery === "reliable-v1" ? "reliable" : "ephemeral"
46796
+ );
46601
46797
  connectionLiveness.start();
46602
46798
  return;
46603
46799
  }
@@ -46605,6 +46801,18 @@ async function runRelayConnection({
46605
46801
  connectionLiveness.acknowledge(message);
46606
46802
  return;
46607
46803
  }
46804
+ if (kind === SESSION_EVENT_ACK_KIND) {
46805
+ publisher.acknowledge(message);
46806
+ return;
46807
+ }
46808
+ if (kind === SESSION_EVENT_DEFERRED_KIND) {
46809
+ publisher.defer(message);
46810
+ return;
46811
+ }
46812
+ if (kind === SESSION_EVENT_REJECTED_KIND) {
46813
+ publisher.quarantine(message);
46814
+ return;
46815
+ }
46608
46816
  commands.handle(message);
46609
46817
  } catch (error62) {
46610
46818
  console.error("Mobius daemon rejected a message", error62);
@@ -46619,7 +46827,6 @@ async function runRelayConnection({
46619
46827
  socket.addEventListener("close", handleClose);
46620
46828
  socket.addEventListener("error", handleError);
46621
46829
  socket.addEventListener("message", handleMessage);
46622
- publisher.connect(socket);
46623
46830
  if (signal.aborted) {
46624
46831
  stop();
46625
46832
  }
@@ -46630,54 +46837,377 @@ async function runRelayConnection({
46630
46837
  }
46631
46838
  }
46632
46839
 
46840
+ // src/relay-connection/session-event-outbox.ts
46841
+ import { randomUUID as randomUUID7 } from "node:crypto";
46842
+ import { mkdirSync as mkdirSync3 } from "node:fs";
46843
+ import { dirname as dirname6 } from "node:path";
46844
+ import { DatabaseSync } from "node:sqlite";
46845
+ var DEFAULT_STANDARD_EVENT_BYTES = 96 * 1024 * 1024;
46846
+ var DEFAULT_CRITICAL_EVENT_BYTES = 128 * 1024 * 1024;
46847
+ var MAX_DEAD_LETTERS = 1024;
46848
+ var DEFAULT_LIMITS = {
46849
+ criticalEventBytes: DEFAULT_CRITICAL_EVENT_BYTES,
46850
+ standardEventBytes: DEFAULT_STANDARD_EVENT_BYTES
46851
+ };
46852
+ var SessionEventOutboxCapacityError = class extends Error {
46853
+ code = "session_event_outbox_full";
46854
+ constructor() {
46855
+ super("The persistent Session event outbox is full");
46856
+ this.name = "SessionEventOutboxCapacityError";
46857
+ }
46858
+ };
46859
+ function rowNumber(row, field) {
46860
+ const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
46861
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
46862
+ throw new Error(`SQLite returned an invalid ${field} value`);
46863
+ }
46864
+ return value;
46865
+ }
46866
+ function rowString(row, field) {
46867
+ const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
46868
+ if (typeof value !== "string" || value.length === 0) {
46869
+ throw new Error(`SQLite returned an invalid ${field} value`);
46870
+ }
46871
+ return value;
46872
+ }
46873
+ function validateLimits(limits) {
46874
+ if (!Number.isSafeInteger(limits.standardEventBytes) || limits.standardEventBytes < 1 || !Number.isSafeInteger(limits.criticalEventBytes) || limits.criticalEventBytes < limits.standardEventBytes) {
46875
+ throw new Error("Session event outbox limits are invalid");
46876
+ }
46877
+ }
46878
+ var SessionEventOutbox = class {
46879
+ #database;
46880
+ #limits;
46881
+ constructor(path3, limits = DEFAULT_LIMITS) {
46882
+ validateLimits(limits);
46883
+ mkdirSync3(dirname6(path3), { mode: 448, recursive: true });
46884
+ this.#limits = limits;
46885
+ this.#database = new DatabaseSync(path3);
46886
+ this.#database.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL; PRAGMA busy_timeout = 5000;");
46887
+ this.#database.exec(`
46888
+ CREATE TABLE IF NOT EXISTS relay_session_event_outbox (
46889
+ ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
46890
+ event_id TEXT NOT NULL UNIQUE,
46891
+ session_id TEXT NOT NULL,
46892
+ source_seq INTEGER NOT NULL UNIQUE,
46893
+ byte_length INTEGER NOT NULL,
46894
+ envelope_json TEXT NOT NULL
46895
+ ) STRICT;
46896
+ CREATE INDEX IF NOT EXISTS relay_session_event_outbox_order
46897
+ ON relay_session_event_outbox(source_seq);
46898
+ CREATE TABLE IF NOT EXISTS relay_session_event_outbox_state (
46899
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
46900
+ stream_id TEXT NOT NULL,
46901
+ next_source_seq INTEGER NOT NULL,
46902
+ total_bytes INTEGER NOT NULL
46903
+ ) STRICT;
46904
+ CREATE TABLE IF NOT EXISTS relay_session_event_dead_letters (
46905
+ ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
46906
+ event_id TEXT NOT NULL UNIQUE,
46907
+ session_id TEXT NOT NULL,
46908
+ rejection_code TEXT NOT NULL,
46909
+ envelope_json TEXT NOT NULL
46910
+ ) STRICT;
46911
+ `);
46912
+ this.#database.prepare(
46913
+ `INSERT OR IGNORE INTO relay_session_event_outbox_state (
46914
+ singleton, stream_id, next_source_seq, total_bytes
46915
+ ) VALUES (1, ?, 1, 0)`
46916
+ ).run(randomUUID7());
46917
+ }
46918
+ acknowledge = (acknowledgement) => this.#remove(acknowledgement.eventId, acknowledgement.sessionId);
46919
+ close = () => {
46920
+ this.#database.close();
46921
+ };
46922
+ enqueue = (sessionId, payload, critical) => {
46923
+ this.#database.exec("BEGIN IMMEDIATE");
46924
+ try {
46925
+ const state = this.#database.prepare(
46926
+ `SELECT stream_id, next_source_seq, total_bytes
46927
+ FROM relay_session_event_outbox_state
46928
+ WHERE singleton = 1`
46929
+ ).get();
46930
+ const sourceSeq = rowNumber(state, "next_source_seq");
46931
+ if (sourceSeq < 1 || !Number.isSafeInteger(sourceSeq + 1)) {
46932
+ throw new Error("The Session event source sequence is exhausted");
46933
+ }
46934
+ const envelope2 = {
46935
+ delivery: {
46936
+ eventId: randomUUID7(),
46937
+ kind: "reliable",
46938
+ sourceSeq: sourceSeq.toString(),
46939
+ streamId: rowString(state, "stream_id")
46940
+ },
46941
+ kind: SESSION_EVENT_KIND,
46942
+ payload,
46943
+ sessionId
46944
+ };
46945
+ const encoded = encodeSessionEventEnvelope(envelope2);
46946
+ const byteLength = Buffer.byteLength(encoded, "utf8");
46947
+ const maximumBytes = critical ? this.#limits.criticalEventBytes : this.#limits.standardEventBytes;
46948
+ if (rowNumber(state, "total_bytes") + byteLength > maximumBytes) {
46949
+ throw new SessionEventOutboxCapacityError();
46950
+ }
46951
+ this.#database.prepare(
46952
+ `INSERT INTO relay_session_event_outbox (
46953
+ event_id, session_id, source_seq, byte_length, envelope_json
46954
+ ) VALUES (?, ?, ?, ?, ?)`
46955
+ ).run(envelope2.delivery.eventId, sessionId, sourceSeq, byteLength, encoded);
46956
+ this.#database.prepare(
46957
+ `UPDATE relay_session_event_outbox_state
46958
+ SET next_source_seq = next_source_seq + 1,
46959
+ total_bytes = total_bytes + ?
46960
+ WHERE singleton = 1`
46961
+ ).run(byteLength);
46962
+ this.#database.exec("COMMIT");
46963
+ return envelope2;
46964
+ } catch (error62) {
46965
+ this.#database.exec("ROLLBACK");
46966
+ throw error62;
46967
+ }
46968
+ };
46969
+ pending = (limit, excludedEventIds) => {
46970
+ if (!Number.isSafeInteger(limit) || limit < 1) {
46971
+ throw new Error("Session event replay limit must be a positive integer");
46972
+ }
46973
+ const exclusion = excludedEventIds.length === 0 ? "" : `WHERE event_id NOT IN (${excludedEventIds.map(() => "?").join(", ")})`;
46974
+ return this.#database.prepare(
46975
+ `SELECT envelope_json
46976
+ FROM relay_session_event_outbox
46977
+ ${exclusion}
46978
+ ORDER BY source_seq
46979
+ LIMIT ?`
46980
+ ).all(...excludedEventIds, limit).map((row) => {
46981
+ const envelope2 = decodeSessionEventEnvelope(rowString(row, "envelope_json"));
46982
+ if (!("delivery" in envelope2)) {
46983
+ throw new Error("SQLite returned an invalid reliable Session event envelope");
46984
+ }
46985
+ return envelope2;
46986
+ });
46987
+ };
46988
+ quarantine = (rejection) => {
46989
+ this.#database.exec("BEGIN IMMEDIATE");
46990
+ try {
46991
+ const stored = this.#database.prepare(
46992
+ `SELECT byte_length, envelope_json
46993
+ FROM relay_session_event_outbox
46994
+ WHERE event_id = ? AND session_id = ?`
46995
+ ).get(rejection.eventId, rejection.sessionId);
46996
+ if (stored === void 0) {
46997
+ this.#database.exec("COMMIT");
46998
+ return false;
46999
+ }
47000
+ this.#database.prepare(
47001
+ `INSERT INTO relay_session_event_dead_letters (
47002
+ event_id, session_id, rejection_code, envelope_json
47003
+ ) VALUES (?, ?, ?, ?)`
47004
+ ).run(rejection.eventId, rejection.sessionId, rejection.code, rowString(stored, "envelope_json"));
47005
+ this.#deleteStored(rejection.eventId, rejection.sessionId, rowNumber(stored, "byte_length"));
47006
+ this.#database.prepare(
47007
+ `DELETE FROM relay_session_event_dead_letters
47008
+ WHERE ordinal NOT IN (
47009
+ SELECT ordinal FROM relay_session_event_dead_letters
47010
+ ORDER BY ordinal DESC LIMIT ?
47011
+ )`
47012
+ ).run(MAX_DEAD_LETTERS);
47013
+ this.#database.exec("COMMIT");
47014
+ return true;
47015
+ } catch (error62) {
47016
+ this.#database.exec("ROLLBACK");
47017
+ throw error62;
47018
+ }
47019
+ };
47020
+ #deleteStored = (eventId, sessionId, byteLength) => {
47021
+ const deletion = this.#database.prepare(
47022
+ `DELETE FROM relay_session_event_outbox
47023
+ WHERE event_id = ? AND session_id = ?`
47024
+ ).run(eventId, sessionId);
47025
+ if (deletion.changes !== 1) {
47026
+ throw new Error("The Session event could not be removed from the outbox");
47027
+ }
47028
+ const state = this.#database.prepare(
47029
+ `UPDATE relay_session_event_outbox_state
47030
+ SET total_bytes = total_bytes - ?
47031
+ WHERE singleton = 1 AND total_bytes >= ?`
47032
+ ).run(byteLength, byteLength);
47033
+ if (state.changes !== 1) {
47034
+ throw new Error("The Session event outbox capacity state is inconsistent");
47035
+ }
47036
+ };
47037
+ #remove = (eventId, sessionId) => {
47038
+ this.#database.exec("BEGIN IMMEDIATE");
47039
+ try {
47040
+ const stored = this.#database.prepare(
47041
+ `SELECT byte_length
47042
+ FROM relay_session_event_outbox
47043
+ WHERE event_id = ? AND session_id = ?`
47044
+ ).get(eventId, sessionId);
47045
+ if (stored === void 0) {
47046
+ this.#database.exec("COMMIT");
47047
+ return false;
47048
+ }
47049
+ this.#deleteStored(eventId, sessionId, rowNumber(stored, "byte_length"));
47050
+ this.#database.exec("COMMIT");
47051
+ return true;
47052
+ } catch (error62) {
47053
+ this.#database.exec("ROLLBACK");
47054
+ throw error62;
47055
+ }
47056
+ };
47057
+ };
47058
+
46633
47059
  // src/relay-connection/session-event-publisher.ts
46634
47060
  import { WebSocket as WebSocket9 } from "undici";
46635
- var MAX_LIVE_EVENT_BUFFERED_BYTES = 16 * 1024 * 1024;
47061
+ var SESSION_EVENT_REPLAY_WINDOW = 16;
47062
+ var SESSION_EVENT_REPLAY_BYTE_LIMIT = 32 * 1024 * 1024;
47063
+ function reservesSessionEventCapacity(event) {
47064
+ if (event.kind === SESSION_COMPLETED_KIND || event.kind === SESSION_FAILED_KIND) {
47065
+ return true;
47066
+ }
47067
+ return event.kind === AGENT_ACTIVITY_KIND && (event.activityType === "progress" || event.activityType === "reasoning") && (event.status === "completed" || event.status === "failed");
47068
+ }
46636
47069
  var DaemonSessionEventPublisher = class {
46637
- #socket = null;
46638
- #reportedDrop = false;
46639
- connect = (socket) => {
46640
- this.#socket = socket;
46641
- this.#reportedDrop = false;
47070
+ #inFlight = /* @__PURE__ */ new Map();
47071
+ #outbox;
47072
+ #reportedCapacityKinds = /* @__PURE__ */ new Set();
47073
+ #timer;
47074
+ #connection = { kind: "disconnected" };
47075
+ #deferredReplay = null;
47076
+ #inFlightBytes = 0;
47077
+ constructor(outbox, timer) {
47078
+ this.#outbox = outbox;
47079
+ this.#timer = timer;
47080
+ }
47081
+ acknowledge = (acknowledgement) => {
47082
+ if (!this.#outbox.acknowledge(acknowledgement)) {
47083
+ return;
47084
+ }
47085
+ this.#removeInFlight(acknowledgement.eventId);
47086
+ this.#publishNextBatch();
47087
+ };
47088
+ close = () => {
47089
+ this.#deferredReplay?.cancel();
47090
+ this.#deferredReplay = null;
47091
+ this.#connection = { kind: "disconnected" };
47092
+ this.#clearInFlight();
47093
+ this.#outbox.close();
47094
+ };
47095
+ connect = (socket, delivery) => {
47096
+ this.#connection = { delivery, kind: "connected", socket };
47097
+ this.#clearInFlight();
47098
+ this.#publishNextBatch();
47099
+ };
47100
+ defer = (deferred) => {
47101
+ if (!this.#inFlight.has(deferred.eventId)) {
47102
+ return;
47103
+ }
47104
+ this.#clearInFlight();
47105
+ if (this.#deferredReplay !== null) {
47106
+ return;
47107
+ }
47108
+ this.#deferredReplay = this.#timer.schedule(() => {
47109
+ this.#deferredReplay = null;
47110
+ this.#publishNextBatch();
47111
+ }, deferred.retryAfterMilliseconds);
46642
47112
  };
46643
47113
  disconnect = (socket) => {
46644
- if (this.#socket === socket) {
46645
- this.#socket = null;
47114
+ if (this.#connection.kind !== "connected" || this.#connection.socket !== socket) {
47115
+ return;
46646
47116
  }
47117
+ this.#connection = { kind: "disconnected" };
47118
+ this.#clearInFlight();
46647
47119
  };
46648
47120
  publish = (sessionId, event) => {
46649
- const socket = this.#socket;
46650
- if (socket === null || socket.readyState !== WebSocket9.OPEN) {
46651
- this.#reportDrop();
47121
+ try {
47122
+ this.#outbox.enqueue(sessionId, encodeRecordedSessionEvent(event), reservesSessionEventCapacity(event));
47123
+ } catch (error62) {
47124
+ if (!(error62 instanceof SessionEventOutboxCapacityError)) {
47125
+ throw error62;
47126
+ }
47127
+ if (!this.#reportedCapacityKinds.has(event.kind)) {
47128
+ this.#reportedCapacityKinds.add(event.kind);
47129
+ console.warn(
47130
+ `Mobius daemon could not persist "${event.kind}" because the Session event outbox is full`
47131
+ );
47132
+ }
46652
47133
  return;
46653
47134
  }
46654
- if (socket.bufferedAmount >= MAX_LIVE_EVENT_BUFFERED_BYTES) {
46655
- this.#socket = null;
46656
- socket.close(1013, "Live event backpressure");
46657
- this.#reportDrop();
47135
+ this.#publishNextBatch();
47136
+ };
47137
+ quarantine = (rejection) => {
47138
+ if (!this.#outbox.quarantine(rejection)) {
46658
47139
  return;
46659
47140
  }
46660
- try {
46661
- socket.send(
46662
- encodeDaemonToServiceMessage({
46663
- kind: SESSION_EVENT_KIND,
46664
- payload: encodeRecordedSessionEvent(event),
46665
- sessionId
46666
- })
46667
- );
46668
- } catch {
46669
- this.#reportDrop();
46670
- }
47141
+ this.#removeInFlight(rejection.eventId);
47142
+ console.error(`Mobius Relay rejected Session event ${rejection.eventId} with code ${rejection.code}`);
47143
+ this.#publishNextBatch();
46671
47144
  };
46672
- close = () => {
46673
- this.#socket = null;
47145
+ #clearInFlight = () => {
47146
+ this.#inFlight.clear();
47147
+ this.#inFlightBytes = 0;
46674
47148
  };
46675
- #reportDrop = () => {
46676
- if (this.#reportedDrop) {
47149
+ #publishNextBatch = () => {
47150
+ if (this.#deferredReplay !== null || this.#connection.kind !== "connected") {
46677
47151
  return;
46678
47152
  }
46679
- this.#reportedDrop = true;
46680
- console.warn("Mobius daemon dropped live Session events while the Relay was unavailable");
47153
+ while (this.#inFlight.size < SESSION_EVENT_REPLAY_WINDOW) {
47154
+ const batch = this.#outbox.pending(SESSION_EVENT_REPLAY_WINDOW - this.#inFlight.size, [
47155
+ ...this.#inFlight.keys()
47156
+ ]);
47157
+ let sent = 0;
47158
+ for (const envelope2 of batch) {
47159
+ const encoded = encodeDaemonToServiceMessage(
47160
+ this.#connection.delivery === "reliable" ? envelope2 : {
47161
+ kind: envelope2.kind,
47162
+ payload: envelope2.payload,
47163
+ sessionId: envelope2.sessionId
47164
+ }
47165
+ );
47166
+ const byteLength = Buffer.byteLength(encoded, "utf8");
47167
+ if (this.#inFlightBytes + byteLength > SESSION_EVENT_REPLAY_BYTE_LIMIT) {
47168
+ return;
47169
+ }
47170
+ if (!this.#send(encoded)) {
47171
+ this.#clearInFlight();
47172
+ return;
47173
+ }
47174
+ if (this.#connection.delivery === "ephemeral") {
47175
+ this.#outbox.acknowledge({
47176
+ eventId: envelope2.delivery.eventId,
47177
+ kind: "session.event.ack",
47178
+ outcome: { kind: "relayed" },
47179
+ sessionId: envelope2.sessionId
47180
+ });
47181
+ sent += 1;
47182
+ continue;
47183
+ }
47184
+ this.#inFlight.set(envelope2.delivery.eventId, { byteLength });
47185
+ this.#inFlightBytes += byteLength;
47186
+ sent += 1;
47187
+ }
47188
+ if (sent === 0) {
47189
+ return;
47190
+ }
47191
+ }
47192
+ };
47193
+ #removeInFlight = (eventId) => {
47194
+ const inFlight = this.#inFlight.get(eventId);
47195
+ if (inFlight === void 0) {
47196
+ return;
47197
+ }
47198
+ this.#inFlight.delete(eventId);
47199
+ this.#inFlightBytes -= inFlight.byteLength;
47200
+ };
47201
+ #send = (encoded) => {
47202
+ if (this.#connection.kind !== "connected" || this.#connection.socket.readyState !== WebSocket9.OPEN) {
47203
+ return false;
47204
+ }
47205
+ try {
47206
+ this.#connection.socket.send(encoded);
47207
+ return true;
47208
+ } catch {
47209
+ return false;
47210
+ }
46681
47211
  };
46682
47212
  };
46683
47213
 
@@ -46738,7 +47268,7 @@ async function installDetectedAgentIntegrations(config2, installations, signal)
46738
47268
  {
46739
47269
  agentId: agent.id,
46740
47270
  kind: AGENT_INSTALL_REQUEST_KIND,
46741
- requestId: `automatic-integration-${randomUUID7()}`
47271
+ requestId: `automatic-integration-${randomUUID8()}`
46742
47272
  },
46743
47273
  signal
46744
47274
  );
@@ -46760,7 +47290,11 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer, opti
46760
47290
  join18(stateDirectory, "sessions", `${createHash6("sha256").update(config2.channelId).digest("hex")}.sqlite`)
46761
47291
  );
46762
47292
  const operations = new DaemonOperationState();
46763
- const publisher = new DaemonSessionEventPublisher();
47293
+ const channelHash = createHash6("sha256").update(config2.channelId).digest("hex");
47294
+ const publisher = new DaemonSessionEventPublisher(
47295
+ new SessionEventOutbox(join18(stateDirectory, "events", `${channelHash}.sqlite`)),
47296
+ timer
47297
+ );
46764
47298
  const activeTurns = new DaemonTurnRegistry(operations);
46765
47299
  const recordSessionEvent = (sessionId, event) => {
46766
47300
  publisher.publish(sessionId, activeTurns.recordEvent(event));