@yefengr/remote-pi 0.7.0 → 0.7.2

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.
@@ -118,5 +118,9 @@ export declare function handleSessionCompact(ctx: ActionCtx | null, sender: Acti
118
118
  export declare function handleSessionNew(ctx: ActionCtx | null, sender: ActionReplySender, msg: SessionNewMsg, onReplaced?: (freshCtx: ActionCtx) => void): Promise<boolean>;
119
119
  export declare function handleThinkingSet(pi: ActionPi, sender: ActionReplySender, msg: ThinkingSetMsg): void;
120
120
  export declare function handleModelSet(pi: ActionPi, ctx: ActionCtx | null, reg: ActionModelRegistry, sender: ActionReplySender, msg: ModelSetMsg, onPersist?: (provider: string, modelId: string) => void): Promise<void>;
121
+ export declare function getModelsList(ctx: ActionCtx | null, reg: ActionModelRegistry, currentNameOrId?: string): {
122
+ models: WireModel[];
123
+ current?: WireModel;
124
+ };
121
125
  export declare function handleListModels(ctx: ActionCtx | null, reg: ActionModelRegistry, sender: ActionReplySender, msg: ListModelsMsg): void;
122
126
  export {};
@@ -130,23 +130,26 @@ export async function handleModelSet(pi, ctx, reg, sender, msg, onPersist) {
130
130
  onPersist?.(model.provider, model.id);
131
131
  });
132
132
  }
133
+ export function getModelsList(ctx, reg, currentNameOrId) {
134
+ // Prefer Pi's LIVE session registry when available so the app sees models
135
+ // registered dynamically by extensions via `pi.registerProvider(...)`.
136
+ // Fall back to remote-pi's own disk-backed registry when no ctx exists.
137
+ const liveReg = ctx?.modelRegistry ?? reg;
138
+ liveReg.refresh();
139
+ const available = liveReg.getAvailable();
140
+ const models = available.map(wireFromModel);
141
+ const liveCurrent = ctx?.getModel?.();
142
+ const cachedMatches = currentNameOrId
143
+ ? available.filter((model) => model.id === currentNameOrId || model.name === currentNameOrId)
144
+ : [];
145
+ const current = liveCurrent ?? (cachedMatches.length === 1 ? cachedMatches[0] : undefined);
146
+ return { models, ...(current ? { current: wireFromModel(current) } : {}) };
147
+ }
133
148
  export function handleListModels(ctx, reg, sender, msg) {
134
149
  // refresh() can throw if `models.json` is malformed — wrap in try so the
135
150
  // app gets an explicit error reply instead of a silent drop.
136
151
  try {
137
- // Prefer Pi's LIVE session registry when available so the app sees models
138
- // registered dynamically by extensions via `pi.registerProvider(...)`.
139
- // Fall back to remote-pi's own disk-backed registry when no ctx exists.
140
- const liveReg = ctx?.modelRegistry ?? reg;
141
- liveReg.refresh();
142
- const models = liveReg.getAvailable().map(wireFromModel);
143
- const current = ctx?.getModel?.();
144
- sender.send({
145
- type: "models_list",
146
- in_reply_to: msg.id,
147
- models,
148
- current: current ? wireFromModel(current) : undefined,
149
- });
152
+ sender.send({ type: "models_list", in_reply_to: msg.id, ...getModelsList(ctx, reg) });
150
153
  }
151
154
  catch (e) {
152
155
  sender.send({
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/actions/handlers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/actions/handlers.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -59,6 +59,12 @@ type BufferMsg = {
59
59
  * `role:"compaction"` marker pushed in `session_compact`. */
60
60
  tokensBefore?: number;
61
61
  };
62
+ export declare const MAX_QUEUED_IMAGE_BYTES_PER_OWNER: number;
63
+ export declare const MAX_QUEUED_IMAGE_BYTES_GLOBAL: number;
64
+ type QueuedImageLimits = {
65
+ perOwner: number;
66
+ global: number;
67
+ };
62
68
  type MeshEnvelope = {
63
69
  id: string;
64
70
  from: string;
@@ -66,6 +72,8 @@ type MeshEnvelope = {
66
72
  body: unknown;
67
73
  };
68
74
  export declare function _getTimelinePublishedEventsForTest(): unknown[];
75
+ /** Test-only: lower the image budget without allocating multi-megabyte frames. */
76
+ export declare function _setQueuedImageLimitsForTest(limits: QueuedImageLimits | null): void;
69
77
  /** Test-only override of the message buffer. */
70
78
  /**
71
79
  * Test-only: emulate what `/remote-pi` does on the returning-user path
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ import { addPeer, getOrCreateEd25519Keypair, KeyringUnavailableError, PairedIden
36
36
  import { MeshClient } from "./mesh/client.js";
37
37
  import { canonicalizeEd25519PublicKey, decodeEd25519PublicKey, publicKeyFingerprint, } from "./mesh/encoding.js";
38
38
  import { SelfRevoke } from "./mesh/self_revoke.js";
39
- import { decodeClientFrameV2, encodeServerFrameV2, parseTimelinePartialV2, } from "./protocol/v2/index.js";
39
+ import { decodeClientFrameV2, encodeServerFrameV2, fingerprintV2Payload, MAX_FRAME_BYTES, parseTimelinePartialV2, } from "./protocol/v2/index.js";
40
40
  import { RelayClient, RoomAlreadyOpenError } from "./transport/relay_client.js";
41
41
  import { PlainPeerChannel, V2PeerChannel } from "./transport/peer_channel.js";
42
42
  import { TimelineV2Service } from "./timeline/v2_service.js";
@@ -45,7 +45,7 @@ import { roomIdFor } from "./rooms.js";
45
45
  import { registerAgentTools } from "./session/tools.js";
46
46
  import { formatPeerInventory } from "./session/peer_inventory.js";
47
47
  import { MeshNode } from "./session/mesh_node.js";
48
- import { handleSessionCompact, handleModelSet, handleThinkingSet, handleListModels, } from "./actions/handlers.js";
48
+ import { handleSessionCompact, handleModelSet, handleThinkingSet, handleListModels, getModelsList, } from "./actions/handlers.js";
49
49
  import { ensureModelRegistry } from "./actions/registry.js";
50
50
  import { ensureGlobalDirs, LOCAL_SESSION_NAME, sessionAuditPath, sessionSockPath, skillsDir, } from "./session/global_config.js";
51
51
  import { acquireCwdLock } from "./session/cwd_lock.js";
@@ -755,7 +755,8 @@ function _ensureV2Service(senderRef) {
755
755
  const steering = frame.streaming_behavior === "steer";
756
756
  const busy = _agentRunActive || _isBusyForQueueDrain() || _v2QueuedDrainScheduled;
757
757
  if (!steering && busy) {
758
- return _enqueueV2UserMessage(senderRef, frame, correlation);
758
+ const queued = _enqueueV2UserMessage(senderRef, frame, correlation);
759
+ return queued === true ? "queued" : queued;
759
760
  }
760
761
  const previousTurnId = _currentTurnId;
761
762
  const seededTurnId = !steering || _currentTurnId === null;
@@ -772,6 +773,8 @@ function _ensureV2Service(senderRef) {
772
773
  return true;
773
774
  },
774
775
  onCancel: (targetId) => _abortCurrentTurn(_lastEventCtx ?? _lastCtx ?? undefined),
776
+ onQueuedMessageClear: (targetId) => _clearV2QueuedUserMessages(senderRef, targetId),
777
+ onListModels: () => getModelsList((_lastEventCtx ?? _lastCtx), ensureModelRegistry((_lastEventCtx ?? _lastCtx)), _currentModel),
775
778
  });
776
779
  return service;
777
780
  }
@@ -789,6 +792,10 @@ function _routeV2ClientFrameFrom(senderRef, frame) {
789
792
  const frames = binding.service.handle(frame);
790
793
  for (const response of frames)
791
794
  binding.channel.sendV2(response);
795
+ if (frame.type === "session_hello") {
796
+ for (const state of _v2QueuedStateFrames(binding.service))
797
+ binding.channel.sendV2(state);
798
+ }
792
799
  }
793
800
  function _attachV2Peer(relay, appPeerId) {
794
801
  _detachPeerChannel(appPeerId);
@@ -808,6 +815,11 @@ let _queuedItems = [];
808
815
  const MAX_V2_QUEUED_USERS_PER_OWNER = 128;
809
816
  const MAX_V2_QUEUED_USERS = 512;
810
817
  const V2_QUEUED_USER_TTL_MS = 5 * 60 * 1000;
818
+ export const MAX_QUEUED_IMAGE_BYTES_PER_OWNER = 8 * 1024 * 1024;
819
+ export const MAX_QUEUED_IMAGE_BYTES_GLOBAL = 32 * 1024 * 1024;
820
+ let _queuedImageBytesGlobal = 0;
821
+ const _queuedImageBytesByOwner = new Map();
822
+ let _queuedImageLimitsForTest = null;
811
823
  let _v2QueuedUserMessages = [];
812
824
  let _v2QueuedDrainScheduled = false;
813
825
  let _v2QueuedEpoch = 0;
@@ -835,12 +847,69 @@ let _pendingMeshMessages = [];
835
847
  let _agentRunActive = false;
836
848
  let _agentRunGeneration = 0;
837
849
  let _meshDrainScheduled = false;
850
+ function _queuedImageLimits() {
851
+ return _queuedImageLimitsForTest ?? {
852
+ perOwner: MAX_QUEUED_IMAGE_BYTES_PER_OWNER,
853
+ global: MAX_QUEUED_IMAGE_BYTES_GLOBAL,
854
+ };
855
+ }
856
+ /** Test-only: lower the image budget without allocating multi-megabyte frames. */
857
+ export function _setQueuedImageLimitsForTest(limits) {
858
+ if (limits && (!Number.isSafeInteger(limits.perOwner) || limits.perOwner < 0 ||
859
+ !Number.isSafeInteger(limits.global) || limits.global < 0)) {
860
+ throw new Error("queued image limits must be non-negative safe integers");
861
+ }
862
+ _queuedImageLimitsForTest = limits ? { ...limits } : null;
863
+ }
864
+ /**
865
+ * Count the retained wire representation, never decoded bytes. Base64 is ASCII
866
+ * under the strict v2 schema, and UTF-8 remains a conservative estimate for
867
+ * legacy input; this is stable without decoding or retaining a second buffer.
868
+ */
869
+ function _estimateQueuedImageBytes(images) {
870
+ if (!images || images.length === 0)
871
+ return 0;
872
+ return images.reduce((total, image) => total + Buffer.byteLength(image.data, "utf8") + Buffer.byteLength(image.mime, "utf8"), 0);
873
+ }
874
+ function _reserveQueuedImageBytes(ownerRef, imageBytes) {
875
+ if (imageBytes === 0)
876
+ return true;
877
+ const limits = _queuedImageLimits();
878
+ const ownerBytes = _queuedImageBytesByOwner.get(ownerRef) ?? 0;
879
+ if (ownerBytes + imageBytes > limits.perOwner || _queuedImageBytesGlobal + imageBytes > limits.global) {
880
+ return false;
881
+ }
882
+ _queuedImageBytesGlobal += imageBytes;
883
+ _queuedImageBytesByOwner.set(ownerRef, ownerBytes + imageBytes);
884
+ return true;
885
+ }
886
+ function _releaseQueuedImageBytes(ownerRef, imageBytes) {
887
+ if (imageBytes === 0)
888
+ return;
889
+ const ownerBytes = _queuedImageBytesByOwner.get(ownerRef) ?? 0;
890
+ const remainingOwnerBytes = Math.max(0, ownerBytes - imageBytes);
891
+ if (remainingOwnerBytes === 0)
892
+ _queuedImageBytesByOwner.delete(ownerRef);
893
+ else
894
+ _queuedImageBytesByOwner.set(ownerRef, remainingOwnerBytes);
895
+ _queuedImageBytesGlobal = Math.max(0, _queuedImageBytesGlobal - imageBytes);
896
+ }
897
+ function _restoreQueuedImageBytes(ownerRef, imageBytes) {
898
+ if (imageBytes === 0)
899
+ return;
900
+ _queuedImageBytesGlobal += imageBytes;
901
+ _queuedImageBytesByOwner.set(ownerRef, (_queuedImageBytesByOwner.get(ownerRef) ?? 0) + imageBytes);
902
+ }
903
+ function _queuedStateItem(item) {
904
+ const { ownerRef, imageBytes: _imageBytes, ...wireItem } = item;
905
+ return { ...wireItem, sender_ref: ownerRef };
906
+ }
838
907
  function _queuedStateMessage() {
839
908
  const first = _queuedItems[0];
840
909
  return {
841
910
  type: "queued_message_state",
842
911
  ...(first ? { id: first.id, text: first.text } : {}),
843
- items: _queuedItems.map((item) => ({ ...item })),
912
+ items: _queuedItems.map(_queuedStateItem),
844
913
  };
845
914
  }
846
915
  function _sendQueuedState(sender) {
@@ -850,28 +919,36 @@ function _broadcastQueuedState() {
850
919
  _broadcastToActive(_queuedStateMessage());
851
920
  }
852
921
  function _resetQueuedItems({ broadcast = false } = {}) {
922
+ for (const item of _queuedItems)
923
+ _releaseQueuedImageBytes(item.ownerRef, item.imageBytes);
853
924
  _queuedItems = [];
854
925
  if (broadcast)
855
926
  _broadcastQueuedState();
856
927
  }
857
- function _upsertQueuedItem(item) {
858
- const index = _queuedItems.findIndex((existing) => existing.id === item.id);
859
- if (index === -1) {
860
- _queuedItems = [..._queuedItems, item];
861
- }
862
- else {
863
- _queuedItems = [
864
- ..._queuedItems.slice(0, index),
865
- item,
866
- ..._queuedItems.slice(index + 1),
867
- ];
928
+ function _enqueuePwaQueuedItem(item) {
929
+ if (_queuedItems.some((existing) => existing.id === item.id)) {
930
+ // Image attachments are immutable while queued. A retry is free; changing
931
+ // the item requires queued_message_clear followed by a new id.
932
+ _broadcastQueuedState();
933
+ return true;
868
934
  }
935
+ if (!_reserveQueuedImageBytes(item.ownerRef, item.imageBytes))
936
+ return false;
937
+ _queuedItems = [..._queuedItems, item];
869
938
  _broadcastQueuedState();
939
+ return true;
870
940
  }
871
941
  function _clearQueuedItems(targetId) {
872
- _queuedItems = targetId
873
- ? _queuedItems.filter((item) => item.id !== targetId)
874
- : [];
942
+ const retained = [];
943
+ for (const item of _queuedItems) {
944
+ if (targetId === undefined || item.id === targetId) {
945
+ _releaseQueuedImageBytes(item.ownerRef, item.imageBytes);
946
+ }
947
+ else {
948
+ retained.push(item);
949
+ }
950
+ }
951
+ _queuedItems = retained;
875
952
  _broadcastQueuedState();
876
953
  }
877
954
  function _isBusyForQueueDrain() {
@@ -912,13 +989,20 @@ function _maybeDrainQueuedItem() {
912
989
  const item = _queuedItems.shift();
913
990
  if (!item)
914
991
  return;
992
+ _releaseQueuedImageBytes(item.ownerRef, item.imageBytes);
915
993
  _broadcastQueuedState();
916
994
  const previousTurnId = _currentTurnId;
917
995
  _currentTurnId = item.id;
918
- const msg = { type: "user_message", id: item.id, text: item.text };
919
- const wake = _wakeAgent(item.text, `queued PWA user_message id=${item.id}`, "steer");
996
+ const msg = {
997
+ type: "user_message",
998
+ id: item.id,
999
+ text: item.text,
1000
+ ...(item.images && item.images.length > 0 ? { images: item.images } : {}),
1001
+ };
1002
+ const wake = _wakeAgent(_v2UserContent(item), `queued PWA user_message id=${item.id}`, "steer");
920
1003
  if (!wake.ok) {
921
1004
  _currentTurnId = previousTurnId;
1005
+ _restoreQueuedImageBytes(item.ownerRef, item.imageBytes);
922
1006
  _queuedItems = [item, ..._queuedItems];
923
1007
  _broadcastQueuedState();
924
1008
  _broadcastToActive({
@@ -940,12 +1024,97 @@ function _v2UserContent(frame) {
940
1024
  : frame.text;
941
1025
  }
942
1026
  function _resetV2QueuedUserMessages() {
1027
+ for (const item of _v2QueuedUserMessages)
1028
+ _releaseQueuedImageBytes(item.senderRef, item.imageBytes);
943
1029
  _v2QueuedUserMessages = [];
944
1030
  _v2QueuedDrainScheduled = false;
945
1031
  _v2QueuedEpoch += 1;
1032
+ _broadcastV2QueuedState();
946
1033
  }
947
1034
  function _v2QueuedPayloadFingerprint(frame) {
948
- return JSON.stringify({ text: frame.text, images: frame.images ?? [] });
1035
+ return fingerprintV2Payload({
1036
+ text: frame.text,
1037
+ images: frame.images ?? [],
1038
+ streaming_behavior: frame.streaming_behavior ?? null,
1039
+ });
1040
+ }
1041
+ function _v2QueuedWireItem(item) {
1042
+ return {
1043
+ id: item.frame.client_request_id,
1044
+ text: item.frame.text,
1045
+ ...(item.frame.images && item.frame.images.length > 0 ? { images: item.frame.images } : {}),
1046
+ sender_ref: item.senderRef,
1047
+ editable: true,
1048
+ created_at: item.enqueuedAt,
1049
+ };
1050
+ }
1051
+ function _v2QueuedItemFitsFrame(item, service) {
1052
+ return Buffer.byteLength(JSON.stringify({
1053
+ protocol_version: 2,
1054
+ type: "queued_message_state",
1055
+ session_id: _currentSessionManager?.getSessionId() ?? "unknown",
1056
+ history_generation: service.generation,
1057
+ snapshot_id: randomUUID(),
1058
+ chunk_index: 0,
1059
+ final: true,
1060
+ items: [_v2QueuedWireItem(item)],
1061
+ }), "utf8") <= MAX_FRAME_BYTES;
1062
+ }
1063
+ function _v2QueuedStateFrames(service) {
1064
+ const snapshotId = randomUUID();
1065
+ const items = _v2QueuedUserMessages.map((item) => _v2QueuedWireItem(item));
1066
+ const chunks = [];
1067
+ let current = [];
1068
+ for (const item of items) {
1069
+ const candidate = [...current, item];
1070
+ const probe = {
1071
+ protocol_version: 2,
1072
+ type: "queued_message_state",
1073
+ session_id: _currentSessionManager?.getSessionId() ?? "unknown",
1074
+ history_generation: service.generation,
1075
+ snapshot_id: snapshotId,
1076
+ chunk_index: chunks.length,
1077
+ final: false,
1078
+ items: candidate,
1079
+ };
1080
+ if (current.length > 0 && Buffer.byteLength(JSON.stringify(probe), "utf8") > MAX_FRAME_BYTES) {
1081
+ chunks.push(current);
1082
+ current = [item];
1083
+ }
1084
+ else {
1085
+ current = candidate;
1086
+ }
1087
+ }
1088
+ if (current.length > 0 || chunks.length === 0)
1089
+ chunks.push(current);
1090
+ return chunks.map((itemsChunk, index) => ({
1091
+ protocol_version: 2,
1092
+ type: "queued_message_state",
1093
+ session_id: _currentSessionManager?.getSessionId() ?? "unknown",
1094
+ history_generation: service.generation,
1095
+ snapshot_id: snapshotId,
1096
+ chunk_index: index,
1097
+ final: index === chunks.length - 1,
1098
+ items: itemsChunk,
1099
+ }));
1100
+ }
1101
+ function _broadcastV2QueuedState() {
1102
+ _broadcastV2((service) => _v2QueuedStateFrames(service));
1103
+ }
1104
+ function _clearV2QueuedUserMessages(senderRef, targetId) {
1105
+ const retained = [];
1106
+ for (const item of _v2QueuedUserMessages) {
1107
+ const matchesOwner = item.senderRef === senderRef;
1108
+ const matchesTarget = targetId === undefined || item.frame.client_request_id === targetId;
1109
+ if (matchesOwner && matchesTarget) {
1110
+ _releaseQueuedImageBytes(item.senderRef, item.imageBytes);
1111
+ }
1112
+ else {
1113
+ retained.push(item);
1114
+ }
1115
+ }
1116
+ _v2QueuedUserMessages = retained;
1117
+ _broadcastV2QueuedState();
949
1118
  }
950
1119
  function _sendV2QueuedUnknown(item, binding) {
951
1120
  const serviceFrames = binding.service.unknownDelivery(item.frame.client_request_id);
@@ -962,17 +1131,22 @@ function _sendV2QueuedUnknown(item, binding) {
962
1131
  }
963
1132
  function _expireV2QueuedUserMessages(now) {
964
1133
  const live = [];
1134
+ let changed = false;
965
1135
  for (const item of _v2QueuedUserMessages) {
966
1136
  if (now - item.enqueuedAt <= V2_QUEUED_USER_TTL_MS) {
967
1137
  live.push(item);
968
1138
  continue;
969
1139
  }
1140
+ changed = true;
1141
+ _releaseQueuedImageBytes(item.senderRef, item.imageBytes);
970
1142
  const binding = _v2ActivePeers.get(item.senderRef);
971
1143
  if (binding && item.frame.history_generation === binding.service.generation) {
972
1144
  _sendV2QueuedUnknown(item, binding);
973
1145
  }
974
1146
  }
975
1147
  _v2QueuedUserMessages = live;
1148
+ if (changed)
1149
+ _broadcastV2QueuedState();
976
1150
  }
977
1151
  function _enqueueV2UserMessage(senderRef, frame, correlation) {
978
1152
  const now = Date.now();
@@ -983,15 +1157,24 @@ function _enqueueV2UserMessage(senderRef, frame, correlation) {
983
1157
  return duplicate.payloadFingerprint === payloadFingerprint;
984
1158
  const ownerCount = _v2QueuedUserMessages.filter((item) => item.senderRef === senderRef).length;
985
1159
  if (ownerCount >= MAX_V2_QUEUED_USERS_PER_OWNER || _v2QueuedUserMessages.length >= MAX_V2_QUEUED_USERS) {
986
- return false;
987
- }
1160
+ return frame.images && frame.images.length > 0 ? "rejected" : false;
1161
+ }
1162
+ const imageBytes = _estimateQueuedImageBytes(frame.images);
1163
+ const service = _v2ActivePeers.get(senderRef)?.service;
1164
+ const candidate = { senderRef, frame, enqueuedAt: now };
1165
+ if (imageBytes > 0 && (!service || !_v2QueuedItemFitsFrame(candidate, service)))
1166
+ return "rejected";
1167
+ if (!_reserveQueuedImageBytes(senderRef, imageBytes))
1168
+ return "rejected";
988
1169
  _v2QueuedUserMessages.push({
989
1170
  senderRef,
990
1171
  frame,
991
1172
  correlation: { ...correlation, delivery: "queued" },
992
1173
  enqueuedAt: now,
1174
+ imageBytes,
993
1175
  payloadFingerprint,
994
1176
  });
1177
+ _broadcastV2QueuedState();
995
1178
  return true;
996
1179
  }
997
1180
  function _scheduleV2QueuedUserDrain() {
@@ -1012,6 +1195,8 @@ function _scheduleV2QueuedUserDrain() {
1012
1195
  const item = _v2QueuedUserMessages.shift();
1013
1196
  if (!item)
1014
1197
  return;
1198
+ _releaseQueuedImageBytes(item.senderRef, item.imageBytes);
1199
+ _broadcastV2QueuedState();
1015
1200
  const binding = _v2ActivePeers.get(item.senderRef);
1016
1201
  if (!binding || item.frame.history_generation !== binding.service.generation) {
1017
1202
  _scheduleV2QueuedUserDrain();
@@ -1293,7 +1478,15 @@ function _detachPeerChannel(appPeerId) {
1293
1478
  catch { /* best-effort */ }
1294
1479
  _activePeers.delete(appPeerId);
1295
1480
  _v2ActivePeers.delete(appPeerId);
1296
- _v2QueuedUserMessages = _v2QueuedUserMessages.filter((item) => item.senderRef !== appPeerId);
1481
+ const retainedV2QueuedItems = [];
1482
+ for (const item of _v2QueuedUserMessages) {
1483
+ if (item.senderRef === appPeerId)
1484
+ _releaseQueuedImageBytes(item.senderRef, item.imageBytes);
1485
+ else
1486
+ retainedV2QueuedItems.push(item);
1487
+ }
1488
+ _v2QueuedUserMessages = retainedV2QueuedItems;
1489
+ _broadcastV2QueuedState();
1297
1490
  if (_peerShort === appPeerId.slice(0, 8)) {
1298
1491
  // Pick a different remaining peer for the UX hint, or clear when none.
1299
1492
  const next = _activePeers.keys().next().value ?? _v2ActivePeers.keys().next().value;
@@ -2521,7 +2714,9 @@ const extension = (pi) => {
2521
2714
  // up exactly ONE connection bound to the restored session. Idempotent +
2522
2715
  // best-effort: every step is guarded so a partially-initialised instance
2523
2716
  // (e.g. shutdown lands mid-`_cmdRoot`) tears down without throwing.
2524
- pi.on("session_shutdown", async () => {
2717
+ pi.on("session_shutdown", async (event) => {
2718
+ // Session replacement needs a fresh v2 handshake; process exit is final.
2719
+ const byeReason = event.reason === "quit" ? "shutdown" : "session_replaced";
2525
2720
  // Revoke async authority synchronously, before any teardown await. `_disposed`
2526
2721
  // blocks the outgoing continuation immediately; the root and candidate
2527
2722
  // generations keep queued work stale even if a same-module session_start
@@ -2542,12 +2737,11 @@ const extension = (pi) => {
2542
2737
  // (issue #55). session_start re-binds `_lastEventCtx` for the new session.
2543
2738
  _lastCtx = null;
2544
2739
  _lastEventCtx = null;
2545
- // No bye reason: the process keeps running and the fresh instance re-joins
2546
- // the SAME relay room, so an explicit offline→online flap would be wrong.
2740
+ // Send bye before detaching its channel or closing the shared relay room.
2547
2741
  // Revoke producer/Relay/bridge authority while the global node is still
2548
2742
  // visible, before close() can begin its asynchronous UDS leave.
2549
2743
  if (_state !== "idle") {
2550
- _goIdle();
2744
+ _goIdle(byeReason);
2551
2745
  }
2552
2746
  else {
2553
2747
  _meshNode?.detachBridge();
@@ -3402,7 +3596,7 @@ async function _cmdRevoke(arg, ctx) {
3402
3596
  const legacyChannel = _activePeers.get(peer.runtimeKey);
3403
3597
  const v2Binding = _v2ActivePeers.get(peer.runtimeKey);
3404
3598
  try {
3405
- legacyChannel?.send({ type: "bye", reason: "session_replaced" });
3599
+ legacyChannel?.send({ type: "bye", reason: "peer_stop" });
3406
3600
  }
3407
3601
  catch { /* best-effort */ }
3408
3602
  try {
@@ -3411,7 +3605,7 @@ async function _cmdRevoke(arg, ctx) {
3411
3605
  type: "bye",
3412
3606
  session_id: _currentSessionManager?.getSessionId() ?? "unknown",
3413
3607
  history_generation: v2Binding.service.generation,
3414
- reason: "session_replaced",
3608
+ reason: "peer_stop",
3415
3609
  });
3416
3610
  }
3417
3611
  catch { /* best-effort */ }
@@ -4413,11 +4607,29 @@ export function _routeClientMessageFrom(sender, msg, ctx) {
4413
4607
  switch (msg.type) {
4414
4608
  case "queued_message_set": {
4415
4609
  const text = msg.text.trim();
4416
- if (!text) {
4610
+ const images = msg.images && msg.images.length > 0 ? msg.images : undefined;
4611
+ if (!text && !images) {
4417
4612
  _clearQueuedItems(msg.id);
4418
4613
  break;
4419
4614
  }
4420
- _upsertQueuedItem({ id: msg.id, text, editable: true, created_at: Date.now() });
4615
+ const queued = _enqueuePwaQueuedItem({
4616
+ id: msg.id,
4617
+ text,
4618
+ ...(images ? { images } : {}),
4619
+ editable: true,
4620
+ created_at: Date.now(),
4621
+ ownerRef: sender.getPeerId(),
4622
+ imageBytes: _estimateQueuedImageBytes(images),
4623
+ });
4624
+ if (!queued) {
4625
+ sender.send({
4626
+ type: "error",
4627
+ code: "too_large",
4628
+ in_reply_to: msg.id,
4629
+ message: "Image queue capacity exceeded; retry after queued work drains.",
4630
+ });
4631
+ break;
4632
+ }
4421
4633
  _maybeDrainQueuedItem();
4422
4634
  break;
4423
4635
  }