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.
- package/LICENSE +21 -0
- package/README.md +48 -4
- package/bin/pi-forge.mjs +37 -0
- package/dist/client/assets/CodeMirrorEditor-BqaaP1EE.js +34 -0
- package/dist/client/assets/CodeMirrorEditor-BqaaP1EE.js.map +1 -0
- package/dist/client/assets/index-B-529kgJ.css +32 -0
- package/dist/client/assets/index-BzKzxXFs.js +392 -0
- package/dist/client/assets/index-BzKzxXFs.js.map +1 -0
- package/dist/client/assets/workbox-window.prod.es5-BBnX5xw4.js +3 -0
- package/dist/client/assets/workbox-window.prod.es5-BBnX5xw4.js.map +1 -0
- package/dist/client/icons/icon-192.png +0 -0
- package/dist/client/icons/icon-512.png +0 -0
- package/dist/client/icons/icon-maskable-512.png +0 -0
- package/dist/client/icons/icon.svg +9 -0
- package/dist/client/index.html +24 -0
- package/dist/client/manifest.webmanifest +1 -0
- package/dist/client/offline.html +142 -0
- package/dist/client/sw.js +3 -0
- package/dist/client/sw.js.map +1 -0
- package/dist/client/workbox-6d7155ed.js +3 -0
- package/dist/client/workbox-6d7155ed.js.map +1 -0
- package/dist/server/agent-resource-loader.js +126 -0
- package/dist/server/agent-resource-loader.js.map +1 -0
- package/dist/server/attachment-converters.js +96 -0
- package/dist/server/attachment-converters.js.map +1 -0
- package/dist/server/auth.js +209 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/compaction-history.js +106 -0
- package/dist/server/compaction-history.js.map +1 -0
- package/dist/server/concurrency.js +49 -0
- package/dist/server/concurrency.js.map +1 -0
- package/dist/server/config-export.js +220 -0
- package/dist/server/config-export.js.map +1 -0
- package/dist/server/config-manager.js +528 -0
- package/dist/server/config-manager.js.map +1 -0
- package/dist/server/config.js +326 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/conversion-worker.mjs +90 -0
- package/dist/server/diagnostics.js +137 -0
- package/dist/server/diagnostics.js.map +1 -0
- package/dist/server/extensions-discovery.js +147 -0
- package/dist/server/extensions-discovery.js.map +1 -0
- package/dist/server/file-manager.js +734 -0
- package/dist/server/file-manager.js.map +1 -0
- package/dist/server/file-references.js +215 -0
- package/dist/server/file-references.js.map +1 -0
- package/dist/server/file-searcher.js +385 -0
- package/dist/server/file-searcher.js.map +1 -0
- package/dist/server/git-runner.js +684 -0
- package/dist/server/git-runner.js.map +1 -0
- package/dist/server/index.js +468 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/mcp/config.js +133 -0
- package/dist/server/mcp/config.js.map +1 -0
- package/dist/server/mcp/manager.js +351 -0
- package/dist/server/mcp/manager.js.map +1 -0
- package/dist/server/mcp/tool-bridge.js +173 -0
- package/dist/server/mcp/tool-bridge.js.map +1 -0
- package/dist/server/project-manager.js +301 -0
- package/dist/server/project-manager.js.map +1 -0
- package/dist/server/pty-manager.js +354 -0
- package/dist/server/pty-manager.js.map +1 -0
- package/dist/server/routes/_schemas.js +73 -0
- package/dist/server/routes/_schemas.js.map +1 -0
- package/dist/server/routes/auth.js +164 -0
- package/dist/server/routes/auth.js.map +1 -0
- package/dist/server/routes/config.js +1163 -0
- package/dist/server/routes/config.js.map +1 -0
- package/dist/server/routes/control.js +464 -0
- package/dist/server/routes/control.js.map +1 -0
- package/dist/server/routes/exec.js +217 -0
- package/dist/server/routes/exec.js.map +1 -0
- package/dist/server/routes/files.js +847 -0
- package/dist/server/routes/files.js.map +1 -0
- package/dist/server/routes/git.js +837 -0
- package/dist/server/routes/git.js.map +1 -0
- package/dist/server/routes/health.js +97 -0
- package/dist/server/routes/health.js.map +1 -0
- package/dist/server/routes/mcp.js +300 -0
- package/dist/server/routes/mcp.js.map +1 -0
- package/dist/server/routes/projects.js +259 -0
- package/dist/server/routes/projects.js.map +1 -0
- package/dist/server/routes/prompt.js +496 -0
- package/dist/server/routes/prompt.js.map +1 -0
- package/dist/server/routes/sessions.js +783 -0
- package/dist/server/routes/sessions.js.map +1 -0
- package/dist/server/routes/stream.js +69 -0
- package/dist/server/routes/stream.js.map +1 -0
- package/dist/server/routes/terminal.js +335 -0
- package/dist/server/routes/terminal.js.map +1 -0
- package/dist/server/session-registry.js +1197 -0
- package/dist/server/session-registry.js.map +1 -0
- package/dist/server/skill-overrides.js +151 -0
- package/dist/server/skill-overrides.js.map +1 -0
- package/dist/server/skills-export.js +257 -0
- package/dist/server/skills-export.js.map +1 -0
- package/dist/server/sse-bridge.js +220 -0
- package/dist/server/sse-bridge.js.map +1 -0
- package/dist/server/tool-overrides.js +277 -0
- package/dist/server/tool-overrides.js.map +1 -0
- package/dist/server/turn-diff-builder.js +280 -0
- package/dist/server/turn-diff-builder.js.map +1 -0
- 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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"}
|