@epoch-agent/server 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1996,6 +1996,16 @@ declare class SessionHub {
1996
1996
  * 而那一行**本来就在列表里**(列表的另一半来自 DB)。列表没变,不该发。
1997
1997
  */
1998
1998
  notifySessionsChanged(sessionId: string): void;
1999
+ /**
2000
+ * 广播「插件那张表变了」(2026-09-24)。语义和「为什么不带 `sessionId`」全在
2001
+ * protocol 的 `plugins-changed` 上。
2002
+ *
2003
+ * 唯一的调用方是 `index.ts` 里那条对 `runtime.plugins.onChange` 的订阅 ——
2004
+ * 同 {@link notifyTasksChanged},Hub 不认识插件表,它在这条链上只负责发帧。
2005
+ *
2006
+ * ⚠️ **它不发给旁听者**(`observe()` 那一路),判据在 {@link publish} 上。
2007
+ */
2008
+ notifyPluginsChanged(): void;
1999
2009
  /**
2000
2010
  * 收摊:撤掉倒计时、放弃所有挂起的审批、中止所有在跑的回合。
2001
2011
  *
@@ -2929,6 +2939,9 @@ interface PluginEntryView {
2929
2939
  marketplace?: string;
2930
2940
  active: boolean;
2931
2941
  counts?: CountsView;
2942
+ /** 宿主给不给这两枚(2026-09-24)。**runtime 算好的**,这一层原样投影 */
2943
+ updateAllowed: boolean;
2944
+ uninstallAllowed: boolean;
2932
2945
  }
2933
2946
  /** `PluginSearchHit`(runtime)的镜像 */
2934
2947
  interface PluginHitView {
@@ -2972,6 +2985,14 @@ interface PluginHitView {
2972
2985
  };
2973
2986
  sourceType?: WirePluginSourceType;
2974
2987
  installable: boolean;
2988
+ /** 宿主给不给「安装」(2026-09-24)。和 `installable` 是两格,判据在 wire 那一格上 */
2989
+ installAllowed: boolean;
2990
+ /** 卡片菜单(2026-09-24)。runtime 已经收过一遍,`danger` 可缺省 → 这一层补成布尔 */
2991
+ menu: readonly {
2992
+ id: string;
2993
+ label: string;
2994
+ danger?: boolean;
2995
+ }[];
2975
2996
  }
2976
2997
  /** `InstallPreview`(core,经 runtime 原样透出)的镜像 —— 「将安装什么」那一屏的内容 */
2977
2998
  interface PluginPreviewView {
@@ -3212,6 +3233,16 @@ interface PluginControlView {
3212
3233
  * 口径逐字同 {@link uploadEnabled}。
3213
3234
  */
3214
3235
  readonly refreshEnabled: boolean;
3236
+ /**
3237
+ * 「添加」那一枚画不画(2026-09-24)。**runtime 算好的结论**,口径同 {@link uploadEnabled}
3238
+ */
3239
+ readonly localInstallEnabled: boolean;
3240
+ /**
3241
+ * 插件那张表变了就叫一声(2026-09-24)。**这个文件不调它** —— 订阅在 `index.ts`,
3242
+ * 转成一帧 `plugins-changed`(判据在 protocol 那一帧上)。镜像上有它是因为
3243
+ * `ApiContext.runtime.plugins` 就是这份镜像,而那条订阅要从它上面拿。
3244
+ */
3245
+ onChange(listener: () => void): () => void;
3215
3246
  /**
3216
3247
  * 一个压缩包 → 一条已经验过的插件目录路径(2026-09-11,落区那一条)。
3217
3248
  *
package/dist/index.js CHANGED
@@ -597,6 +597,10 @@ var SessionHub = class {
597
597
  if (this.closed) return;
598
598
  this.publish(sessionId, { type: "sessions-changed" });
599
599
  }
600
+ notifyPluginsChanged() {
601
+ if (this.closed) return;
602
+ this.publish(void 0, { type: "plugins-changed" });
603
+ }
600
604
  shutdown() {
601
605
  if (this.closed) return;
602
606
  this.closed = true;
@@ -607,10 +611,15 @@ var SessionHub = class {
607
611
  this.counted.clear();
608
612
  }
609
613
  publish(sessionId, event) {
610
- const frame = { seq: ++this.seq, sessionId, ts: this.clock(), event };
614
+ const frame = {
615
+ seq: ++this.seq,
616
+ ...sessionId === void 0 ? {} : { sessionId },
617
+ ts: this.clock(),
618
+ event
619
+ };
611
620
  this.ring.push(frame);
612
621
  for (const sink of this.sinks) sink(frame);
613
- for (const sink of this.observers) sink(frame);
622
+ if (sessionId !== void 0) for (const sink of this.observers) sink(frame);
614
623
  return frame;
615
624
  }
616
625
  connectionFrame(event) {
@@ -2745,7 +2754,9 @@ function toWirePluginEntry(entry, displayNames) {
2745
2754
  installedAt: entry.installedAt,
2746
2755
  marketplace: entry.marketplace ?? null,
2747
2756
  active: entry.active,
2748
- counts: entry.counts ? toWireCounts(entry.counts) : null
2757
+ counts: entry.counts ? toWireCounts(entry.counts) : null,
2758
+ updateAllowed: entry.updateAllowed,
2759
+ uninstallAllowed: entry.uninstallAllowed
2749
2760
  };
2750
2761
  }
2751
2762
  function toWirePluginHit(hit, installed) {
@@ -2771,7 +2782,14 @@ function toWirePluginHit(hit, installed) {
2771
2782
  homepage: hit.entry.homepage ?? null,
2772
2783
  visibility: hit.entry.visibility ?? null,
2773
2784
  status: hit.entry.status ?? null,
2774
- updateAvailable: mine !== void 0 && !mine.linked && hit.entry.version !== void 0 && hit.entry.version !== mine.version
2785
+ updateAvailable: mine !== void 0 && !mine.linked && hit.entry.version !== void 0 && hit.entry.version !== mine.version,
2786
+ installAllowed: hit.installAllowed,
2787
+ updateAllowed: mine?.updateAllowed ?? false,
2788
+ menu: hit.menu.map((item) => ({
2789
+ id: item.id,
2790
+ label: item.label,
2791
+ danger: item.danger === true
2792
+ }))
2775
2793
  };
2776
2794
  }
2777
2795
  function toWireMarketplace(market) {
@@ -2856,7 +2874,10 @@ function listPlugins(ctx, res, lang) {
2856
2874
  function pluginsPayload(control) {
2857
2875
  const installed = control.list();
2858
2876
  const installedMap = new Map(
2859
- installed.map((p) => [p.name, { version: p.version, linked: p.linked }])
2877
+ installed.map((p) => [
2878
+ p.name,
2879
+ { version: p.version, linked: p.linked, updateAllowed: p.updateAllowed }
2880
+ ])
2860
2881
  );
2861
2882
  const shown = displayNamesOf(control);
2862
2883
  return {
@@ -2866,7 +2887,8 @@ function pluginsPayload(control) {
2866
2887
  pendingRestart: control.pendingRestart,
2867
2888
  uploadEnabled: control.uploadEnabled,
2868
2889
  uploadOptions: control.uploadOptions,
2869
- refreshEnabled: control.refreshEnabled
2890
+ refreshEnabled: control.refreshEnabled,
2891
+ localInstallEnabled: control.localInstallEnabled
2870
2892
  };
2871
2893
  }
2872
2894
  function sendPreview(res, outcome) {
@@ -5270,6 +5292,7 @@ async function createWebServer(opts) {
5270
5292
  const unsubscribeTasks = observeBackgroundTasks((sessionId) => {
5271
5293
  hub.notifyTasksChanged(sessionId);
5272
5294
  });
5295
+ const unsubscribePlugins = opts.runtime.plugins?.onChange(() => hub.notifyPluginsChanged()) ?? (() => void 0);
5273
5296
  let handle = null;
5274
5297
  const server = createServer((req, res) => {
5275
5298
  if (handle) return handle(req, res);
@@ -5280,6 +5303,7 @@ async function createWebServer(opts) {
5280
5303
  const listened = await listen(server, binding.host, binding.port);
5281
5304
  if (!listened.ok) {
5282
5305
  unsubscribeTasks();
5306
+ unsubscribePlugins();
5283
5307
  return listened;
5284
5308
  }
5285
5309
  handle = buildHandler(opts, hub, binding, listened.port, webRoot);
@@ -5293,7 +5317,7 @@ async function createWebServer(opts) {
5293
5317
  ...webRoot === void 0 ? {} : { webRoot },
5294
5318
  observe: (sink, opts2) => hub.observe(sink, opts2 ?? {}),
5295
5319
  snapshot: () => hub.listSessions(),
5296
- close: makeCloser(server, hub, opts.runtime, unsubscribeTasks)
5320
+ close: makeCloser(server, hub, opts.runtime, unsubscribeTasks, unsubscribePlugins)
5297
5321
  }
5298
5322
  };
5299
5323
  }
@@ -5345,11 +5369,12 @@ function describeListenError(err, host, port) {
5345
5369
  }
5346
5370
  return t("web.listen_failed", { host, port, reason: err.message }, void 0);
5347
5371
  }
5348
- function makeCloser(server, hub, runtime, unsubscribeTasks) {
5372
+ function makeCloser(server, hub, runtime, unsubscribeTasks, unsubscribePlugins) {
5349
5373
  let closing = null;
5350
5374
  return () => {
5351
5375
  closing ??= (async () => {
5352
5376
  unsubscribeTasks();
5377
+ unsubscribePlugins();
5353
5378
  hub.shutdown();
5354
5379
  server.closeAllConnections();
5355
5380
  await new Promise((resolve4) => server.close(() => resolve4()));