@agentdevjs/viewer 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6844,9 +6844,9 @@ var ViewerWorker = class _ViewerWorker {
6844
6844
  reject(err);
6845
6845
  }
6846
6846
  });
6847
- this.server.listen(this.port, async () => {
6848
- const url = `http://localhost:${this.port}`;
6849
- console.log(`[Viewer Worker] ${url}`);
6847
+ this.server.listen(this.port, "127.0.0.1", async () => {
6848
+ const url = `http://127.0.0.1:${this.port}`;
6849
+ console.log(`[Viewer Worker] ${url} (loopback only)`);
6850
6850
  console.log(`[Viewer Worker] MCP endpoint: ${url}/mcp`);
6851
6851
  if (this.openBrowser) {
6852
6852
  try {
@@ -7037,7 +7037,7 @@ var ViewerWorker = class _ViewerWorker {
7037
7037
  }
7038
7038
  const msgMatch = url.match(/^\/api\/agents\/([^/]+)\/messages$/);
7039
7039
  if (msgMatch && req.method === "GET") {
7040
- this.handleGetAgentMessages(req, res, msgMatch[1]);
7040
+ this.handleGetAgentMessages(req, res, msgMatch[1], urlObj.searchParams);
7041
7041
  return;
7042
7042
  }
7043
7043
  const toolsMatch = url.match(/^\/api\/agents\/([^/]+)\/tools$/);
@@ -7197,20 +7197,46 @@ var ViewerWorker = class _ViewerWorker {
7197
7197
  }
7198
7198
  }
7199
7199
  }
7200
- } catch (e) {
7200
+ } catch {
7201
7201
  }
7202
7202
  return null;
7203
7203
  }
7204
7204
  /**
7205
7205
  * GET /api/agents/:id/messages - 获取指定 Agent 的消息
7206
+ *
7207
+ * 增量取数(ADR-0012):?since=<n> 返回自下标 n 起的尾部切片(附 baseCount);
7208
+ * ?tail=1 返回最后一条。无参数路径的响应形状与历史完全一致(全量数组)。
7206
7209
  */
7207
- handleGetAgentMessages(req, res, agentId) {
7210
+ handleGetAgentMessages(req, res, agentId, searchParams) {
7208
7211
  const session = this.agentSessions.get(agentId);
7209
7212
  if (!session) {
7210
7213
  res.writeHead(404);
7211
7214
  res.end(JSON.stringify({ error: "Agent not found" }));
7212
7215
  return;
7213
7216
  }
7217
+ const since = searchParams?.get("since");
7218
+ if (since !== null && since !== void 0) {
7219
+ const n = Number.parseInt(since, 10);
7220
+ if (Number.isNaN(n) || n < 0) {
7221
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
7222
+ res.end(JSON.stringify({ error: "Invalid since parameter" }));
7223
+ return;
7224
+ }
7225
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
7226
+ res.end(JSON.stringify({
7227
+ messages: session.messages.slice(n),
7228
+ baseCount: n
7229
+ }));
7230
+ return;
7231
+ }
7232
+ if (searchParams?.get("tail") === "1") {
7233
+ const last = session.messages.length > 0 ? session.messages[session.messages.length - 1] : null;
7234
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
7235
+ res.end(JSON.stringify({
7236
+ messages: last ? [last] : []
7237
+ }));
7238
+ return;
7239
+ }
7214
7240
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
7215
7241
  res.end(JSON.stringify({
7216
7242
  agentId,
@@ -7252,7 +7278,9 @@ var ViewerWorker = class _ViewerWorker {
7252
7278
  return;
7253
7279
  }
7254
7280
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
7255
- res.end(JSON.stringify(this.getMergedOverview(session)));
7281
+ const overview = this.getMergedOverview(session);
7282
+ const probe = this.getMessagesProbe(session);
7283
+ res.end(JSON.stringify(probe ? { ...overview, _messagesProbe: probe } : overview));
7256
7284
  }
7257
7285
  handleGetAgentTodoPlan(req, res, agentId) {
7258
7286
  const session = this.agentSessions.get(agentId);
@@ -7438,7 +7466,7 @@ var ViewerWorker = class _ViewerWorker {
7438
7466
  delete session.inputLease;
7439
7467
  res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
7440
7468
  res.end(JSON.stringify({ success: true }));
7441
- } catch (e) {
7469
+ } catch {
7442
7470
  res.writeHead(400);
7443
7471
  res.end(JSON.stringify({ error: "Invalid request" }));
7444
7472
  }
@@ -7759,17 +7787,35 @@ var ViewerWorker = class _ViewerWorker {
7759
7787
  }
7760
7788
  /**
7761
7789
  * 应用内存限制
7790
+ *
7791
+ * 字节限制基于增量缓存 _totalBytes(推送时维护,修剪时扣减):未超限
7792
+ * 零序列化开销;未初始化(旧路径未走过推送)时先全量求和建立基线。
7793
+ * 切点语义与原实现一致:超出 MAX_BYTES 时保留首个超限条目之后的尾部。
7762
7794
  */
7763
7795
  enforceMemoryLimits(session) {
7796
+ const store = session;
7764
7797
  while (session.messages.length > this.MAX_MESSAGES) {
7765
- session.messages.shift();
7798
+ const removed = session.messages.shift();
7799
+ if (typeof store._totalBytes === "number") {
7800
+ store._totalBytes -= JSON.stringify(removed).length;
7801
+ }
7766
7802
  }
7767
- let byteSize = 0;
7768
- for (let i = 0; i < session.messages.length; i++) {
7769
- byteSize += JSON.stringify(session.messages[i]).length;
7770
- if (byteSize > this.MAX_BYTES) {
7771
- session.messages = session.messages.slice(i + 1);
7772
- break;
7803
+ if (typeof store._totalBytes !== "number") {
7804
+ let total = 0;
7805
+ for (let i = 0; i < session.messages.length; i++) {
7806
+ total += JSON.stringify(session.messages[i]).length;
7807
+ }
7808
+ store._totalBytes = total;
7809
+ }
7810
+ if (store._totalBytes > this.MAX_BYTES) {
7811
+ let byteSize = 0;
7812
+ for (let i = 0; i < session.messages.length; i++) {
7813
+ byteSize += JSON.stringify(session.messages[i]).length;
7814
+ if (byteSize > this.MAX_BYTES) {
7815
+ session.messages = session.messages.slice(i + 1);
7816
+ store._totalBytes -= byteSize;
7817
+ break;
7818
+ }
7773
7819
  }
7774
7820
  }
7775
7821
  }
@@ -7778,7 +7824,7 @@ var ViewerWorker = class _ViewerWorker {
7778
7824
  * 处理注册 Agent
7779
7825
  */
7780
7826
  handleRegisterAgent(msg, clientId) {
7781
- const { agentId, name, createdAt, projectRoot, templateMounts, templateEntries, hookInspector, overview, activeInputRequest, inputPolicy } = msg;
7827
+ const { agentId, name, projectRoot, templateMounts, templateEntries, hookInspector, overview, activeInputRequest, inputPolicy } = msg;
7782
7828
  const session = this.getOrCreateSession(agentId, name);
7783
7829
  if (inputPolicy === "none") {
7784
7830
  session.inputPolicy = "none";
@@ -7887,18 +7933,33 @@ var ViewerWorker = class _ViewerWorker {
7887
7933
  /**
7888
7934
  * 处理推送消息(带去重优化)
7889
7935
  *
7890
- * 只有在消息真正变化时才更新会话并触发前端更新
7936
+ * 只有在消息真正变化时才更新会话并触发前端更新。推送时刻新旧数组都在手上,
7937
+ * 顺带完成变更分类(ADR-0012)并增量维护总字节缓存;分类为 rewrite 时
7938
+ * 照常更新 session.messages——修正"中段变化但 count 与末条签名均不变"
7939
+ * 被静默丢弃的盲区。
7891
7940
  */
7892
7941
  handlePushMessages(msg) {
7893
7942
  const { agentId, messages } = msg;
7894
7943
  const session = this.agentSessions.get(agentId);
7895
7944
  if (!session) return;
7896
- const messagesChanged = this.hasMessagesChanged(session, messages);
7897
- if (messagesChanged) {
7945
+ const oldMessages = session.messages;
7946
+ const change = this.classifyMessagesChange(oldMessages, messages);
7947
+ if (change) {
7898
7948
  session.messages = messages;
7949
+ const store = session;
7950
+ store._messagesChangeSeq = (typeof store._messagesChangeSeq === "number" ? store._messagesChangeSeq : 0) + 1;
7951
+ this.updateTotalBytes(session, oldMessages, messages, change.changeKind);
7899
7952
  session._lastMessageSig = this.getLastMessageSignature(messages);
7900
7953
  this.updateSessionActivity(agentId);
7954
+ const preTrimCount = messages.length;
7901
7955
  this.enforceMemoryLimits(session);
7956
+ const trimmed = session.messages.length < preTrimCount;
7957
+ store._messagesChange = {
7958
+ changeKind: trimmed ? "rewrite" : change.changeKind,
7959
+ sinceIndex: trimmed ? 0 : change.sinceIndex,
7960
+ fakeFullBytes: store._totalBytes
7961
+ };
7962
+ } else if (typeof session._totalBytes !== "number") {
7902
7963
  }
7903
7964
  }
7904
7965
  createEmptyOverview() {
@@ -8031,18 +8092,78 @@ var ViewerWorker = class _ViewerWorker {
8031
8092
  };
8032
8093
  }
8033
8094
  /**
8034
- * 检查消息是否发生变化
8095
+ * 变更分类(ADR-0012,推送时刻新旧数组都在手上):
8096
+ * - append:新数组以旧数组为前缀且变长(sinceIndex = 旧数组长度)
8097
+ * - tail:条数不变,仅最后一条不同(流式输出)
8098
+ * - rewrite:其余(中段替换 / 条数减少,含 count 与末条签名均不变的盲区)
8099
+ * 返回 null 表示未变化。
8035
8100
  */
8036
- hasMessagesChanged(session, newMessages) {
8037
- if (newMessages.length !== session.messages.length) {
8038
- return true;
8101
+ classifyMessagesChange(oldMessages, newMessages) {
8102
+ const oldLen = oldMessages.length;
8103
+ const newLen = newMessages.length;
8104
+ if (newLen > oldLen && this.isSameMessagePrefix(oldMessages, newMessages, oldLen)) {
8105
+ return { changeKind: "append", sinceIndex: oldLen };
8106
+ }
8107
+ if (newLen === oldLen) {
8108
+ if (newLen === 0) return null;
8109
+ const lastSigChanged = this.getLastMessageSignature(newMessages) !== this.getLastMessageSignature(oldMessages);
8110
+ const prefixLen = lastSigChanged ? newLen - 1 : newLen;
8111
+ if (this.isSameMessagePrefix(oldMessages, newMessages, prefixLen)) {
8112
+ return lastSigChanged ? { changeKind: "tail", sinceIndex: newLen - 1 } : null;
8113
+ }
8114
+ return { changeKind: "rewrite", sinceIndex: 0 };
8039
8115
  }
8040
- if (newMessages.length === 0) {
8041
- return false;
8116
+ return { changeKind: "rewrite", sinceIndex: 0 };
8117
+ }
8118
+ /**
8119
+ * 判断两数组前 len 条是否逐条一致:先比引用(运行时对未变化条目通常
8120
+ * 复用同一对象),不一致再退回 JSON 全等(消息对象可能被重建)。
8121
+ */
8122
+ isSameMessagePrefix(oldMessages, newMessages, len) {
8123
+ for (let i = 0; i < len; i++) {
8124
+ if (oldMessages[i] === newMessages[i]) continue;
8125
+ if (JSON.stringify(oldMessages[i]) !== JSON.stringify(newMessages[i])) return false;
8126
+ }
8127
+ return true;
8128
+ }
8129
+ /**
8130
+ * 消息探测数据(/overview HTTP 组装用,ADR-0012)。count 取当前消息数,
8131
+ * 其余为最近一次推送记录下的分类与假想全量字节;未走过推送路径
8132
+ * (_totalBytes 未初始化)时返回 null,调用方据此整体省略 _messagesProbe。
8133
+ */
8134
+ getMessagesProbe(session) {
8135
+ const change = session._messagesChange;
8136
+ if (!change || typeof session._totalBytes !== "number") return null;
8137
+ return {
8138
+ seq: session._messagesChangeSeq || 0,
8139
+ count: session.messages.length,
8140
+ changeKind: change.changeKind,
8141
+ sinceIndex: change.sinceIndex,
8142
+ fakeFullBytes: change.fakeFullBytes
8143
+ };
8144
+ }
8145
+ /**
8146
+ * 增量维护 session 总字节缓存(_totalBytes = 全量响应体假想字节数):
8147
+ * append 只序列化新增条目,tail 只序列化末条新旧两份;rewrite(rollback /
8148
+ * compact 等低频路径)与未初始化基线时全量重建。
8149
+ */
8150
+ updateTotalBytes(session, oldMessages, newMessages, changeKind) {
8151
+ const store = session;
8152
+ if (typeof store._totalBytes !== "number" || changeKind === "rewrite") {
8153
+ let total = 0;
8154
+ for (let i = 0; i < newMessages.length; i++) {
8155
+ total += JSON.stringify(newMessages[i]).length;
8156
+ }
8157
+ store._totalBytes = total;
8158
+ return;
8159
+ }
8160
+ if (changeKind === "append") {
8161
+ for (let i = oldMessages.length; i < newMessages.length; i++) {
8162
+ store._totalBytes += JSON.stringify(newMessages[i]).length;
8163
+ }
8164
+ return;
8042
8165
  }
8043
- const newSig = this.getLastMessageSignature(newMessages);
8044
- const oldSig = session._lastMessageSig;
8045
- return newSig !== oldSig;
8166
+ store._totalBytes += JSON.stringify(newMessages[newMessages.length - 1]).length - JSON.stringify(oldMessages[oldMessages.length - 1]).length;
8046
8167
  }
8047
8168
  /**
8048
8169
  * 获取最后一条消息的签名(用于变化检测)
@@ -8565,4 +8686,4 @@ export {
8565
8686
  generateViewerHtml,
8566
8687
  ViewerWorker
8567
8688
  };
8568
- //# sourceMappingURL=chunk-OPBQEOJC.js.map
8689
+ //# sourceMappingURL=chunk-OVQA4A62.js.map