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,220 @@
1
+ import { randomUUID } from "node:crypto";
2
+ /**
3
+ * Per-client outbound-buffer cap. When Node's internal socket buffer
4
+ * for a given client exceeds this many bytes, we drop the client
5
+ * rather than retain it indefinitely. A wedged consumer (paused tab,
6
+ * stopped-reading client, ws-proxy that buffers without flushing)
7
+ * can otherwise balloon resident memory by hundreds of MB during a
8
+ * verbose tool execution before the kernel forces socket close.
9
+ *
10
+ * 256 KB matches roughly 50-100 typical events worth of unflushed
11
+ * data — well above any legitimate transient buffering and below
12
+ * the threshold where memory pressure starts mattering.
13
+ */
14
+ const BACKPRESSURE_LIMIT_BYTES = 256 * 1024;
15
+ /**
16
+ * Cadence at which we send an SSE comment line (`: heartbeat\n\n`) on
17
+ * every open stream. EventSource ignores comment lines silently, so the
18
+ * browser sees nothing — but the bytes reset any idle-connection timer
19
+ * sitting between us and the client. OpenShift's HAProxy router defaults
20
+ * `timeout server` to 30s for HTTP routes, and any L7 proxy / load
21
+ * balancer enforces a similar window; with no agent activity between
22
+ * turns, an idle SSE stream gets killed by the middlebox and the
23
+ * browser shows "reconnecting." 20s gives us comfortable margin under
24
+ * the typical 30s default.
25
+ */
26
+ const HEARTBEAT_INTERVAL_MS = 20_000;
27
+ /**
28
+ * Event types we forward to browser clients. Anything else from the SDK is
29
+ * dropped on the floor — keeps the wire stream stable across SDK upgrades and
30
+ * matches the dev-plan catalog.
31
+ */
32
+ const ALLOWED_EVENT_TYPES = new Set([
33
+ "agent_start",
34
+ "agent_end",
35
+ "turn_start",
36
+ "turn_end",
37
+ "message_start",
38
+ "message_update",
39
+ "message_end",
40
+ "tool_execution_start",
41
+ "tool_execution_update",
42
+ "tool_execution_end",
43
+ "tool_call",
44
+ "tool_result",
45
+ "queue_update",
46
+ "compaction_start",
47
+ "compaction_end",
48
+ "auto_retry_start",
49
+ "auto_retry_end",
50
+ "snapshot",
51
+ ]);
52
+ export function isAllowedEvent(event) {
53
+ return ALLOWED_EVENT_TYPES.has(event.type);
54
+ }
55
+ /**
56
+ * Serialize an event into the SSE wire format. Returns the full chunk
57
+ * including the trailing blank line that delimits messages.
58
+ */
59
+ export function serializeSSE(event) {
60
+ return `data: ${JSON.stringify(event)}\n\n`;
61
+ }
62
+ /**
63
+ * Build a snapshot from the current LiveSession state. Pulled out so callers
64
+ * (and tests) can assert the same shape the bridge sends on connect.
65
+ */
66
+ export function buildSnapshot(live) {
67
+ return {
68
+ type: "snapshot",
69
+ sessionId: live.sessionId,
70
+ projectId: live.projectId,
71
+ messages: live.session.messages,
72
+ isStreaming: live.session.isStreaming,
73
+ };
74
+ }
75
+ /**
76
+ * Hijack the Fastify reply and turn it into a long-lived SSE stream attached
77
+ * to `live.clients`. Sends a snapshot immediately, forwards filtered
78
+ * AgentSessionEvents, and unregisters on socket close.
79
+ *
80
+ * The caller's route handler should NOT call `reply.send()` — `hijack()`
81
+ * tells Fastify the response is being driven manually.
82
+ *
83
+ * Throws if the prelude (writeHead / snapshot write) fails. The caller is a
84
+ * route handler that has already hijacked, so Fastify's reply.send(err)
85
+ * fallback is a no-op; this function destroys the underlying socket on
86
+ * failure so the client doesn't hang waiting for headers.
87
+ */
88
+ export function createSSEClient(reply, live) {
89
+ reply.hijack();
90
+ const raw = reply.raw;
91
+ // Closure state — declared up here so the prelude's catch can clean up
92
+ // even if `client` was never finalized.
93
+ let registeredClient;
94
+ let closed = false;
95
+ // The whole prelude (headers, registration, snapshot) is guarded as one
96
+ // unit. Anything that throws here would otherwise hang the client socket:
97
+ // after hijack() Fastify's wrap-thenable.js catches the throw and calls
98
+ // reply.send(err), which is a no-op because reply.sent === true post-hijack.
99
+ // Net result without this guard: no headers, no body, no end → connection
100
+ // hangs until the OS times out. So on any prelude failure we destroy the
101
+ // raw socket and remove the partially-registered client from the registry.
102
+ try {
103
+ raw.writeHead(200, {
104
+ "Content-Type": "text/event-stream",
105
+ "Cache-Control": "no-cache, no-transform",
106
+ Connection: "keep-alive",
107
+ // Disable proxy buffering (nginx, Caddy) so events flush immediately.
108
+ "X-Accel-Buffering": "no",
109
+ });
110
+ const id = randomUUID();
111
+ /**
112
+ * Direct raw write that bypasses the event filter. Used for synthetic
113
+ * frames the bridge owns (snapshot today; heartbeats / keepalive in
114
+ * later phases). Filter-bypass cannot leak SDK events because callers
115
+ * supply already-shaped objects.
116
+ */
117
+ let heartbeatTimer;
118
+ const close = () => {
119
+ if (closed)
120
+ return;
121
+ closed = true;
122
+ if (heartbeatTimer !== undefined) {
123
+ clearInterval(heartbeatTimer);
124
+ heartbeatTimer = undefined;
125
+ }
126
+ if (registeredClient !== undefined)
127
+ live.clients.delete(registeredClient);
128
+ try {
129
+ raw.end();
130
+ }
131
+ catch {
132
+ // socket already torn down — fine
133
+ }
134
+ };
135
+ const writeRaw = (chunk) => {
136
+ if (closed)
137
+ return;
138
+ // Backpressure guard: if the OS-side socket buffer has accumulated
139
+ // more than BACKPRESSURE_LIMIT_BYTES of unwritten bytes, the
140
+ // consumer is wedged (slow network, paused tab, hostile client
141
+ // that opened the SSE stream and stopped reading). Drop the
142
+ // client rather than letting Node's internal buffer grow without
143
+ // bound — `live.clients` retains the SSEClient object until
144
+ // socket close fires, and a wedged TCP socket can take 30+
145
+ // seconds to time out. Without this, repeated events on a
146
+ // verbose tool execution can balloon resident memory by hundreds
147
+ // of MB before the kernel forces the close.
148
+ if (raw.writableLength > BACKPRESSURE_LIMIT_BYTES) {
149
+ close();
150
+ return;
151
+ }
152
+ try {
153
+ raw.write(chunk);
154
+ }
155
+ catch {
156
+ // Socket already torn down (client dropped, network blip).
157
+ // Close cleans up the registry entry + ends the response.
158
+ close();
159
+ }
160
+ };
161
+ const send = (event) => {
162
+ if (closed)
163
+ return;
164
+ if (!isAllowedEvent(event))
165
+ return;
166
+ writeRaw(serializeSSE(event));
167
+ };
168
+ const client = { id, send, close };
169
+ registeredClient = client;
170
+ // Order matters: write the snapshot BEFORE adding to live.clients.
171
+ // The registry's subscribe handler (session-registry.ts:makeSubscribeHandler)
172
+ // fans events out to live.clients synchronously inside the SDK's emit
173
+ // call. If we registered first, an SDK event firing in the window
174
+ // between live.clients.add and the snapshot write would land on the
175
+ // wire BEFORE the snapshot — and the browser treats `snapshot` as
176
+ // authoritative (replaces messagesBySession), so a streaming token
177
+ // delta arriving before the snapshot would be wiped on the
178
+ // snapshot's arrival. Snapshot first → register second → events
179
+ // start flowing in the correct order.
180
+ //
181
+ // Snapshot bypass — uses writeRaw, the same surface a future heartbeat
182
+ // would use. Server-issued synthetic frames flow through writeRaw;
183
+ // SDK-relayed events flow through send (which applies the filter).
184
+ writeRaw(serializeSSE(buildSnapshot(live)));
185
+ live.clients.add(client);
186
+ // Wire close listeners AFTER the snapshot write so an immediate socket
187
+ // close can't double-fire close() before the registry is in a coherent
188
+ // state. Node's 'close' event fires next-tick anyway, but explicit
189
+ // ordering is cheap insurance.
190
+ raw.on("close", close);
191
+ raw.on("error", close);
192
+ // Idle-timer reset for L7 proxies (OpenShift HAProxy router, nginx,
193
+ // ALB, etc.). Comment line, no `data:` field — EventSource skips it.
194
+ // Uses writeRaw so the same backpressure guard applies; if the socket
195
+ // is wedged the heartbeat will trip the limit and call close(), which
196
+ // tears the timer down.
197
+ heartbeatTimer = setInterval(() => {
198
+ writeRaw(": heartbeat\n\n");
199
+ }, HEARTBEAT_INTERVAL_MS);
200
+ // Don't keep the Node event loop alive just for heartbeats — when
201
+ // the socket closes the close handler clears the timer anyway.
202
+ heartbeatTimer.unref();
203
+ return client;
204
+ }
205
+ catch (err) {
206
+ // Prelude failure — clean up partial state and tear the socket down so
207
+ // the client gets a connection drop instead of a hung half-open socket.
208
+ closed = true;
209
+ if (registeredClient !== undefined)
210
+ live.clients.delete(registeredClient);
211
+ try {
212
+ raw.destroy();
213
+ }
214
+ catch {
215
+ // already destroyed
216
+ }
217
+ throw err;
218
+ }
219
+ }
220
+ //# sourceMappingURL=sse-bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-bridge.js","sourceRoot":"","sources":["../src/sse-bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,277 @@
1
+ /**
2
+ * pi-forge-private per-tool overrides at
3
+ * `${FORGE_DATA_DIR}/tool-overrides.json`.
4
+ *
5
+ * Two layers, both allow-by-default:
6
+ *
7
+ * 1. **Global** — flat sets `builtin` / `mcp`. A tool whose name
8
+ * appears in the relevant set is disabled for every project
9
+ * that doesn't override it.
10
+ * 2. **Per-project** — tri-state. For each project the user can
11
+ * explicitly enable a tool (overrides global disable),
12
+ * explicitly disable it (overrides global enable), or stay
13
+ * silent (= inherit global).
14
+ *
15
+ * Effective state for a tool in project P:
16
+ *
17
+ * project[P].enable.includes(name) → enabled (project override wins)
18
+ * project[P].disable.includes(name) → disabled (project override wins)
19
+ * otherwise → !global[family].includes(name)
20
+ *
21
+ * Same shape as `skill-overrides.ts` for the per-project layer; the
22
+ * difference is the global layer (skills don't have one — pi's
23
+ * `settings.skills` lives in pi config, not in this file). Same
24
+ * atomic-write shape (.tmp + rename, mode 0600). Empty arrays /
25
+ * empty project entries are pruned so the file doesn't grow with
26
+ * stale keys after a series of toggles back to inherit.
27
+ *
28
+ * Naming convention matches the names pi sees on the wire:
29
+ * - builtin: bare tool name (`bash`, `read`, `grep`, …)
30
+ * - mcp: bridged tool name `<server>__<tool>` (the same format
31
+ * `mcp/manager.bridgeMcpTool` produces; pi never sees
32
+ * the un-prefixed inner name from the MCP server)
33
+ *
34
+ * Lives outside `${PI_CONFIG_DIR}` because pi's SDK has no native
35
+ * concept of per-tool toggles — this is purely a pi-forge filter
36
+ * applied to the `tools` allowlist passed to `createAgentSession`.
37
+ */
38
+ import { chmodSync } from "node:fs";
39
+ import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
40
+ import { dirname } from "node:path";
41
+ import { randomUUID } from "node:crypto";
42
+ import { config } from "./config.js";
43
+ const EMPTY = { builtin: [], mcp: [], extension: [], projects: {} };
44
+ function emptyProject() {
45
+ return {
46
+ builtin: { enable: [], disable: [] },
47
+ mcp: { enable: [], disable: [] },
48
+ extension: { enable: [], disable: [] },
49
+ };
50
+ }
51
+ async function ensureDir() {
52
+ await mkdir(dirname(config.toolOverridesFile), { recursive: true });
53
+ }
54
+ async function atomicWrite(data) {
55
+ await ensureDir();
56
+ const path = config.toolOverridesFile;
57
+ const tmp = `${path}.${randomUUID()}.tmp`;
58
+ await writeFile(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
59
+ try {
60
+ chmodSync(tmp, 0o600);
61
+ }
62
+ catch {
63
+ // best-effort
64
+ }
65
+ try {
66
+ await rename(tmp, path);
67
+ }
68
+ catch (err) {
69
+ await unlink(tmp).catch(() => undefined);
70
+ throw err;
71
+ }
72
+ }
73
+ function normalizeFamilyOverrides(v) {
74
+ if (typeof v !== "object" || v === null)
75
+ return { enable: [], disable: [] };
76
+ const obj = v;
77
+ return {
78
+ enable: Array.isArray(obj.enable)
79
+ ? obj.enable.filter((n) => typeof n === "string")
80
+ : [],
81
+ disable: Array.isArray(obj.disable)
82
+ ? obj.disable.filter((n) => typeof n === "string")
83
+ : [],
84
+ };
85
+ }
86
+ /**
87
+ * Read the current overrides from disk. Returns an empty shape if
88
+ * the file is missing, malformed, or has unexpected fields — same
89
+ * "errors don't bubble" posture as skill-overrides, so a corrupt
90
+ * file at boot doesn't block session creation. Backwards-compatible
91
+ * with files written by the pre-per-project version (no `projects`
92
+ * key).
93
+ */
94
+ export async function readToolOverrides() {
95
+ try {
96
+ const raw = await readFile(config.toolOverridesFile, "utf8");
97
+ if (raw.trim().length === 0) {
98
+ return { builtin: [], mcp: [], extension: [], projects: {} };
99
+ }
100
+ const parsed = JSON.parse(raw);
101
+ if (typeof parsed !== "object" || parsed === null) {
102
+ return { builtin: [], mcp: [], extension: [], projects: {} };
103
+ }
104
+ const obj = parsed;
105
+ const builtin = Array.isArray(obj.builtin)
106
+ ? obj.builtin.filter((n) => typeof n === "string")
107
+ : [];
108
+ const mcp = Array.isArray(obj.mcp)
109
+ ? obj.mcp.filter((n) => typeof n === "string")
110
+ : [];
111
+ // `extension` is a new family added alongside the existing
112
+ // builtin/mcp pair. Backwards-compatible: an older overrides
113
+ // file with no `extension` key parses to the empty list, so
114
+ // every extension tool is enabled by default until the user
115
+ // explicitly disables it.
116
+ const extension = Array.isArray(obj.extension)
117
+ ? obj.extension.filter((n) => typeof n === "string")
118
+ : [];
119
+ const projects = {};
120
+ if (typeof obj.projects === "object" && obj.projects !== null) {
121
+ for (const [pid, val] of Object.entries(obj.projects)) {
122
+ if (typeof val !== "object" || val === null)
123
+ continue;
124
+ const v = val;
125
+ projects[pid] = {
126
+ builtin: normalizeFamilyOverrides(v.builtin),
127
+ mcp: normalizeFamilyOverrides(v.mcp),
128
+ extension: normalizeFamilyOverrides(v.extension),
129
+ };
130
+ }
131
+ }
132
+ return { builtin, mcp, extension, projects };
133
+ }
134
+ catch (err) {
135
+ if (err.code === "ENOENT") {
136
+ return { builtin: [], mcp: [], extension: [], projects: {} };
137
+ }
138
+ throw err;
139
+ }
140
+ }
141
+ /**
142
+ * Toggle a tool's GLOBAL state. `enabled: false` adds the name to
143
+ * the disabled set; `enabled: true` removes it (returning to the
144
+ * implicit-enabled default). Idempotent.
145
+ */
146
+ export async function setToolEnabled(family, name, enabled) {
147
+ const cur = await readToolOverrides();
148
+ const list = familyList(cur, family);
149
+ const idx = list.indexOf(name);
150
+ if (enabled) {
151
+ if (idx === -1)
152
+ return cur; // already enabled (not in disabled set)
153
+ list.splice(idx, 1);
154
+ }
155
+ else {
156
+ if (idx !== -1)
157
+ return cur; // already disabled
158
+ list.push(name);
159
+ }
160
+ await atomicWrite(cur);
161
+ return cur;
162
+ }
163
+ function familyList(cur, family) {
164
+ if (family === "builtin")
165
+ return cur.builtin;
166
+ if (family === "mcp")
167
+ return cur.mcp;
168
+ return cur.extension;
169
+ }
170
+ function projectFamily(entry, family) {
171
+ if (family === "builtin")
172
+ return entry.builtin;
173
+ if (family === "mcp")
174
+ return entry.mcp;
175
+ return entry.extension;
176
+ }
177
+ /**
178
+ * Set or clear a per-project override for a single tool.
179
+ * `state: "enabled" | "disabled"` adds an explicit override;
180
+ * `state: undefined` clears any existing override (= inherit
181
+ * global). Empty project entries are pruned.
182
+ */
183
+ export async function setProjectToolOverride(projectId, family, name, state) {
184
+ const cur = await readToolOverrides();
185
+ const entry = cur.projects[projectId] ?? emptyProject();
186
+ const fam = projectFamily(entry, family);
187
+ // Remove from BOTH lists first — flipping enable→disable or vice
188
+ // versa is just remove + maybe add. Same dance as
189
+ // skill-overrides.setProjectSkillOverride.
190
+ fam.enable = fam.enable.filter((n) => n !== name);
191
+ fam.disable = fam.disable.filter((n) => n !== name);
192
+ if (state === "enabled")
193
+ fam.enable.push(name);
194
+ else if (state === "disabled")
195
+ fam.disable.push(name);
196
+ // Prune the project entry if every family is empty after the
197
+ // toggle. Keeps the file from accumulating stale keys after the
198
+ // user toggles back to inherit.
199
+ const empty = entry.builtin.enable.length === 0 &&
200
+ entry.builtin.disable.length === 0 &&
201
+ entry.mcp.enable.length === 0 &&
202
+ entry.mcp.disable.length === 0 &&
203
+ entry.extension.enable.length === 0 &&
204
+ entry.extension.disable.length === 0;
205
+ if (empty) {
206
+ delete cur.projects[projectId];
207
+ }
208
+ else {
209
+ cur.projects[projectId] = entry;
210
+ }
211
+ await atomicWrite(cur);
212
+ return cur;
213
+ }
214
+ /**
215
+ * Look up a project's explicit position on a tool. Returns
216
+ * `undefined` when the project has no opinion (= inherit from
217
+ * global).
218
+ */
219
+ export function getProjectToolState(overrides, projectId, family, name) {
220
+ const entry = overrides.projects[projectId];
221
+ if (entry === undefined)
222
+ return undefined;
223
+ const fam = projectFamily(entry, family);
224
+ if (fam.enable.includes(name))
225
+ return "enabled";
226
+ if (fam.disable.includes(name))
227
+ return "disabled";
228
+ return undefined;
229
+ }
230
+ /**
231
+ * Compute whether a tool is effective (enabled) for a project,
232
+ * combining the global default with any per-project override.
233
+ * Pass `projectId === undefined` to evaluate global state only.
234
+ */
235
+ export function isToolEffective(overrides, projectId, family, name) {
236
+ if (projectId !== undefined) {
237
+ const state = getProjectToolState(overrides, projectId, family, name);
238
+ if (state === "enabled")
239
+ return true;
240
+ if (state === "disabled")
241
+ return false;
242
+ }
243
+ return !familyList(overrides, family).includes(name);
244
+ }
245
+ /**
246
+ * Apply the overrides as a filter over a candidate tool list.
247
+ * Returns the names that should remain ACTIVE for the given
248
+ * project (or globally if `projectId === undefined`).
249
+ *
250
+ * Used at every `createAgentSession` call site to filter the
251
+ * allowlist passed as `tools: [...]`. Builds the family-sets once
252
+ * per call so the filter is O(1) per tool.
253
+ */
254
+ export function filterEnabledTools(overrides, projectId, candidates) {
255
+ return candidates
256
+ .filter(({ family, name }) => isToolEffective(overrides, projectId, family, name))
257
+ .map(({ name }) => name);
258
+ }
259
+ export async function getAllToolOverrides() {
260
+ const cur = await readToolOverrides();
261
+ return { projects: cur.projects };
262
+ }
263
+ /**
264
+ * Drop every override mention of a deleted project so the file
265
+ * doesn't accumulate orphaned entries. Called from project-manager
266
+ * on project delete (parallels `skill-overrides.clearProjectOverrides`).
267
+ */
268
+ export async function clearProjectOverrides(projectId) {
269
+ const cur = await readToolOverrides();
270
+ if (cur.projects[projectId] === undefined)
271
+ return;
272
+ delete cur.projects[projectId];
273
+ await atomicWrite(cur);
274
+ }
275
+ /** Suppress unused-export warning for tests that import EMPTY. */
276
+ export const _EMPTY_FOR_TESTS = EMPTY;
277
+ //# sourceMappingURL=tool-overrides.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-overrides.js","sourceRoot":"","sources":["../src/tool-overrides.ts"],"names":[],"mappings":"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"}