pi-webapp 0.1.12 → 0.1.14
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/README.md +2 -0
- package/README.zh-CN.md +2 -0
- package/bin/pi-webapp.mjs +29 -8
- package/dist/extension.js +17 -17
- package/docs/sentry.md +4 -0
- package/package.json +2 -2
- package/web/dist/.vite/manifest.json +17 -17
- package/web/dist/assets/{app-Boh5DRzv.js → app-COe0narp.js} +5 -5
- package/web/dist/assets/app.js +2 -2
- package/web/dist/assets/{graphql-IPoCyjbx.js → graphql-BNu6SI9R.js} +1 -1
- package/web/dist/assets/{http-k59Hanmf.js → http-DiAxPfIa.js} +1 -1
- package/web/dist/assets/{php-BG9P_ECJ.js → php-Nt75L53G.js} +1 -1
- package/web/dist/assets/{rst-Rxh6XweC.js → rst-Lhsfs6gm.js} +1 -1
- package/web/dist/assets/{ruby-vsmpeItB.js → ruby-Cs7cjW_-.js} +1 -1
- package/web/dist/assets/{svelte-BbsEXeJC.js → svelte-CnEWAvNs.js} +1 -1
- package/web/dist/assets/{vue-DgFjFt96.js → vue-DELTyRQi.js} +1 -1
package/README.md
CHANGED
|
@@ -46,6 +46,8 @@ In Pi, enter `/web` to open the interface in your default browser. Pi also print
|
|
|
46
46
|
|
|
47
47
|
If you enter `/web` manually in a Pi terminal, that terminal session stays synchronized with the browser. With the launcher, Pi runs in the background. Sessions opened in other workspaces use the Pi SDK and load that workspace's skills and project context. They load enabled Web plugin Pi entries explicitly while keeping other Pi extensions isolated from the parent bridge.
|
|
48
48
|
|
|
49
|
+
Chats run independently: switching workspaces or chats, or creating a new chat, leaves existing tasks running in the background. Returning to a chat restores its running output, history, and pending plugin interactions. Stop affects only the selected chat. Sessions remain in memory until the Pi service stops; stopping or restarting the service ends background tasks.
|
|
50
|
+
|
|
49
51
|
On startup, the app discovers workspaces from Pi's saved sessions, so conversations from other directories appear in the sidebar. Workspace records and attachments stay under Pi's existing `pi-web/` data directory. Removing a workspace from the sidebar does not delete its files or sessions; you can add it again manually.
|
|
50
52
|
|
|
51
53
|
## Develop locally
|
package/README.zh-CN.md
CHANGED
|
@@ -46,6 +46,8 @@ pi
|
|
|
46
46
|
|
|
47
47
|
如果通过 Pi 终端手动输入 `/web`,该终端会话会与浏览器保持同步。使用启动器时,Pi 在后台运行。在其他工作区打开的会话使用 Pi SDK,并加载该工作区的技能和项目上下文;已启用的 Web 插件 Pi 入口会显式加载,其他普通 Pi 扩展仍与主会话隔离。
|
|
48
48
|
|
|
49
|
+
每个聊天独立运行:切换工作区、切换聊天或新建聊天时,原任务会继续在后台执行。切回后会恢复正在输出的内容、历史记录和待处理的插件交互。“停止”只影响当前选中的聊天。会话实例会保留到 Pi 服务停止;停止或重启服务会结束后台任务。
|
|
50
|
+
|
|
49
51
|
启动时会从 Pi 已保存的会话中发现工作区,因此先前在其他目录使用 Pi 的会话也会出现在侧边栏。工作区记录和附件继续存放在 Pi 现有的 `pi-web/` 数据目录下。从侧边栏移除工作区不会删除其文件或会话;移除后如需再次显示,可以手动添加。
|
|
50
52
|
|
|
51
53
|
## 本地开发
|
package/bin/pi-webapp.mjs
CHANGED
|
@@ -131,26 +131,43 @@ async function serve(pi, args) {
|
|
|
131
131
|
let runningUrl = null;
|
|
132
132
|
let buffer = "";
|
|
133
133
|
let killTimer;
|
|
134
|
+
let stopReason;
|
|
135
|
+
let forceStopRequested = false;
|
|
136
|
+
const instanceId = randomUUID();
|
|
137
|
+
const startedAt = performance.now();
|
|
138
|
+
const lifecycle = (event, fields = {}) => {
|
|
139
|
+
try {
|
|
140
|
+
console.error(JSON.stringify({ timestamp: new Date().toISOString(), event,
|
|
141
|
+
level: event === "pi_process_exit" && (!stopping || failed) ? "error" : "info",
|
|
142
|
+
instanceId, pid: process.pid, piPid: child.pid, uptimeMs: Math.round(performance.now() - startedAt), ...fields }));
|
|
143
|
+
} catch { /* Diagnostics must not interrupt process supervision. */ }
|
|
144
|
+
};
|
|
134
145
|
const devWatchers = [];
|
|
135
146
|
let devReloadTimer;
|
|
136
147
|
let devReloadCount = 0;
|
|
137
148
|
let devReloadCommand;
|
|
138
|
-
const shutdown = () => {
|
|
149
|
+
const shutdown = (reason) => {
|
|
139
150
|
if (stopping) return;
|
|
140
151
|
stopping = true;
|
|
152
|
+
stopReason = reason;
|
|
153
|
+
lifecycle("launcher_stop_requested", { reason });
|
|
141
154
|
child.stdin?.end();
|
|
142
|
-
killTimer = setTimeout(() =>
|
|
155
|
+
killTimer = setTimeout(() => {
|
|
156
|
+
forceStopRequested = true;
|
|
157
|
+
lifecycle("pi_force_stop", { reason, signal: "SIGTERM" });
|
|
158
|
+
child.kill("SIGTERM");
|
|
159
|
+
}, 5000);
|
|
143
160
|
killTimer.unref();
|
|
144
161
|
};
|
|
145
162
|
const fail = (message) => {
|
|
146
163
|
if (failed || ready) return;
|
|
147
164
|
failed = true;
|
|
148
165
|
notifyParent({ type: "error", message });
|
|
149
|
-
shutdown();
|
|
166
|
+
shutdown("startup_failure");
|
|
150
167
|
};
|
|
151
168
|
const startupTimer = setTimeout(() => fail(`Pi 启动超时。日志:${logPath}`), 60000);
|
|
152
169
|
const stopTimer = setInterval(async () => {
|
|
153
|
-
try { if ((await readFile(stopPath, "utf8")).trim() === nonce) shutdown(); }
|
|
170
|
+
try { if ((await readFile(stopPath, "utf8")).trim() === nonce) shutdown("stop_request"); }
|
|
154
171
|
catch { /* No stop request. */ }
|
|
155
172
|
if (stopping) return;
|
|
156
173
|
try {
|
|
@@ -158,12 +175,12 @@ async function serve(pi, args) {
|
|
|
158
175
|
const prepared = JSON.parse(await readFile(updatePath, "utf8"));
|
|
159
176
|
if (request.nonce === nonce && request.launcher === prepared.launcher && existsSync(request.launcher)) {
|
|
160
177
|
restartRequest = request;
|
|
161
|
-
shutdown();
|
|
178
|
+
shutdown("restart_request");
|
|
162
179
|
}
|
|
163
180
|
} catch { /* No valid restart request. */ }
|
|
164
181
|
}, 400);
|
|
165
|
-
process.on("SIGTERM", shutdown);
|
|
166
|
-
process.on("SIGINT", shutdown);
|
|
182
|
+
process.on("SIGTERM", () => shutdown("signal_sigterm"));
|
|
183
|
+
process.on("SIGINT", () => shutdown("signal_sigint"));
|
|
167
184
|
child.stdout?.on("data", (chunk) => {
|
|
168
185
|
if (failed) return;
|
|
169
186
|
buffer += chunk.toString("utf8");
|
|
@@ -210,6 +227,7 @@ async function serve(pi, args) {
|
|
|
210
227
|
if (closed || failed) return;
|
|
211
228
|
ready = true;
|
|
212
229
|
clearTimeout(startupTimer);
|
|
230
|
+
lifecycle("launcher_ready", { port: Number(url.port) });
|
|
213
231
|
notifyParent({ type: "ready", pid: process.pid, url: url.href });
|
|
214
232
|
for (const root of pluginRoots) {
|
|
215
233
|
try {
|
|
@@ -233,8 +251,11 @@ async function serve(pi, args) {
|
|
|
233
251
|
child.stdin?.write(JSON.stringify({ id: "list-commands", type: "get_commands" }) + "\n");
|
|
234
252
|
await new Promise((resolveExit) => {
|
|
235
253
|
child.once("error", (error) => { fail(`无法启动 Pi:${error.message}`); resolveExit(); });
|
|
236
|
-
child.once("close", (code) => {
|
|
254
|
+
child.once("close", (code, signal) => {
|
|
237
255
|
closed = true;
|
|
256
|
+
lifecycle("pi_process_exit", { ready, exitCode: code, signal,
|
|
257
|
+
reason: stopReason ?? (ready ? "unexpected_exit" : "startup_failure"), forceStopRequested,
|
|
258
|
+
port: runningUrl ? Number(new URL(runningUrl).port) : null });
|
|
238
259
|
if (!ready) fail(`Pi 提前退出(代码 ${code ?? "未知"})。日志:${logPath}`);
|
|
239
260
|
resolveExit();
|
|
240
261
|
});
|