pi-forge 0.0.0 → 1.1.4

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 (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +48 -4
  3. package/bin/pi-forge.mjs +37 -0
  4. package/dist/client/assets/CodeMirrorEditor-BqaaP1EE.js +34 -0
  5. package/dist/client/assets/CodeMirrorEditor-BqaaP1EE.js.map +1 -0
  6. package/dist/client/assets/index-B-529kgJ.css +32 -0
  7. package/dist/client/assets/index-BzKzxXFs.js +392 -0
  8. package/dist/client/assets/index-BzKzxXFs.js.map +1 -0
  9. package/dist/client/assets/workbox-window.prod.es5-BBnX5xw4.js +3 -0
  10. package/dist/client/assets/workbox-window.prod.es5-BBnX5xw4.js.map +1 -0
  11. package/dist/client/icons/icon-192.png +0 -0
  12. package/dist/client/icons/icon-512.png +0 -0
  13. package/dist/client/icons/icon-maskable-512.png +0 -0
  14. package/dist/client/icons/icon.svg +9 -0
  15. package/dist/client/index.html +24 -0
  16. package/dist/client/manifest.webmanifest +1 -0
  17. package/dist/client/offline.html +142 -0
  18. package/dist/client/sw.js +3 -0
  19. package/dist/client/sw.js.map +1 -0
  20. package/dist/client/workbox-6d7155ed.js +3 -0
  21. package/dist/client/workbox-6d7155ed.js.map +1 -0
  22. package/dist/server/agent-resource-loader.js +126 -0
  23. package/dist/server/agent-resource-loader.js.map +1 -0
  24. package/dist/server/attachment-converters.js +96 -0
  25. package/dist/server/attachment-converters.js.map +1 -0
  26. package/dist/server/auth.js +209 -0
  27. package/dist/server/auth.js.map +1 -0
  28. package/dist/server/compaction-history.js +106 -0
  29. package/dist/server/compaction-history.js.map +1 -0
  30. package/dist/server/concurrency.js +49 -0
  31. package/dist/server/concurrency.js.map +1 -0
  32. package/dist/server/config-export.js +220 -0
  33. package/dist/server/config-export.js.map +1 -0
  34. package/dist/server/config-manager.js +528 -0
  35. package/dist/server/config-manager.js.map +1 -0
  36. package/dist/server/config.js +326 -0
  37. package/dist/server/config.js.map +1 -0
  38. package/dist/server/conversion-worker.mjs +90 -0
  39. package/dist/server/diagnostics.js +137 -0
  40. package/dist/server/diagnostics.js.map +1 -0
  41. package/dist/server/extensions-discovery.js +147 -0
  42. package/dist/server/extensions-discovery.js.map +1 -0
  43. package/dist/server/file-manager.js +734 -0
  44. package/dist/server/file-manager.js.map +1 -0
  45. package/dist/server/file-references.js +215 -0
  46. package/dist/server/file-references.js.map +1 -0
  47. package/dist/server/file-searcher.js +385 -0
  48. package/dist/server/file-searcher.js.map +1 -0
  49. package/dist/server/git-runner.js +684 -0
  50. package/dist/server/git-runner.js.map +1 -0
  51. package/dist/server/index.js +468 -0
  52. package/dist/server/index.js.map +1 -0
  53. package/dist/server/mcp/config.js +133 -0
  54. package/dist/server/mcp/config.js.map +1 -0
  55. package/dist/server/mcp/manager.js +351 -0
  56. package/dist/server/mcp/manager.js.map +1 -0
  57. package/dist/server/mcp/tool-bridge.js +173 -0
  58. package/dist/server/mcp/tool-bridge.js.map +1 -0
  59. package/dist/server/project-manager.js +301 -0
  60. package/dist/server/project-manager.js.map +1 -0
  61. package/dist/server/pty-manager.js +354 -0
  62. package/dist/server/pty-manager.js.map +1 -0
  63. package/dist/server/routes/_schemas.js +73 -0
  64. package/dist/server/routes/_schemas.js.map +1 -0
  65. package/dist/server/routes/auth.js +164 -0
  66. package/dist/server/routes/auth.js.map +1 -0
  67. package/dist/server/routes/config.js +1163 -0
  68. package/dist/server/routes/config.js.map +1 -0
  69. package/dist/server/routes/control.js +464 -0
  70. package/dist/server/routes/control.js.map +1 -0
  71. package/dist/server/routes/exec.js +217 -0
  72. package/dist/server/routes/exec.js.map +1 -0
  73. package/dist/server/routes/files.js +847 -0
  74. package/dist/server/routes/files.js.map +1 -0
  75. package/dist/server/routes/git.js +837 -0
  76. package/dist/server/routes/git.js.map +1 -0
  77. package/dist/server/routes/health.js +97 -0
  78. package/dist/server/routes/health.js.map +1 -0
  79. package/dist/server/routes/mcp.js +300 -0
  80. package/dist/server/routes/mcp.js.map +1 -0
  81. package/dist/server/routes/projects.js +259 -0
  82. package/dist/server/routes/projects.js.map +1 -0
  83. package/dist/server/routes/prompt.js +496 -0
  84. package/dist/server/routes/prompt.js.map +1 -0
  85. package/dist/server/routes/sessions.js +783 -0
  86. package/dist/server/routes/sessions.js.map +1 -0
  87. package/dist/server/routes/stream.js +69 -0
  88. package/dist/server/routes/stream.js.map +1 -0
  89. package/dist/server/routes/terminal.js +335 -0
  90. package/dist/server/routes/terminal.js.map +1 -0
  91. package/dist/server/session-registry.js +1197 -0
  92. package/dist/server/session-registry.js.map +1 -0
  93. package/dist/server/skill-overrides.js +151 -0
  94. package/dist/server/skill-overrides.js.map +1 -0
  95. package/dist/server/skills-export.js +257 -0
  96. package/dist/server/skills-export.js.map +1 -0
  97. package/dist/server/sse-bridge.js +220 -0
  98. package/dist/server/sse-bridge.js.map +1 -0
  99. package/dist/server/tool-overrides.js +277 -0
  100. package/dist/server/tool-overrides.js.map +1 -0
  101. package/dist/server/turn-diff-builder.js +280 -0
  102. package/dist/server/turn-diff-builder.js.map +1 -0
  103. package/package.json +53 -12
@@ -0,0 +1,326 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ function readEnv(key) {
7
+ const v = process.env[key];
8
+ return v === undefined || v === "" ? undefined : v;
9
+ }
10
+ function readInt(key, fallback) {
11
+ const v = readEnv(key);
12
+ if (v === undefined)
13
+ return fallback;
14
+ const n = Number.parseInt(v, 10);
15
+ if (!Number.isFinite(n) || n < 0) {
16
+ throw new Error(`config: ${key} must be a non-negative integer (got ${v})`);
17
+ }
18
+ return n;
19
+ }
20
+ function readStringList(key) {
21
+ const v = readEnv(key);
22
+ if (v === undefined)
23
+ return [];
24
+ // Comma- or whitespace-separated; either is natural in shell, k8s
25
+ // env, and docker-compose `environment:` lists. Drop empties so
26
+ // trailing commas don't produce ghost entries.
27
+ return v
28
+ .split(/[,\s]+/)
29
+ .map((s) => s.trim())
30
+ .filter((s) => s.length > 0);
31
+ }
32
+ function readBool(key, fallback) {
33
+ const v = readEnv(key)?.toLowerCase();
34
+ if (v === undefined)
35
+ return fallback;
36
+ if (["1", "true", "yes", "on"].includes(v))
37
+ return true;
38
+ if (["0", "false", "no", "off"].includes(v))
39
+ return false;
40
+ throw new Error(`config: ${key} must be a boolean-ish value (got ${v})`);
41
+ }
42
+ /**
43
+ * Forge-owned root. `~/.pi-forge` is the single dotdir we own.
44
+ * By default it holds both the project registry and the workspace where
45
+ * user code lives:
46
+ *
47
+ * ~/.pi-forge/
48
+ * ├── projects.json ← FORGE_DATA_DIR by default
49
+ * └── workspace/ ← WORKSPACE_PATH by default
50
+ *
51
+ * Either path can be relocated independently via its env var (e.g. point
52
+ * `WORKSPACE_PATH` at an existing `~/Code` dir to use code you already
53
+ * have on disk). Docker compose sets both explicitly so the container
54
+ * layout is unchanged.
55
+ */
56
+ const HOME = homedir();
57
+ if (HOME === "/" || HOME === "") {
58
+ throw new Error(`config: os.homedir() returned ${JSON.stringify(HOME)}. ` +
59
+ "This usually means HOME / USERPROFILE is unset. " +
60
+ "Set WORKSPACE_PATH, PI_CONFIG_DIR, and FORGE_DATA_DIR explicitly, " +
61
+ "or run the server with a real user account.");
62
+ }
63
+ const FORGE_HOME = join(HOME, ".pi-forge");
64
+ const WORKSPACE_PATH = resolve(readEnv("WORKSPACE_PATH") ?? join(FORGE_HOME, "workspace"));
65
+ // Default to the current user's home so local dev on macOS/Linux just works.
66
+ // In the documented Docker setup this still resolves to `/root/.pi/agent`
67
+ // (root's homedir IS `/root` inside the container), so the production target
68
+ // is unchanged. Override explicitly via PI_CONFIG_DIR if needed.
69
+ const PI_CONFIG_DIR = resolve(readEnv("PI_CONFIG_DIR") ?? join(HOME, ".pi", "agent"));
70
+ const SESSION_DIR = resolve(readEnv("SESSION_DIR") ?? `${WORKSPACE_PATH}/.pi/sessions`);
71
+ /**
72
+ * Forge-owned data dir. Holds `projects.json` (the project registry
73
+ * pi-forge layers on top of pi) and any other state that's ours, not
74
+ * pi's. Defaults to `FORGE_HOME` (~/.pi-forge) so projects.json
75
+ * sits next to the workspace folder. Kept SEPARATE from `PI_CONFIG_DIR`
76
+ * (~/.pi/agent), which is owned by the pi SDK — auth.json, models.json,
77
+ * settings.json. Dropping our state into the SDK's dir was the original
78
+ * design and got refactored out.
79
+ */
80
+ const FORGE_DATA_DIR = resolve(readEnv("FORGE_DATA_DIR") ?? FORGE_HOME);
81
+ /**
82
+ * Path to the built client (Vite output). In production we serve this via
83
+ * `@fastify/static`. The default resolves relative to the compiled server
84
+ * file (`packages/server/dist/config.js` → `../../client/dist`), which
85
+ * works for both the local `npm run build && node dist/index.js` flow and
86
+ * the Docker image (which mirrors the same `packages/server/dist` +
87
+ * `packages/client/dist` layout). Override with `CLIENT_DIST_PATH` if you
88
+ * relocate the built assets.
89
+ */
90
+ const CLIENT_DIST_PATH = resolve(readEnv("CLIENT_DIST_PATH") ??
91
+ join(dirname(fileURLToPath(import.meta.url)), "..", "..", "client", "dist"));
92
+ const UI_PASSWORD = readEnv("UI_PASSWORD");
93
+ const API_KEY = readEnv("API_KEY");
94
+ const CORS_ORIGIN = readEnv("CORS_ORIGIN");
95
+ /**
96
+ * Load a JWT signing key from `${FORGE_DATA_DIR}/jwt-secret`, or
97
+ * generate-and-persist one on first boot. Treated like an SSH host key:
98
+ * created once, persisted to the data dir (which is the PVC / bind-mount
99
+ * in K8s and Docker), reused across restarts so issued tokens stay
100
+ * valid. Setting `JWT_SECRET` env explicitly skips this entirely.
101
+ *
102
+ * Only invoked when `UI_PASSWORD` is set — if browser auth isn't on,
103
+ * we don't need a secret at all.
104
+ */
105
+ function loadOrGenerateJwtSecret(dataDir) {
106
+ const path = join(dataDir, "jwt-secret");
107
+ if (existsSync(path)) {
108
+ const v = readFileSync(path, "utf8").trim();
109
+ // 32 bytes = 256 bits ≈ 43 base64url chars. Anything shorter is
110
+ // either truncated or hand-edited; regenerate rather than trust it.
111
+ if (v.length >= 32)
112
+ return v;
113
+ }
114
+ mkdirSync(dataDir, { recursive: true });
115
+ const secret = randomBytes(48).toString("base64url");
116
+ const tmp = `${path}.tmp`;
117
+ writeFileSync(tmp, `${secret}\n`, { mode: 0o600 });
118
+ chmodSync(tmp, 0o600);
119
+ renameSync(tmp, path);
120
+ console.log(`[config] auto-generated JWT secret persisted at ${path}. ` +
121
+ "Delete this file to rotate (logs out all browser sessions).");
122
+ return secret;
123
+ }
124
+ const JWT_SECRET = readEnv("JWT_SECRET") ??
125
+ (UI_PASSWORD !== undefined ? loadOrGenerateJwtSecret(FORGE_DATA_DIR) : undefined);
126
+ export const config = Object.freeze({
127
+ port: readInt("PORT", 3000),
128
+ // HOST default depends on NODE_ENV. Production binds 0.0.0.0 (Docker
129
+ // image's normal mode); dev binds 127.0.0.1 so a `npm run dev` on a
130
+ // laptop doesn't silently expose the agent's shell + filesystem to
131
+ // anyone on the same WiFi/VLAN. Operators who want LAN access in dev
132
+ // can set HOST=0.0.0.0 explicitly.
133
+ host: readEnv("HOST") ?? (process.env.NODE_ENV === "production" ? "0.0.0.0" : "127.0.0.1"),
134
+ logLevel: readEnv("LOG_LEVEL") ?? "info",
135
+ isTest: (readEnv("NODE_ENV") ?? "") === "test",
136
+ trustProxy: readBool("TRUST_PROXY", false),
137
+ workspacePath: WORKSPACE_PATH,
138
+ piConfigDir: PI_CONFIG_DIR,
139
+ forgeDataDir: FORGE_DATA_DIR,
140
+ sessionDir: SESSION_DIR,
141
+ clientDistPath: CLIENT_DIST_PATH,
142
+ serveClient: readBool("SERVE_CLIENT", true),
143
+ /**
144
+ * Frontend "minimal" mode. When true, the client UI hides the
145
+ * terminal, git pane, last-turn pane, and the providers/agent
146
+ * settings sections, and replaces the project folder picker with
147
+ * a name-only form that creates `<workspacePath>/<name>`. Server
148
+ * routes are unchanged — this is purely a frontend gate exposed
149
+ * via `GET /api/v1/ui-config`. Use case: locked-down deployments
150
+ * where provider config is managed at the deploy level.
151
+ */
152
+ minimalUi: readBool("MINIMAL_UI", false),
153
+ /**
154
+ * When true, `GET /config/providers` filters out provider entries
155
+ * whose name does NOT appear as a key in `models.json`. Built-in
156
+ * providers (anthropic, openai, etc. that the SDK ships with) are
157
+ * hidden from the Settings → Providers list, leaving only the
158
+ * custom providers the operator added via `models.json`. Useful
159
+ * for deployments that route every model through a single internal
160
+ * gateway (vLLM, LiteLLM, internal proxy) and don't want users
161
+ * picking the public providers from the UI.
162
+ *
163
+ * Intentionally not exposed in docker-compose / .env.example —
164
+ * advanced env knob, document if/when it's needed widely.
165
+ */
166
+ hideBuiltinProviders: readBool("HIDE_BUILTIN_PROVIDERS", false),
167
+ /**
168
+ * Path to the forge-owned MCP server registry. Lives in the
169
+ * data dir (not pi's config dir) because pi has no native MCP
170
+ * support — `mcp.json` is purely a forge file, surfaced to
171
+ * the agent via `customTools` on createAgentSession.
172
+ */
173
+ mcpConfigFile: join(FORGE_DATA_DIR, "mcp.json"),
174
+ /**
175
+ * Path to the forge-private per-project skill overrides file.
176
+ * Lives in the data dir (NOT in PI_CONFIG_DIR — pi's settings.skills
177
+ * is global, and not in `<project>/.pi/` — the user picked
178
+ * forge-private over team-shared so each install has its own
179
+ * preferences without bleeding into the project tree).
180
+ */
181
+ skillOverridesFile: join(FORGE_DATA_DIR, "skills-overrides.json"),
182
+ /**
183
+ * Path to the forge-private per-tool override file. Captures
184
+ * "user has explicitly disabled this builtin tool" and "user has
185
+ * explicitly disabled this MCP tool" — both as flat allow-by-default
186
+ * sets. Lives in the data dir (forge-owned; pi's SDK has no
187
+ * native concept of per-tool toggles, this is purely a forge
188
+ * filter applied to the `tools` allowlist passed to
189
+ * createAgentSession).
190
+ */
191
+ toolOverridesFile: join(FORGE_DATA_DIR, "tool-overrides.json"),
192
+ /**
193
+ * Whether `/api/docs` (Swagger UI + OpenAPI JSON spec) is reachable.
194
+ * Defaults to true so Docker / production deploys keep working without
195
+ * extra config (the README quickstart documents `/api/docs`). When
196
+ * auth is enabled, the existing token check still gates the docs;
197
+ * when auth is disabled, the docs are an info-leak surface (route
198
+ * catalogue, body schemas), so security-conscious operators in
199
+ * unauthenticated public-internet deployments should set
200
+ * `EXPOSE_DOCS=false` — though that combo is itself discouraged
201
+ * (see SECURITY.md: never network-expose without auth + TLS).
202
+ */
203
+ exposeDocs: readBool("EXPOSE_DOCS", true),
204
+ auth: Object.freeze({
205
+ uiPassword: UI_PASSWORD,
206
+ jwtSecret: JWT_SECRET,
207
+ apiKey: API_KEY,
208
+ jwtExpiresInSeconds: readInt("JWT_EXPIRES_IN_SECONDS", 60 * 60 * 24 * 7),
209
+ loginRateLimitMax: readInt("RATE_LIMIT_LOGIN_MAX", 10),
210
+ loginRateLimitWindowMs: readInt("RATE_LIMIT_LOGIN_WINDOW_MS", 60_000),
211
+ /**
212
+ * When true and the only credential is the env-provided UI_PASSWORD
213
+ * (no on-disk hash yet), the login response carries
214
+ * `mustChangePassword: true` and the issued JWT is restricted —
215
+ * the user can only call `POST /auth/change-password` until they
216
+ * pick a new password. After the user changes it, the new password
217
+ * is hashed and persisted to `${FORGE_DATA_DIR}/password-hash`,
218
+ * and subsequent logins ignore the env value.
219
+ *
220
+ * Defaults to true so deployments that bake an initial password
221
+ * into env (helm secret, docker-compose .env) don't accidentally
222
+ * leave that credential as the long-lived one.
223
+ */
224
+ requirePasswordChange: readBool("REQUIRE_PASSWORD_CHANGE", true),
225
+ /** Where the persisted scrypt hash lives — see auth.ts. */
226
+ passwordHashFile: join(FORGE_DATA_DIR, "password-hash"),
227
+ }),
228
+ /**
229
+ * Per-route rate limits applied to the cost-heavy / disk-heavy / CPU-heavy
230
+ * routes. Defaults are conservative — enough headroom for an interactive
231
+ * user, low enough that a leaked-token spam loop hits the cap fast.
232
+ * Operators with higher legitimate volume can raise via env.
233
+ */
234
+ rateLimits: Object.freeze({
235
+ // /sessions/:id/{prompt,steer,compact,navigate} — per-user prompt
236
+ // floor. 60 / minute = 1 / second sustained, far above interactive
237
+ // typing speed; a runaway script gets capped in roughly 1 minute.
238
+ promptMax: readInt("RATE_LIMIT_PROMPT_MAX", 60),
239
+ promptWindowMs: readInt("RATE_LIMIT_PROMPT_WINDOW_MS", 60_000),
240
+ // /files/upload — disk fill. 30 / minute keeps an attentive user
241
+ // unblocked while capping a fill-the-disk loop.
242
+ uploadMax: readInt("RATE_LIMIT_UPLOAD_MAX", 30),
243
+ uploadWindowMs: readInt("RATE_LIMIT_UPLOAD_WINDOW_MS", 60_000),
244
+ // /files/search — CPU. ripgrep walks the workspace; each search is
245
+ // bounded by ripgrep but a tight loop still spins a CPU core.
246
+ searchMax: readInt("RATE_LIMIT_SEARCH_MAX", 60),
247
+ searchWindowMs: readInt("RATE_LIMIT_SEARCH_WINDOW_MS", 60_000),
248
+ // /git/push — network amplification + rate-limited by the git remote.
249
+ // Conservative — pushing 10x in a minute is almost always a mistake.
250
+ pushMax: readInt("RATE_LIMIT_PUSH_MAX", 10),
251
+ pushWindowMs: readInt("RATE_LIMIT_PUSH_WINDOW_MS", 60_000),
252
+ }),
253
+ corsOrigin: CORS_ORIGIN,
254
+ /**
255
+ * Extra env-var names the operator wants the integrated terminal
256
+ * (and the `!` exec route) to inherit from the pi-forge process.
257
+ *
258
+ * The terminal env starts from a small allowlist of harmless system
259
+ * vars (PATH, HOME, USER, SHELL, TERM, locales — see
260
+ * `pty-manager.ts#TERMINAL_ENV_ALLOWLIST`). Everything else is
261
+ * dropped — including provider API keys (`OPENAI_API_KEY`,
262
+ * `AWS_ACCESS_KEY_ID`, etc.) the operator may have in their host
263
+ * shell that would otherwise be inherited by every spawn. This
264
+ * defaults to fail-safe: any new sensitive var the operator sets
265
+ * is hidden from the shell unless they explicitly pass it through.
266
+ *
267
+ * Add specific vars here when the shell genuinely needs them
268
+ * (e.g. `KUBECONFIG`, `EDITOR`, `OPENAI_BASE_URL` for an internal
269
+ * proxy). Format: comma- or whitespace-separated.
270
+ *
271
+ * Example: `TERMINAL_PASSTHROUGH_ENV=KUBECONFIG,EDITOR,NODE_ENV`
272
+ */
273
+ terminalPassthroughEnv: Object.freeze(readStringList("TERMINAL_PASSTHROUGH_ENV")),
274
+ /**
275
+ * Opt-in: append a pi-forge-defined "secret hygiene" rule to the
276
+ * agent's system prompt. The rule asks the model to treat env-var
277
+ * values as credentials by default and not echo them into responses
278
+ * or tool outputs unless explicitly asked. See
279
+ * `agent-resource-loader.ts#FORGE_SECRET_HYGIENE_RULE` for the
280
+ * exact wording and `SECURITY.md` for the threat-model framing
281
+ * (behavioral nudge, not a security control).
282
+ *
283
+ * Default OFF. Operators who want it explicitly opt in by setting
284
+ * `AGENT_SECRET_HYGIENE_RULE=true`. Kept opt-in (rather than
285
+ * default-on) so the pi-forge doesn't ship invisible behavioral
286
+ * rules that constrain the agent in ways the user never asked for.
287
+ * Deliberately not surfaced in `docker-compose.yml` or
288
+ * `.env.example` — this is an advanced knob, intentionally
289
+ * discoverable only via SECURITY.md so operators meet the rule
290
+ * the same time they meet its caveats.
291
+ */
292
+ agentSecretHygieneRule: readBool("AGENT_SECRET_HYGIENE_RULE", false),
293
+ /**
294
+ * How long a detached PTY (its WebSocket closed but no replacement
295
+ * attached yet) is held alive before being reaped. The 10-minute
296
+ * default protects the common reattach use case: page refresh,
297
+ * transient network blip, laptop sleep — none of those should kill
298
+ * the user's shell.
299
+ *
300
+ * Operators in resource-constrained envs (kiosks, low-RAM
301
+ * containers) can shrink this. The integration test pins it to
302
+ * ~200 ms so the reap-on-close assertion completes within a normal
303
+ * test budget instead of waiting 10 minutes.
304
+ *
305
+ * Read by `pty-manager.ts#IDLE_REAP_MS` at module load. Setting
306
+ * this to 0 effectively disables reattach-after-WS-drop (every WS
307
+ * close becomes a hard kill); use that deliberately or not at all.
308
+ */
309
+ terminalIdleReapMs: readInt("PTY_IDLE_REAP_MS", 10 * 60 * 1000),
310
+ });
311
+ export function authEnabled() {
312
+ return (config.auth.uiPassword !== undefined ||
313
+ config.auth.apiKey !== undefined ||
314
+ existsSync(config.auth.passwordHashFile));
315
+ }
316
+ /**
317
+ * True iff this deployment supports the browser password-change flow:
318
+ * either an env-supplied UI_PASSWORD is in use OR a hash has already
319
+ * been persisted from a prior change. API-key-only deployments don't
320
+ * have a password to change, so the Settings → General password
321
+ * section hides on them. Read by /ui-config.
322
+ */
323
+ export function passwordAuthEnabled() {
324
+ return config.auth.uiPassword !== undefined || existsSync(config.auth.passwordHashFile);
325
+ }
326
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACpG,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,SAAS,OAAO,CAAC,GAAW;IAC1B,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3B,OAAO,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,OAAO,CAAC,GAAW,EAAE,QAAgB;IAC5C,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACvB,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IACrC,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,WAAW,GAAG,wCAAwC,CAAC,GAAG,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,cAAc,CAAC,GAAW;IACjC,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACvB,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IAC/B,kEAAkE;IAClE,gEAAgE;IAChE,+CAA+C;IAC/C,OAAO,CAAC;SACL,KAAK,CAAC,QAAQ,CAAC;SACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACjC,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW,EAAE,QAAiB;IAC9C,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,WAAW,EAAE,CAAC;IACtC,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IACrC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACxD,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1D,MAAM,IAAI,KAAK,CAAC,WAAW,GAAG,qCAAqC,CAAC,GAAG,CAAC,CAAC;AAC3E,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;AACvB,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;IAChC,MAAM,IAAI,KAAK,CACb,iCAAiC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI;QACvD,kDAAkD;QAClD,oEAAoE;QACpE,6CAA6C,CAChD,CAAC;AACJ,CAAC;AACD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;AAC3C,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC;AAC3F,6EAA6E;AAC7E,0EAA0E;AAC1E,6EAA6E;AAC7E,iEAAiE;AACjE,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;AACtF,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,GAAG,cAAc,eAAe,CAAC,CAAC;AACxF;;;;;;;;GAQG;AACH,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI,UAAU,CAAC,CAAC;AAExE;;;;;;;;GAQG;AACH,MAAM,gBAAgB,GAAG,OAAO,CAC9B,OAAO,CAAC,kBAAkB,CAAC;IACzB,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,CAC9E,CAAC;AAEF,MAAM,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;AAC3C,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;AACnC,MAAM,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;AAE3C;;;;;;;;;GASG;AACH,SAAS,uBAAuB,CAAC,OAAe;IAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IACzC,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5C,gEAAgE;QAChE,oEAAoE;QACpE,IAAI,CAAC,CAAC,MAAM,IAAI,EAAE;YAAE,OAAO,CAAC,CAAC;IAC/B,CAAC;IACD,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACrD,MAAM,GAAG,GAAG,GAAG,IAAI,MAAM,CAAC;IAC1B,aAAa,CAAC,GAAG,EAAE,GAAG,MAAM,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnD,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACtB,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACtB,OAAO,CAAC,GAAG,CACT,mDAAmD,IAAI,IAAI;QACzD,6DAA6D,CAChE,CAAC;IACF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,GACd,OAAO,CAAC,YAAY,CAAC;IACrB,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,uBAAuB,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AAEpF,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAClC,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC;IAC3B,qEAAqE;IACrE,oEAAoE;IACpE,mEAAmE;IACnE,qEAAqE;IACrE,mCAAmC;IACnC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC;IAC1F,QAAQ,EAAE,OAAO,CAAC,WAAW,CAAC,IAAI,MAAM;IACxC,MAAM,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM;IAC9C,UAAU,EAAE,QAAQ,CAAC,aAAa,EAAE,KAAK,CAAC;IAC1C,aAAa,EAAE,cAAc;IAC7B,WAAW,EAAE,aAAa;IAC1B,YAAY,EAAE,cAAc;IAC5B,UAAU,EAAE,WAAW;IACvB,cAAc,EAAE,gBAAgB;IAChC,WAAW,EAAE,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC;IAC3C;;;;;;;;OAQG;IACH,SAAS,EAAE,QAAQ,CAAC,YAAY,EAAE,KAAK,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,oBAAoB,EAAE,QAAQ,CAAC,wBAAwB,EAAE,KAAK,CAAC;IAC/D;;;;;OAKG;IACH,aAAa,EAAE,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC;IAC/C;;;;;;OAMG;IACH,kBAAkB,EAAE,IAAI,CAAC,cAAc,EAAE,uBAAuB,CAAC;IACjE;;;;;;;;OAQG;IACH,iBAAiB,EAAE,IAAI,CAAC,cAAc,EAAE,qBAAqB,CAAC;IAC9D;;;;;;;;;;OAUG;IACH,UAAU,EAAE,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC;IACzC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC;QAClB,UAAU,EAAE,WAAW;QACvB,SAAS,EAAE,UAAU;QACrB,MAAM,EAAE,OAAO;QACf,mBAAmB,EAAE,OAAO,CAAC,wBAAwB,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QACxE,iBAAiB,EAAE,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC;QACtD,sBAAsB,EAAE,OAAO,CAAC,4BAA4B,EAAE,MAAM,CAAC;QACrE;;;;;;;;;;;;WAYG;QACH,qBAAqB,EAAE,QAAQ,CAAC,yBAAyB,EAAE,IAAI,CAAC;QAChE,2DAA2D;QAC3D,gBAAgB,EAAE,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC;KACxD,CAAC;IACF;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC;QACxB,kEAAkE;QAClE,mEAAmE;QACnE,kEAAkE;QAClE,SAAS,EAAE,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC;QAC/C,cAAc,EAAE,OAAO,CAAC,6BAA6B,EAAE,MAAM,CAAC;QAC9D,iEAAiE;QACjE,gDAAgD;QAChD,SAAS,EAAE,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC;QAC/C,cAAc,EAAE,OAAO,CAAC,6BAA6B,EAAE,MAAM,CAAC;QAC9D,mEAAmE;QACnE,8DAA8D;QAC9D,SAAS,EAAE,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC;QAC/C,cAAc,EAAE,OAAO,CAAC,6BAA6B,EAAE,MAAM,CAAC;QAC9D,sEAAsE;QACtE,qEAAqE;QACrE,OAAO,EAAE,OAAO,CAAC,qBAAqB,EAAE,EAAE,CAAC;QAC3C,YAAY,EAAE,OAAO,CAAC,2BAA2B,EAAE,MAAM,CAAC;KAC3D,CAAC;IACF,UAAU,EAAE,WAAW;IACvB;;;;;;;;;;;;;;;;;;OAkBG;IACH,sBAAsB,EAAE,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,0BAA0B,CAAC,CAAC;IACjF;;;;;;;;;;;;;;;;;OAiBG;IACH,sBAAsB,EAAE,QAAQ,CAAC,2BAA2B,EAAE,KAAK,CAAC;IACpE;;;;;;;;;;;;;;;OAeG;IACH,kBAAkB,EAAE,OAAO,CAAC,kBAAkB,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;CACvD,CAAC,CAAC;AAEZ,MAAM,UAAU,WAAW;IACzB,OAAO,CACL,MAAM,CAAC,IAAI,CAAC,UAAU,KAAK,SAAS;QACpC,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS;QAChC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,CACzC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,MAAM,CAAC,IAAI,CAAC,UAAU,KAAK,SAAS,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;AAC1F,CAAC"}
@@ -0,0 +1,90 @@
1
+ // Worker entry for PDF / DOCX / XLSX text extraction.
2
+ //
3
+ // Lives off the main thread because pdfjs-dist (used by pdf-parse) and
4
+ // ExcelJS do heavy synchronous JavaScript work — a 2 MB PDF can block
5
+ // the event loop for several seconds. While blocked, the SSE bridge's
6
+ // heartbeat can't fire and the underlying TCP socket can stall enough
7
+ // for Node's HTTP layer (or any proxy in front) to drop the
8
+ // connection. Browsers see the SSE close and the chat shows
9
+ // "Reconnecting…" right after the user submits a prompt with an
10
+ // attachment.
11
+ //
12
+ // Pure ESM JS (.mjs) so it runs unmodified under both `tsx` (dev) and
13
+ // compiled-prod node — the build script copies it next to the
14
+ // compiled .js dispatcher.
15
+ import { Buffer } from "node:buffer";
16
+ import { parentPort } from "node:worker_threads";
17
+ import ExcelJS from "exceljs";
18
+ import mammoth from "mammoth";
19
+ import { PDFParse } from "pdf-parse";
20
+
21
+ if (parentPort === null) {
22
+ throw new Error("conversion-worker.mjs must be loaded as a worker_threads worker");
23
+ }
24
+
25
+ async function convertPdf(buf) {
26
+ let parser;
27
+ try {
28
+ parser = new PDFParse({ data: new Uint8Array(buf) });
29
+ const result = await parser.getText();
30
+ if (result.pages.length === 0) {
31
+ return "[PDF contained no extractable text — possibly a scanned/image-only document]";
32
+ }
33
+ return result.pages.map((p) => `--- Page ${p.num} ---\n${p.text.trimEnd()}`).join("\n\n");
34
+ } finally {
35
+ if (parser !== undefined) {
36
+ await parser.destroy().catch(() => undefined);
37
+ }
38
+ }
39
+ }
40
+
41
+ async function convertDocx(buf) {
42
+ const result = await mammoth.extractRawText({ buffer: buf });
43
+ return result.value;
44
+ }
45
+
46
+ async function convertXlsx(buf) {
47
+ const wb = new ExcelJS.Workbook();
48
+ await wb.xlsx.load(buf);
49
+ const sheets = [];
50
+ wb.eachSheet((ws) => {
51
+ const rows = [];
52
+ ws.eachRow({ includeEmpty: false }, (row) => {
53
+ const cells = [];
54
+ row.eachCell({ includeEmpty: true }, (cell) => {
55
+ cells.push(csvEscape(cell.text ?? ""));
56
+ });
57
+ rows.push(cells.join(","));
58
+ });
59
+ sheets.push(`--- Sheet: ${ws.name} ---\n${rows.join("\n")}`);
60
+ });
61
+ if (sheets.length === 0) return "[Workbook contained no readable sheets]";
62
+ return sheets.join("\n\n");
63
+ }
64
+
65
+ function csvEscape(s) {
66
+ if (s.length === 0) return s;
67
+ if (/[",\r\n]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
68
+ return s;
69
+ }
70
+
71
+ parentPort.on("message", async (msg) => {
72
+ // msg = { id, format, buf } — buf arrives as ArrayBuffer because
73
+ // that transfers cheaply between threads.
74
+ const { id, format, buf } = msg;
75
+ const buffer = Buffer.from(buf);
76
+ try {
77
+ let text;
78
+ if (format === "pdf") text = await convertPdf(buffer);
79
+ else if (format === "docx") text = await convertDocx(buffer);
80
+ else if (format === "xlsx") text = await convertXlsx(buffer);
81
+ else throw new Error(`unknown format: ${format}`);
82
+ parentPort.postMessage({ id, ok: true, text });
83
+ } catch (err) {
84
+ parentPort.postMessage({
85
+ id,
86
+ ok: false,
87
+ error: err instanceof Error ? err.message : String(err),
88
+ });
89
+ }
90
+ });
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Operator-visible error diagnostics. The pi SDK swallows provider
3
+ * errors into terse messages ("Connection Error", "fetch failed",
4
+ * "Provider returned an error stop reason") that omit the actual
5
+ * cause — most commonly a TLS handshake failure, DNS error, or
6
+ * connection refused. Without the cause chain, an operator gets a
7
+ * useless one-liner and has to attach a debugger.
8
+ *
9
+ * This module:
10
+ * 1. Installs `unhandledRejection` + `uncaughtException` handlers
11
+ * that print the full `cause` chain to stderr.
12
+ * 2. Exports `formatErrorChain()` for any code path that has caught
13
+ * an Error and wants to log the full underlying detail.
14
+ * 3. When `DEBUG_FETCH=1`, wraps `globalThis.fetch` to log any
15
+ * rejection with the full cause chain. This is the surface where
16
+ * TLS / DNS / connection errors actually originate; the SDK's
17
+ * stringification loses them, so we capture before the SDK does.
18
+ *
19
+ * All output goes to `process.stderr` as JSON lines so
20
+ * `docker logs <container> | jq` works.
21
+ */
22
+ function asErrorLike(v) {
23
+ if (v === null || v === undefined)
24
+ return undefined;
25
+ if (typeof v !== "object")
26
+ return undefined;
27
+ return v;
28
+ }
29
+ export function formatErrorChain(input) {
30
+ const chain = [];
31
+ let stack;
32
+ const seen = new Set();
33
+ const visit = (v, depth) => {
34
+ if (depth > 10)
35
+ return;
36
+ const e = asErrorLike(v);
37
+ if (!e || seen.has(v))
38
+ return;
39
+ seen.add(v);
40
+ const entry = {};
41
+ if (e.name !== undefined)
42
+ entry.name = e.name;
43
+ if (e.code !== undefined)
44
+ entry.code = e.code;
45
+ if (e.message !== undefined)
46
+ entry.message = e.message;
47
+ chain.push(entry);
48
+ if (stack === undefined && typeof e.stack === "string")
49
+ stack = e.stack;
50
+ if (e.cause !== undefined)
51
+ visit(e.cause, depth + 1);
52
+ if (Array.isArray(e.errors)) {
53
+ for (const inner of e.errors)
54
+ visit(inner, depth + 1);
55
+ }
56
+ };
57
+ visit(input, 0);
58
+ if (chain.length === 0) {
59
+ return { message: typeof input === "string" ? input : JSON.stringify(input), chain: [] };
60
+ }
61
+ const message = chain
62
+ .map((c) => [c.name, c.code, c.message].filter(Boolean).join(": "))
63
+ .filter(Boolean)
64
+ .join(" → ");
65
+ const result = { message, chain };
66
+ if (stack !== undefined)
67
+ result.stack = stack;
68
+ return result;
69
+ }
70
+ function writeJson(level, payload) {
71
+ process.stderr.write(`${JSON.stringify({ level, time: new Date().toISOString(), ...payload })}\n`);
72
+ }
73
+ /**
74
+ * Install once at server startup. Idempotent — guards against double
75
+ * registration (e.g. tests that spin up the server multiple times in
76
+ * one process).
77
+ */
78
+ let installed = false;
79
+ export function installDiagnostics() {
80
+ if (installed)
81
+ return;
82
+ installed = true;
83
+ process.on("unhandledRejection", (reason) => {
84
+ const f = formatErrorChain(reason);
85
+ writeJson("error", {
86
+ msg: "unhandledRejection",
87
+ error: f.message,
88
+ chain: f.chain,
89
+ stack: f.stack,
90
+ });
91
+ });
92
+ process.on("uncaughtException", (err) => {
93
+ const f = formatErrorChain(err);
94
+ writeJson("error", {
95
+ msg: "uncaughtException",
96
+ error: f.message,
97
+ chain: f.chain,
98
+ stack: f.stack,
99
+ });
100
+ });
101
+ if (process.env.DEBUG_FETCH === "1") {
102
+ const orig = globalThis.fetch;
103
+ globalThis.fetch = async (input, init) => {
104
+ const url = typeof input === "string"
105
+ ? input
106
+ : input instanceof URL
107
+ ? input.href
108
+ : input.url;
109
+ try {
110
+ const res = await orig(input, init);
111
+ if (!res.ok) {
112
+ writeJson("warn", {
113
+ msg: "fetch non-2xx",
114
+ url,
115
+ method: init?.method ?? "GET",
116
+ status: res.status,
117
+ statusText: res.statusText,
118
+ });
119
+ }
120
+ return res;
121
+ }
122
+ catch (err) {
123
+ const f = formatErrorChain(err);
124
+ writeJson("error", {
125
+ msg: "fetch threw",
126
+ url,
127
+ method: init?.method ?? "GET",
128
+ error: f.message,
129
+ chain: f.chain,
130
+ });
131
+ throw err;
132
+ }
133
+ };
134
+ writeJson("info", { msg: "DEBUG_FETCH enabled — wrapping globalThis.fetch" });
135
+ }
136
+ }
137
+ //# sourceMappingURL=diagnostics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"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"}