@qt4798/pi-web 0.14.4 → 0.14.6

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.
Files changed (39) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +22 -22
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/required-server-files.js +1 -1
  5. package/.next/required-server-files.json +1 -1
  6. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  7. package/.next/server/app/_global-error.html +1 -1
  8. package/.next/server/app/_global-error.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  12. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next/server/app/_not-found.html +1 -1
  14. package/.next/server/app/_not-found.rsc +1 -1
  15. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  16. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  18. package/.next/server/app/api/app-update/route.js +1 -1
  19. package/.next/server/app/index.html +1 -1
  20. package/.next/server/app/index.rsc +3 -3
  21. package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
  22. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  23. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  24. package/.next/server/app/page.js +1 -1
  25. package/.next/server/app/page_client-reference-manifest.js +1 -1
  26. package/.next/server/app-paths-manifest.json +22 -22
  27. package/.next/server/chunks/7915.js +1 -1
  28. package/.next/server/middleware-build-manifest.js +1 -1
  29. package/.next/server/pages/404.html +1 -1
  30. package/.next/server/pages/500.html +1 -1
  31. package/.next/static/chunks/app/{page-bcc0e2ed5b5f6035.js → page-54c57e029a68ffd4.js} +1 -1
  32. package/.next/trace +7 -8
  33. package/.next/trace-build +1 -1
  34. package/bin/memory-watchdog.js +135 -0
  35. package/bin/pi-web.js +8 -2
  36. package/bin/watchdog-preload.js +20 -0
  37. package/package.json +1 -1
  38. /package/.next/static/{h9xeO9wlQinP87V3yuXj6 → 7xdY7qoeKsF7B1usdcD8R}/_buildManifest.js +0 -0
  39. /package/.next/static/{h9xeO9wlQinP87V3yuXj6 → 7xdY7qoeKsF7B1usdcD8R}/_ssgManifest.js +0 -0
package/.next/trace-build CHANGED
@@ -1 +1 @@
1
- [{"name":"run-webpack","duration":16322663,"timestamp":87065397042,"id":14,"parentId":1,"tags":{},"startTime":1787935240121,"traceId":"c607530efec7c7b6"},{"name":"run-typescript","duration":3020795,"timestamp":87081721948,"id":869,"parentId":1,"tags":{},"startTime":1787935256446,"traceId":"c607530efec7c7b6"},{"name":"static-check","duration":1663427,"timestamp":87084750021,"id":872,"parentId":1,"tags":{},"startTime":1787935259474,"traceId":"c607530efec7c7b6"},{"name":"static-generation","duration":2207359,"timestamp":87086531616,"id":1056,"parentId":1,"tags":{},"startTime":1787935261256,"traceId":"c607530efec7c7b6"},{"name":"collect-build-traces","duration":16065660,"timestamp":87086414991,"id":1053,"parentId":1,"tags":{},"startTime":1787935261139,"traceId":"c607530efec7c7b6"},{"name":"output-standalone","duration":1490491,"timestamp":87102483639,"id":1064,"parentId":1,"tags":{},"startTime":1787935277208,"traceId":"c607530efec7c7b6"},{"name":"telemetry-flush","duration":15,"timestamp":87103975796,"id":1067,"parentId":1,"tags":{},"startTime":1787935278700,"traceId":"c607530efec7c7b6"},{"name":"next-build","duration":39200258,"timestamp":87064775557,"id":1,"tags":{"buildMode":"default","version":"16.3.1","bundler":"webpack","has-custom-webpack-config":"false","use-build-worker":"true"},"startTime":1787935239500,"traceId":"c607530efec7c7b6"}]
1
+ [{"name":"run-webpack","duration":24268003,"timestamp":171763054687,"id":14,"parentId":1,"tags":{},"startTime":1788019938891,"traceId":"b5c8c000f3d6e835"},{"name":"run-typescript","duration":4511201,"timestamp":171787325982,"id":865,"parentId":1,"tags":{},"startTime":1788019963163,"traceId":"b5c8c000f3d6e835"},{"name":"static-check","duration":3333877,"timestamp":171791847649,"id":868,"parentId":1,"tags":{},"startTime":1788019967685,"traceId":"b5c8c000f3d6e835"},{"name":"static-generation","duration":3878156,"timestamp":171795418581,"id":1052,"parentId":1,"tags":{},"startTime":1788019971256,"traceId":"b5c8c000f3d6e835"},{"name":"collect-build-traces","duration":26656691,"timestamp":171795184673,"id":1049,"parentId":1,"tags":{},"startTime":1788019971022,"traceId":"b5c8c000f3d6e835"},{"name":"output-standalone","duration":2424887,"timestamp":171821843015,"id":1060,"parentId":1,"tags":{},"startTime":1788019997681,"traceId":"b5c8c000f3d6e835"},{"name":"telemetry-flush","duration":34,"timestamp":171824273008,"id":1063,"parentId":1,"tags":{},"startTime":1788020000111,"traceId":"b5c8c000f3d6e835"},{"name":"next-build","duration":62105703,"timestamp":171762167345,"id":1,"tags":{"buildMode":"default","version":"16.3.1","bundler":"webpack","has-custom-webpack-config":"false","use-build-worker":"true"},"startTime":1788019938003,"traceId":"b5c8c000f3d6e835"}]
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+
3
+ // Idle memory watchdog for the next-server process. V8 only returns freed
4
+ // heap to the OS on process exit, so a long-running server's RSS settles at
5
+ // its high-water mark no matter how much GC reclaims internally. This module
6
+ // watches RSS and exits the process once it has stayed above the threshold
7
+ // while no agent session is running; the supervisor (launchd KeepAlive) then
8
+ // starts a fresh process.
9
+
10
+ const DEFAULT_RESTART_MB = 1229;
11
+ const DEFAULT_CHECK_MINUTES = 5;
12
+ const CONSECUTIVE_OVER_LIMIT = 2;
13
+ const STARTUP_GRACE_TICKS = 3;
14
+ const RUNNING_CHECK_TIMEOUT_MS = 5000;
15
+
16
+ function parsePositiveInt(raw, fallback, minimum) {
17
+ const n = Number.parseInt(raw ?? "", 10);
18
+ if (!Number.isFinite(n) || n < minimum) return fallback;
19
+ return n;
20
+ }
21
+
22
+ /**
23
+ * Resolve the watchdog config from env. Returns null when the watchdog must
24
+ * not run: explicitly disabled (PI_WEB_RSS_RESTART_MB=0), running on Windows
25
+ * (no launchd-style supervisor to restart us, and auto-restart is not wanted
26
+ * there), or no port known (without a port we cannot ask /api/agent/running
27
+ * and must never exit).
28
+ */
29
+ function resolveConfig(env) {
30
+ if (process.platform === "win32") return null;
31
+ const rawRestart = env.PI_WEB_RSS_RESTART_MB;
32
+ if (rawRestart !== undefined && rawRestart !== "" && Number(rawRestart) === 0) {
33
+ return null;
34
+ }
35
+ const port = Number.parseInt(env.PI_WEB_PORT ?? "", 10);
36
+ if (!Number.isFinite(port) || port <= 0) return null;
37
+ return {
38
+ port,
39
+ hostname: env.PI_WEB_HOSTNAME || "127.0.0.1",
40
+ restartMb: parsePositiveInt(rawRestart, DEFAULT_RESTART_MB, 1),
41
+ checkMinutes: parsePositiveInt(env.PI_WEB_RSS_CHECK_MINUTES, DEFAULT_CHECK_MINUTES, 1),
42
+ };
43
+ }
44
+
45
+ function basicAuthHeaders(env) {
46
+ // Next.js loads PI_WEB_PASSWORD from .env in production, and the proxy gate
47
+ // rejects unauthenticated /api calls. Reuse the credential this process
48
+ // already holds so the loopback check passes without weakening auth.
49
+ const password = env.PI_WEB_PASSWORD;
50
+ if (!password) return undefined;
51
+ return { authorization: `Basic ${Buffer.from(`pi:${password}`).toString("base64")}` };
52
+ }
53
+
54
+ async function isServerIdle(config, env) {
55
+ const res = await fetch(`http://${config.hostname}:${config.port}/api/agent/running`, {
56
+ headers: basicAuthHeaders(env),
57
+ signal: AbortSignal.timeout(RUNNING_CHECK_TIMEOUT_MS),
58
+ });
59
+ if (!res.ok) return null;
60
+ const data = await res.json();
61
+ const ids = Array.isArray(data && data.runningSessionIds) ? data.runningSessionIds : [];
62
+ return ids.length === 0;
63
+ }
64
+
65
+ /**
66
+ * Start the watchdog loop in the current process. Never throws; failures to
67
+ * check are skipped conservatively (streak reset) so a transient error can
68
+ * never cause an exit.
69
+ */
70
+ function startMemoryWatchdog(log = defaultLog) {
71
+ const config = resolveConfig(process.env);
72
+ if (!config) return null;
73
+
74
+ let ticks = 0;
75
+ let overLimitStreak = 0;
76
+ let loggedNonIdle = false;
77
+ log(`watching RSS every ${config.checkMinutes}min, restart at >= ${config.restartMb}MB when idle (grace: ${STARTUP_GRACE_TICKS} ticks)`);
78
+
79
+ const timer = setInterval(() => {
80
+ ticks += 1;
81
+ const rssMb = process.memoryUsage().rss / (1024 * 1024);
82
+ if (ticks <= STARTUP_GRACE_TICKS || rssMb < config.restartMb) {
83
+ overLimitStreak = 0;
84
+ return;
85
+ }
86
+ isServerIdle(config, process.env)
87
+ .then((idle) => {
88
+ if (idle !== true) {
89
+ // Sessions running, or the check failed — never exit on unknown state.
90
+ if (!loggedNonIdle) {
91
+ loggedNonIdle = true;
92
+ log(`over limit (${Math.round(rssMb)}MB) but idle check says ${idle === null ? "unavailable (check failed)" : "sessions running"}; not restarting`);
93
+ }
94
+ overLimitStreak = 0;
95
+ return;
96
+ }
97
+ overLimitStreak += 1;
98
+ log(`RSS ${Math.round(rssMb)}MB >= ${config.restartMb}MB and idle (${overLimitStreak}/${CONSECUTIVE_OVER_LIMIT})`);
99
+ if (overLimitStreak >= CONSECUTIVE_OVER_LIMIT) {
100
+ clearInterval(timer);
101
+ log("exiting so the supervisor can restart us and reclaim memory");
102
+ process.exit(0);
103
+ }
104
+ })
105
+ .catch((err) => {
106
+ if (!loggedNonIdle) {
107
+ loggedNonIdle = true;
108
+ log(`over limit but idle check errored: ${err && err.message}; not restarting`);
109
+ }
110
+ overLimitStreak = 0;
111
+ });
112
+ }, config.checkMinutes * 60 * 1000);
113
+ // The server itself keeps the process alive; the watchdog must never be the
114
+ // reason a process stays up.
115
+ timer.unref();
116
+ return timer;
117
+ }
118
+
119
+ function defaultLog(message) {
120
+ console.log(`[memory-watchdog] ${message}`);
121
+ }
122
+
123
+ module.exports = { startMemoryWatchdog, resolveConfig };
124
+
125
+ // CLI: `node bin/memory-watchdog.js` runs the loop in-process (smoke tests).
126
+ // The interval is unref'd for server use, so re-ref it to keep this process
127
+ // alive.
128
+ if (require.main === module) {
129
+ const timer = startMemoryWatchdog();
130
+ if (!timer) {
131
+ console.log("[memory-watchdog] disabled (PI_WEB_RSS_RESTART_MB=0 or no PI_WEB_PORT)");
132
+ } else {
133
+ timer.ref();
134
+ }
135
+ }
package/bin/pi-web.js CHANGED
@@ -48,6 +48,12 @@ const { port, hostname, openBrowser } = parseLaunchOptions();
48
48
  const { mergedNodeOptions } = require("./with-memory-limit");
49
49
  process.env.NODE_OPTIONS = mergedNodeOptions(process.env.NODE_OPTIONS);
50
50
 
51
+ // Idle memory watchdog, loaded only into the next-server process. Injected as
52
+ // a node CLI arg (not NODE_OPTIONS) so agent-spawned child processes never
53
+ // inherit it. Disabled with PI_WEB_RSS_RESTART_MB=0; the module itself also
54
+ // refuses to run when it doesn't know the port to ask about running sessions.
55
+ const watchdogPreload = path.join(__dirname, "watchdog-preload.js");
56
+
51
57
  if (!fs.existsSync(nextDir)) {
52
58
  console.error("Build artifacts not found. Please report this issue.");
53
59
  process.exit(1);
@@ -71,10 +77,10 @@ const nextArgs = ["start", "-p", port, "-H", hostname];
71
77
 
72
78
  // Always run next's JS entry with node directly — avoids .bin symlink issues
73
79
  // and path-with-spaces problems on Windows when shell: true is used.
74
- const child = spawn(process.execPath, [nextBin, ...nextArgs], {
80
+ const child = spawn(process.execPath, ["--require", watchdogPreload, nextBin, ...nextArgs], {
75
81
  cwd: pkgDir,
76
82
  stdio: ["inherit", "pipe", "inherit"],
77
- env: { ...process.env, PI_WEB_HOSTNAME: hostname },
83
+ env: { ...process.env, PI_WEB_HOSTNAME: hostname, PI_WEB_PORT: String(port) },
78
84
  });
79
85
  wireChildProcessLifecycle(child);
80
86
 
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ // Loaded into the next-server process via `node --require bin/watchdog-preload.js
4
+ // next start ...` (wired up in pi-web.js). Startup is deferred so a problem
5
+ // here can never delay or break server boot — worst case is one warn line and
6
+ // no watchdog.
7
+
8
+ try {
9
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
10
+ const { startMemoryWatchdog } = require("./memory-watchdog");
11
+ setTimeout(() => {
12
+ try {
13
+ startMemoryWatchdog();
14
+ } catch (err) {
15
+ console.warn(`[memory-watchdog] failed to start: ${err && err.message}`);
16
+ }
17
+ }, 3000).unref();
18
+ } catch (err) {
19
+ console.warn(`[memory-watchdog] preload failed: ${err && err.message}`);
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qt4798/pi-web",
3
- "version": "0.14.4",
3
+ "version": "0.14.6",
4
4
  "description": "Web workspace for the pi coding agent",
5
5
  "homepage": "https://github.com/t479842598/pi-web-QT#readme",
6
6
  "repository": {