pi-web-ui 0.34.3 → 0.35.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.
@@ -2212,8 +2212,14 @@ export class ClientSession {
2212
2212
  * keeps everything up to (but not including) that question, then sends the
2213
2213
  * edited text there. The original thread is untouched and stays in the
2214
2214
  * session list, so nothing is ever lost.
2215
+ *
2216
+ * Attachments (attachments) travel through the SAME pipeline as prompt()
2217
+ * — the fork intentionally drops the original attachment asides because
2218
+ * they live on the old branch past the fork point, so the browser re-sends
2219
+ * the images it kept in the edit composer (original image blocks + any
2220
+ * newly pasted/dropped ones). Text-only edits pass undefined.
2215
2221
  */
2216
- async editMessage(messageId, text) {
2222
+ async editMessage(messageId, text, attachments) {
2217
2223
  if (this.quiesceBlocked())
2218
2224
  return;
2219
2225
  const trimmed = text.trim();
@@ -2248,7 +2254,7 @@ export class ClientSession {
2248
2254
  return;
2249
2255
  }
2250
2256
  await this.bindSession();
2251
- await this.prompt(trimmed);
2257
+ await this.prompt(trimmed, attachments);
2252
2258
  this.emit({
2253
2259
  type: "notice",
2254
2260
  level: "info",
@@ -521,7 +521,7 @@ wss.on("connection", (ws) => {
521
521
  void cs.newChat();
522
522
  break;
523
523
  case "edit_message":
524
- void cs.editMessage(msg.messageId, msg.text);
524
+ void cs.editMessage(msg.messageId, msg.text, msg.attachments);
525
525
  break;
526
526
  case "cycle_model":
527
527
  void cs.cycleModel();
@@ -748,7 +748,12 @@ wss.on("connection", (ws) => {
748
748
  // freshly dropped plugins show up without a server restart.
749
749
  pluginMgr
750
750
  .ensureLoaded()
751
- .then((plugins) => send({ type: "plugins", plugins, epoch: pluginMgr.epoch }))
751
+ .then((plugins) => {
752
+ send({ type: "plugins", plugins, epoch: pluginMgr.epoch });
753
+ // 让各插件向新接入的客户端推送自身初始状态(onAttach 钩子)——
754
+ // 插件不要依赖客户端挂载后自己拉(见 plugins.ts onAttach 注释)。
755
+ pluginMgr.notifyAttach(cid);
756
+ })
752
757
  .catch(() => { });
753
758
  // Replay anything that arrived while the session was starting.
754
759
  const queued = pending;
@@ -101,12 +101,33 @@ export class PluginManager {
101
101
  this.deliverAll({ type: "plugins", plugins: list, epoch: this.epochCounter });
102
102
  }
103
103
  /** 服务端热重载:反激活全部 → 清缓存 → 重扫重激活 → epoch+1。
104
- * 返回新目录清单(含激活结果)。 */
104
+ * 返回新目录清单(含激活结果)。重激活后的插件实例是新模块,
105
+ * 内存状态为初始值——逐个客户端触发 onAttach 让它们重推自身状态。 */
105
106
  async reload() {
106
107
  this.dispose();
107
108
  this.attempted.clear();
108
109
  this.epochCounter += 1;
109
- return this.ensureLoaded();
110
+ const list = await this.ensureLoaded();
111
+ for (const s of this.senders) {
112
+ const cid = s.cid();
113
+ if (cid)
114
+ this.notifyAttach(cid);
115
+ }
116
+ return list;
117
+ }
118
+ /** 每个客户端 attach 后调用:让各插件向该客户端推送自身完整状态。
119
+ * 异常隔离——单个插件钩子报错不影响其他插件与其他钩子。 */
120
+ notifyAttach(clientId) {
121
+ for (const [id, p] of this.loaded) {
122
+ for (const h of p.attachHandlers) {
123
+ try {
124
+ h(clientId);
125
+ }
126
+ catch (err) {
127
+ console.error(`[plugin:${id}] onAttach handler failed:`, err);
128
+ }
129
+ }
130
+ }
110
131
  }
111
132
  /** agent-service 调:把 SDK 工具执行事件扇出给所有插件(异常隔离)。 */
112
133
  emitToolEvent(ev) {
@@ -265,6 +286,19 @@ export class PluginManager {
265
286
  icon: typeof m.icon === "string" && m.icon.trim() ? m.icon.trim() : undefined,
266
287
  hasClient: existsSync(join(dir, "client", "entry.mjs")),
267
288
  error: this.loaded.get(name)?.info.error,
289
+ // 安装来源(pi-web-ui install 写入的 .pi-source.json)——
290
+ // 设置面板据此显示「更新」按钮;手工拷入的插件没有此文件。
291
+ source: await readFile(join(dir, ".pi-source.json"), "utf8")
292
+ .then((raw) => {
293
+ try {
294
+ const s = JSON.parse(raw);
295
+ return typeof s.source === "string" && s.source ? s.source : undefined;
296
+ }
297
+ catch {
298
+ return undefined;
299
+ }
300
+ })
301
+ .catch(() => undefined),
268
302
  });
269
303
  }
270
304
  catch {
@@ -279,7 +313,9 @@ export class PluginManager {
279
313
  const handlers = new Set();
280
314
  this.messageHandlers.set(info.id, handlers);
281
315
  const toolHandlers = new Set();
316
+ const attachHandlers = new Set();
282
317
  const unregisterTools = [];
318
+ const p = { info, toolHandlers, attachHandlers };
283
319
  const host = {
284
320
  broadcast: (payload) => this.broadcast(info.id, payload),
285
321
  notify: (level, text) => this.notifyAll(level, text),
@@ -292,6 +328,10 @@ export class PluginManager {
292
328
  toolHandlers.add(h);
293
329
  return () => toolHandlers.delete(h);
294
330
  },
331
+ onAttach: (h) => {
332
+ attachHandlers.add(h);
333
+ return () => attachHandlers.delete(h);
334
+ },
295
335
  // 包一层:插件反激活时自动注销它注册的全部 AI 工具,不留悬挂项。
296
336
  registerAgentTool: (tool) => {
297
337
  const off = this.registerAgentTool(info.id, tool);
@@ -317,6 +357,7 @@ export class PluginManager {
317
357
  info: { ...info },
318
358
  deactivate: typeof ret === "function" ? ret : undefined,
319
359
  toolHandlers,
360
+ attachHandlers,
320
361
  agentToolUnsubscribers: unregisterTools,
321
362
  });
322
363
  console.log(`[plugin:${info.id}] activated (v${info.version ?? "?"})`);
@@ -325,6 +366,7 @@ export class PluginManager {
325
366
  this.loaded.set(info.id, {
326
367
  info: { ...info, error: err.message },
327
368
  toolHandlers,
369
+ attachHandlers,
328
370
  });
329
371
  console.error(`[plugin:${info.id}] activate failed:`, err);
330
372
  }
@@ -8,4 +8,4 @@
8
8
  * its own copy in web/src/protocol-version.ts; scripts/check-protocol-sync.mjs
9
9
  * verifies the two never drift.
10
10
  */
11
- export const PROTOCOL_VERSION = 7;
11
+ export const PROTOCOL_VERSION = 9;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-web-ui",
3
- "version": "0.34.3",
3
+ "version": "0.35.1",
4
4
  "description": "Web chat interface for the pi coding agent, powered by the pi SDK (@earendil-works/pi-coding-agent) — one-command run, Docker/systemd/launchd deployable",
5
5
  "license": "MIT",
6
6
  "type": "module",