patchwork-os 0.2.0-beta.1 → 0.2.0-beta.2
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/deploy/deploy-dashboard.sh +25 -1
- package/deploy/macos/README.md +153 -0
- package/deploy/macos/com.patchwork.bridge.plist.template +54 -0
- package/deploy/macos/com.patchwork.tunnel.plist.template +76 -0
- package/deploy/macos/install-mac-bridge.sh +244 -0
- package/deploy/macos/uninstall-mac-bridge.sh +22 -0
- package/dist/approvalHttp.d.ts +14 -0
- package/dist/approvalHttp.js +172 -1
- package/dist/approvalHttp.js.map +1 -1
- package/dist/approvalQueue.d.ts +27 -2
- package/dist/approvalQueue.js +44 -7
- package/dist/approvalQueue.js.map +1 -1
- package/dist/automation.d.ts +34 -3
- package/dist/automation.js +85 -10
- package/dist/automation.js.map +1 -1
- package/dist/bridge.js +3 -1
- package/dist/bridge.js.map +1 -1
- package/dist/claudeOrchestrator.js +5 -2
- package/dist/claudeOrchestrator.js.map +1 -1
- package/dist/commands/recipe.js +10 -1
- package/dist/commands/recipe.js.map +1 -1
- package/dist/config.d.ts +8 -0
- package/dist/config.js +19 -0
- package/dist/config.js.map +1 -1
- package/dist/connectors/baseConnector.js +25 -3
- package/dist/connectors/baseConnector.js.map +1 -1
- package/dist/drivers/gemini/index.d.ts +22 -0
- package/dist/drivers/gemini/index.js +240 -129
- package/dist/drivers/gemini/index.js.map +1 -1
- package/dist/drivers/local/index.d.ts +17 -0
- package/dist/drivers/local/index.js +99 -0
- package/dist/drivers/local/index.js.map +1 -1
- package/dist/drivers/openai/index.js +30 -2
- package/dist/drivers/openai/index.js.map +1 -1
- package/dist/extensionClient.d.ts +8 -0
- package/dist/extensionClient.js +24 -2
- package/dist/extensionClient.js.map +1 -1
- package/dist/fp/automationInterpreter.d.ts +9 -1
- package/dist/fp/automationInterpreter.js +151 -34
- package/dist/fp/automationInterpreter.js.map +1 -1
- package/dist/fp/automationProgram.d.ts +30 -0
- package/dist/fp/automationProgram.js.map +1 -1
- package/dist/fp/automationState.d.ts +23 -4
- package/dist/fp/automationState.js +28 -4
- package/dist/fp/automationState.js.map +1 -1
- package/dist/fp/interpreterContext.d.ts +66 -1
- package/dist/fp/interpreterContext.js +140 -1
- package/dist/fp/interpreterContext.js.map +1 -1
- package/dist/fp/policyParser.js +29 -1
- package/dist/fp/policyParser.js.map +1 -1
- package/dist/oauth.d.ts +9 -0
- package/dist/oauth.js +33 -0
- package/dist/oauth.js.map +1 -1
- package/dist/patchworkConfig.d.ts +16 -0
- package/dist/patchworkConfig.js.map +1 -1
- package/dist/recipes/scheduler.d.ts +7 -0
- package/dist/recipes/scheduler.js +30 -13
- package/dist/recipes/scheduler.js.map +1 -1
- package/dist/recipes/schema.d.ts +6 -0
- package/dist/recipes/tools/file.js +5 -2
- package/dist/recipes/tools/file.js.map +1 -1
- package/dist/recipes/yamlRunner.d.ts +17 -0
- package/dist/recipes/yamlRunner.js +60 -3
- package/dist/recipes/yamlRunner.js.map +1 -1
- package/dist/recipesHttp.d.ts +3 -1
- package/dist/recipesHttp.js +9 -3
- package/dist/recipesHttp.js.map +1 -1
- package/dist/server.d.ts +46 -1
- package/dist/server.js +178 -3
- package/dist/server.js.map +1 -1
- package/dist/streamableHttp.d.ts +9 -4
- package/dist/streamableHttp.js +17 -9
- package/dist/streamableHttp.js.map +1 -1
- package/dist/tools/openInBrowser.js +6 -1
- package/dist/tools/openInBrowser.js.map +1 -1
- package/dist/tools/utils.js +7 -4
- package/dist/tools/utils.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { chmodSync, existsSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { isAbsolute, join, resolve } from "node:path";
|
|
5
5
|
import { sanitizeEnv } from "../claude/envSanitizer.js";
|
|
@@ -21,27 +21,99 @@ export class GeminiSubprocessDriver {
|
|
|
21
21
|
log;
|
|
22
22
|
bridgeMcp;
|
|
23
23
|
name = "gemini";
|
|
24
|
+
/**
|
|
25
|
+
* Process-wide serialization for `~/.gemini/settings.json` mutation. Two
|
|
26
|
+
* concurrent `run()` invocations would otherwise race:
|
|
27
|
+
*
|
|
28
|
+
* A reads file (originalContent = X)
|
|
29
|
+
* A writes A's-token
|
|
30
|
+
* B reads file (originalContent = A's-token) ← B captures wrong baseline
|
|
31
|
+
* B writes B's-token
|
|
32
|
+
* A's child starts, reads settings — sees B's-token (uses wrong creds)
|
|
33
|
+
* A finishes, restores to X (wipes B's token mid-flight)
|
|
34
|
+
* B's child reads settings — sees X (no MCP config)
|
|
35
|
+
* B finishes, restores to A's-token (token leaks past run)
|
|
36
|
+
*
|
|
37
|
+
* The settings file is global to ~/.gemini/, so per-call isolation requires
|
|
38
|
+
* either Gemini-CLI's `--settings <path>` (not universal across versions)
|
|
39
|
+
* or a strict mutex across the whole run() lifetime. We take the mutex
|
|
40
|
+
* approach — Gemini subprocess runs are slow (seconds-to-minutes) and the
|
|
41
|
+
* operator-driven concurrency level is already low, so the throughput
|
|
42
|
+
* cost is acceptable for cross-version correctness.
|
|
43
|
+
*/
|
|
44
|
+
static settingsMutex = Promise.resolve();
|
|
24
45
|
constructor(binary, log, bridgeMcp) {
|
|
25
46
|
this.binary = binary;
|
|
26
47
|
this.log = log;
|
|
27
48
|
this.bridgeMcp = bridgeMcp;
|
|
28
49
|
}
|
|
29
50
|
async run(input) {
|
|
51
|
+
// Resolve bridgeMcp ONCE per run() and pass the result down. The
|
|
52
|
+
// closure may be cheap today, but calling it twice (once at the lock
|
|
53
|
+
// gate, once inside _runLocked) opens a TOCTOU window: if the value
|
|
54
|
+
// ever flips falsy→truthy between the two calls, the gate skips the
|
|
55
|
+
// mutex but _runLocked writes settings.json without a lock.
|
|
56
|
+
const mcp = this.bridgeMcp?.();
|
|
57
|
+
if (mcp) {
|
|
58
|
+
// If we're going to mutate ~/.gemini/settings.json, wait for any prior
|
|
59
|
+
// Gemini run holding the same file to finish first.
|
|
60
|
+
const prior = GeminiSubprocessDriver.settingsMutex;
|
|
61
|
+
let releaseLock;
|
|
62
|
+
const ourLock = new Promise((resolve) => {
|
|
63
|
+
releaseLock = resolve;
|
|
64
|
+
});
|
|
65
|
+
GeminiSubprocessDriver.settingsMutex = ourLock;
|
|
66
|
+
try {
|
|
67
|
+
await prior;
|
|
68
|
+
return await this._runLocked(input, mcp);
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
releaseLock();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// No mcp injection → no settings file write → no mutex needed.
|
|
75
|
+
return this._runLocked(input, undefined);
|
|
76
|
+
}
|
|
77
|
+
async _runLocked(input, mcp) {
|
|
30
78
|
const opts = input.providerOptions ?? {};
|
|
31
79
|
const approvalMode = typeof opts.approvalMode === "string" ? opts.approvalMode : "yolo";
|
|
32
80
|
// Inject bridge MCP into ~/.gemini/settings.json before spawning so the
|
|
33
|
-
// subprocess can call bridge tools. Gemini CLI reads settings.json at
|
|
34
|
-
|
|
81
|
+
// subprocess can call bridge tools. Gemini CLI reads settings.json at
|
|
82
|
+
// startup. We snapshot whatever was there before (or remember "absent")
|
|
83
|
+
// and restore it in a finally block at the end of run() — the bearer
|
|
84
|
+
// token must NOT outlive this single invocation in a shared-home file.
|
|
85
|
+
//
|
|
86
|
+
// URL is rewritten to 127.0.0.1:<port> for the spawned subprocess: the
|
|
87
|
+
// bridge may be bound 0.0.0.0 with a public --issuer-url, but the local
|
|
88
|
+
// child should always dial loopback so neither the URL nor the token
|
|
89
|
+
// ever leave this machine.
|
|
90
|
+
let settingsCleanup = null;
|
|
35
91
|
if (mcp) {
|
|
36
92
|
const settingsFile = join(homedir(), ".gemini", "settings.json");
|
|
37
93
|
try {
|
|
94
|
+
let originalContent = null;
|
|
38
95
|
let settings = {};
|
|
39
96
|
if (existsSync(settingsFile)) {
|
|
40
|
-
|
|
97
|
+
originalContent = readFileSync(settingsFile, "utf-8");
|
|
98
|
+
settings = JSON.parse(originalContent);
|
|
41
99
|
}
|
|
42
100
|
const mcpServers = (settings.mcpServers ?? {});
|
|
101
|
+
const previousBridgeEntry = Object.hasOwn(mcpServers, "claude-ide-bridge")
|
|
102
|
+
? mcpServers["claude-ide-bridge"]
|
|
103
|
+
: undefined;
|
|
104
|
+
const localUrl = (() => {
|
|
105
|
+
try {
|
|
106
|
+
const u = new URL(mcp.url);
|
|
107
|
+
// Force loopback — keeps token off the wire even if bridge bound 0.0.0.0
|
|
108
|
+
u.hostname = "127.0.0.1";
|
|
109
|
+
return u.toString();
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return mcp.url;
|
|
113
|
+
}
|
|
114
|
+
})();
|
|
43
115
|
mcpServers["claude-ide-bridge"] = {
|
|
44
|
-
url:
|
|
116
|
+
url: localUrl,
|
|
45
117
|
headers: { Authorization: `Bearer ${mcp.authToken}` },
|
|
46
118
|
};
|
|
47
119
|
settings.mcpServers = mcpServers;
|
|
@@ -49,159 +121,198 @@ export class GeminiSubprocessDriver {
|
|
|
49
121
|
mode: 0o600,
|
|
50
122
|
});
|
|
51
123
|
chmodSync(settingsFile, 0o600);
|
|
124
|
+
// Schedule restoration. If the original file existed, write back its
|
|
125
|
+
// exact bytes; if the bridge entry was absent, remove the key. If the
|
|
126
|
+
// file did not exist before, delete it. Best-effort; logged on failure.
|
|
127
|
+
settingsCleanup = () => {
|
|
128
|
+
try {
|
|
129
|
+
if (originalContent === null) {
|
|
130
|
+
// File was created by us — try to remove. Tolerate ENOENT.
|
|
131
|
+
try {
|
|
132
|
+
unlinkSync(settingsFile);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
/* ignore */
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const parsed = JSON.parse(originalContent);
|
|
140
|
+
if (previousBridgeEntry === undefined) {
|
|
141
|
+
const restoredServers = (parsed.mcpServers ?? {});
|
|
142
|
+
if (Object.hasOwn(restoredServers, "claude-ide-bridge")) {
|
|
143
|
+
delete restoredServers["claude-ide-bridge"];
|
|
144
|
+
}
|
|
145
|
+
parsed.mcpServers = restoredServers;
|
|
146
|
+
}
|
|
147
|
+
writeFileSync(settingsFile, JSON.stringify(parsed, null, 2), {
|
|
148
|
+
mode: 0o600,
|
|
149
|
+
});
|
|
150
|
+
chmodSync(settingsFile, 0o600);
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
this.log(`[GeminiSubprocessDriver] WARN: could not restore ~/.gemini/settings.json: ${err instanceof Error ? err.message : String(err)}`);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
52
156
|
}
|
|
53
157
|
catch (err) {
|
|
54
158
|
this.log(`[GeminiSubprocessDriver] WARN: could not update ~/.gemini/settings.json: ${err instanceof Error ? err.message : String(err)}`);
|
|
55
159
|
}
|
|
56
160
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
// Strip MCP_* and CLAUDECODE vars; preserve GEMINI_API_KEY + GOOGLE_* vars
|
|
75
|
-
const env = sanitizeEnv(process.env);
|
|
76
|
-
// Also strip Claude-specific auth vars that could confuse Gemini
|
|
77
|
-
for (const key of Object.keys(env)) {
|
|
78
|
-
if (key.startsWith("ANTHROPIC_") || key === "CLAUDE_API_KEY") {
|
|
79
|
-
delete env[key];
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
this.log(`[GeminiSubprocessDriver] spawning: ${this.binary} -p <prompt> (workspace: ${input.workspace})`);
|
|
83
|
-
const child = spawn(this.binary, args, {
|
|
84
|
-
cwd: homedir(),
|
|
85
|
-
env,
|
|
86
|
-
signal: input.signal,
|
|
87
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
88
|
-
});
|
|
89
|
-
// unref() so the bridge can exit without waiting for the subprocess,
|
|
90
|
-
// but keep detached=false so the subprocess dies cleanly with the bridge
|
|
91
|
-
// rather than getting SIGPIPE when the pipe closes mid-run.
|
|
92
|
-
child.unref();
|
|
93
|
-
let lineBuf = "";
|
|
94
|
-
let accumulated = "";
|
|
95
|
-
let outputBytesSent = 0;
|
|
96
|
-
let firstAssistantAt;
|
|
97
|
-
let doneFromResult = false;
|
|
98
|
-
let resultSuccess = true;
|
|
99
|
-
child.stdout.setEncoding("utf-8");
|
|
100
|
-
child.stdout.on("data", (chunk) => {
|
|
101
|
-
const { lines, remainder } = splitLines(lineBuf, chunk);
|
|
102
|
-
lineBuf = remainder;
|
|
103
|
-
for (const line of lines) {
|
|
104
|
-
if (line.trim() === "")
|
|
105
|
-
continue;
|
|
106
|
-
let event;
|
|
107
|
-
try {
|
|
108
|
-
event = JSON.parse(line);
|
|
161
|
+
try {
|
|
162
|
+
const args = [
|
|
163
|
+
"-p",
|
|
164
|
+
input.prompt,
|
|
165
|
+
"--output-format",
|
|
166
|
+
"stream-json",
|
|
167
|
+
"--approval-mode",
|
|
168
|
+
approvalMode,
|
|
169
|
+
];
|
|
170
|
+
if (input.model)
|
|
171
|
+
args.push("-m", input.model);
|
|
172
|
+
// contextFiles: pass as --include-directories; normalize relative paths against workspace
|
|
173
|
+
for (const f of input.contextFiles ?? []) {
|
|
174
|
+
if (typeof f === "string" && f.length > 0 && !f.startsWith("-")) {
|
|
175
|
+
const abs = isAbsolute(f) ? f : resolve(input.workspace, f);
|
|
176
|
+
args.push("--include-directories", abs);
|
|
109
177
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
178
|
+
}
|
|
179
|
+
// Strip MCP_* and CLAUDECODE vars; preserve GEMINI_API_KEY + GOOGLE_* vars
|
|
180
|
+
const env = sanitizeEnv(process.env);
|
|
181
|
+
// Also strip Claude-specific auth vars that could confuse Gemini
|
|
182
|
+
for (const key of Object.keys(env)) {
|
|
183
|
+
if (key.startsWith("ANTHROPIC_") || key === "CLAUDE_API_KEY") {
|
|
184
|
+
delete env[key];
|
|
113
185
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
186
|
+
}
|
|
187
|
+
this.log(`[GeminiSubprocessDriver] spawning: ${this.binary} -p <prompt> (workspace: ${input.workspace})`);
|
|
188
|
+
const child = spawn(this.binary, args, {
|
|
189
|
+
cwd: homedir(),
|
|
190
|
+
env,
|
|
191
|
+
signal: input.signal,
|
|
192
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
193
|
+
});
|
|
194
|
+
// unref() so the bridge can exit without waiting for the subprocess,
|
|
195
|
+
// but keep detached=false so the subprocess dies cleanly with the bridge
|
|
196
|
+
// rather than getting SIGPIPE when the pipe closes mid-run.
|
|
197
|
+
child.unref();
|
|
198
|
+
let lineBuf = "";
|
|
199
|
+
let accumulated = "";
|
|
200
|
+
let outputBytesSent = 0;
|
|
201
|
+
let firstAssistantAt;
|
|
202
|
+
let doneFromResult = false;
|
|
203
|
+
let resultSuccess = true;
|
|
204
|
+
child.stdout.setEncoding("utf-8");
|
|
205
|
+
child.stdout.on("data", (chunk) => {
|
|
206
|
+
const { lines, remainder } = splitLines(lineBuf, chunk);
|
|
207
|
+
lineBuf = remainder;
|
|
208
|
+
for (const line of lines) {
|
|
209
|
+
if (line.trim() === "")
|
|
210
|
+
continue;
|
|
211
|
+
let event;
|
|
212
|
+
try {
|
|
213
|
+
event = JSON.parse(line);
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
// Non-JSON stderr/warning lines — skip (Gemini prints "YOLO mode..." to stdout)
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (event.type === "message" &&
|
|
220
|
+
event.role === "assistant" &&
|
|
221
|
+
event.content) {
|
|
222
|
+
if (firstAssistantAt === undefined)
|
|
223
|
+
firstAssistantAt = Date.now();
|
|
224
|
+
const text = event.content;
|
|
225
|
+
accumulated += text;
|
|
226
|
+
if (outputBytesSent < OUTPUT_CAP) {
|
|
227
|
+
const send = text.slice(0, OUTPUT_CAP - outputBytesSent);
|
|
228
|
+
if (send.length > 0) {
|
|
229
|
+
input.onChunk?.(send);
|
|
230
|
+
outputBytesSent += send.length;
|
|
231
|
+
}
|
|
126
232
|
}
|
|
127
233
|
}
|
|
234
|
+
else if (event.type === "result") {
|
|
235
|
+
doneFromResult = true;
|
|
236
|
+
resultSuccess = event.status === "success";
|
|
237
|
+
}
|
|
128
238
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
239
|
+
});
|
|
240
|
+
let stderr = "";
|
|
241
|
+
child.stderr.setEncoding("utf-8");
|
|
242
|
+
child.stderr.on("data", (chunk) => {
|
|
243
|
+
if (stderr.length < OUTPUT_CAP) {
|
|
244
|
+
stderr += chunk;
|
|
245
|
+
if (stderr.length > OUTPUT_CAP)
|
|
246
|
+
stderr = stderr.slice(0, OUTPUT_CAP);
|
|
132
247
|
}
|
|
248
|
+
});
|
|
249
|
+
const start = Date.now();
|
|
250
|
+
const stderrTailOf = (s) => s.length > 0 ? scrubSecrets(s.slice(-2048)) : undefined;
|
|
251
|
+
const startupMsOf = () => firstAssistantAt !== undefined ? firstAssistantAt - start : undefined;
|
|
252
|
+
let startupTimedOut = false;
|
|
253
|
+
const startupHandle = input.startupTimeoutMs
|
|
254
|
+
? setTimeout(() => {
|
|
255
|
+
if (firstAssistantAt === undefined && !doneFromResult) {
|
|
256
|
+
startupTimedOut = true;
|
|
257
|
+
child.kill();
|
|
258
|
+
}
|
|
259
|
+
}, input.startupTimeoutMs)
|
|
260
|
+
: null;
|
|
261
|
+
let exitCode;
|
|
262
|
+
try {
|
|
263
|
+
exitCode = await new Promise((resolve, reject) => {
|
|
264
|
+
child.on("close", (code) => resolve(code ?? 0));
|
|
265
|
+
child.on("error", reject);
|
|
266
|
+
});
|
|
133
267
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
let startupTimedOut = false;
|
|
148
|
-
const startupHandle = input.startupTimeoutMs
|
|
149
|
-
? setTimeout(() => {
|
|
150
|
-
if (firstAssistantAt === undefined && !doneFromResult) {
|
|
151
|
-
startupTimedOut = true;
|
|
152
|
-
child.kill();
|
|
268
|
+
catch (err) {
|
|
269
|
+
if (startupHandle)
|
|
270
|
+
clearTimeout(startupHandle);
|
|
271
|
+
const isAbort = (err instanceof Error && err.name === "AbortError") ||
|
|
272
|
+
input.signal.aborted;
|
|
273
|
+
if (isAbort) {
|
|
274
|
+
return {
|
|
275
|
+
text: accumulated.slice(0, OUTPUT_CAP),
|
|
276
|
+
durationMs: Date.now() - start,
|
|
277
|
+
wasAborted: true,
|
|
278
|
+
startupMs: startupMsOf(),
|
|
279
|
+
stderrTail: stderrTailOf(stderr),
|
|
280
|
+
};
|
|
153
281
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
let exitCode;
|
|
157
|
-
try {
|
|
158
|
-
exitCode = await new Promise((resolve, reject) => {
|
|
159
|
-
child.on("close", (code) => resolve(code ?? 0));
|
|
160
|
-
child.on("error", reject);
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
catch (err) {
|
|
282
|
+
throw err;
|
|
283
|
+
}
|
|
164
284
|
if (startupHandle)
|
|
165
285
|
clearTimeout(startupHandle);
|
|
166
|
-
|
|
167
|
-
input.signal.aborted;
|
|
168
|
-
if (isAbort) {
|
|
286
|
+
if (startupTimedOut) {
|
|
169
287
|
return {
|
|
170
288
|
text: accumulated.slice(0, OUTPUT_CAP),
|
|
171
289
|
durationMs: Date.now() - start,
|
|
172
290
|
wasAborted: true,
|
|
173
|
-
|
|
291
|
+
startupTimedOut: true,
|
|
174
292
|
stderrTail: stderrTailOf(stderr),
|
|
175
293
|
};
|
|
176
294
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
295
|
+
const effectiveExitCode = doneFromResult
|
|
296
|
+
? resultSuccess
|
|
297
|
+
? 0
|
|
298
|
+
: 1
|
|
299
|
+
: exitCode;
|
|
300
|
+
if (effectiveExitCode !== 0 && stderr) {
|
|
301
|
+
this.log(`[GeminiSubprocessDriver] stderr: ${stderr.slice(0, 500)}`);
|
|
302
|
+
}
|
|
182
303
|
return {
|
|
183
304
|
text: accumulated.slice(0, OUTPUT_CAP),
|
|
305
|
+
exitCode: effectiveExitCode,
|
|
184
306
|
durationMs: Date.now() - start,
|
|
185
|
-
wasAborted: true,
|
|
186
|
-
startupTimedOut: true,
|
|
187
307
|
stderrTail: stderrTailOf(stderr),
|
|
308
|
+
startupMs: startupMsOf(),
|
|
188
309
|
};
|
|
189
310
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
: exitCode;
|
|
195
|
-
if (effectiveExitCode !== 0 && stderr) {
|
|
196
|
-
this.log(`[GeminiSubprocessDriver] stderr: ${stderr.slice(0, 500)}`);
|
|
311
|
+
finally {
|
|
312
|
+
// Always restore ~/.gemini/settings.json — bridge bearer token must
|
|
313
|
+
// not survive in this shared-home file past one invocation.
|
|
314
|
+
settingsCleanup?.();
|
|
197
315
|
}
|
|
198
|
-
return {
|
|
199
|
-
text: accumulated.slice(0, OUTPUT_CAP),
|
|
200
|
-
exitCode: effectiveExitCode,
|
|
201
|
-
durationMs: Date.now() - start,
|
|
202
|
-
stderrTail: stderrTailOf(stderr),
|
|
203
|
-
startupMs: startupMsOf(),
|
|
204
|
-
};
|
|
205
316
|
}
|
|
206
317
|
async runOutcome(input) {
|
|
207
318
|
return toProviderTaskOutcome(await this.run(input));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/drivers/gemini/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/drivers/gemini/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
import { OpenAIApiDriver } from "../openai/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Reject any LOCAL_ENDPOINT that doesn't resolve to loopback or RFC1918
|
|
4
|
+
* private space. The local driver streams the user prompt + context-file
|
|
5
|
+
* paths to whatever URL is set; if a malicious recipe (or a user pasting
|
|
6
|
+
* a phishy "free local LLM" link) sets `LOCAL_ENDPOINT=https://attacker/v1`,
|
|
7
|
+
* everything the local driver receives gets exfiltrated.
|
|
8
|
+
*
|
|
9
|
+
* Rule: hostname must be one of
|
|
10
|
+
* - localhost / 127.0.0.1 / ::1
|
|
11
|
+
* - RFC1918 private space (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
|
|
12
|
+
* - link-local (169.254.0.0/16, fe80::/10)
|
|
13
|
+
* - .local mDNS / .lan / .home / .internal suffixes
|
|
14
|
+
*
|
|
15
|
+
* To opt out (e.g. authorized remote inference cluster), the operator can
|
|
16
|
+
* set LOCAL_ENDPOINT_ALLOW_REMOTE=1 — explicit override, audited via env.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isLoopbackOrPrivateEndpoint(rawUrl: string): boolean;
|
|
2
19
|
/**
|
|
3
20
|
* Local LLM driver — Ollama, LM Studio, vLLM, llama.cpp server, and most
|
|
4
21
|
* other self-hosted runtimes expose an OpenAI-compatible chat-completions
|
|
@@ -1,4 +1,96 @@
|
|
|
1
1
|
import { OpenAIApiDriver } from "../openai/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Reject any LOCAL_ENDPOINT that doesn't resolve to loopback or RFC1918
|
|
4
|
+
* private space. The local driver streams the user prompt + context-file
|
|
5
|
+
* paths to whatever URL is set; if a malicious recipe (or a user pasting
|
|
6
|
+
* a phishy "free local LLM" link) sets `LOCAL_ENDPOINT=https://attacker/v1`,
|
|
7
|
+
* everything the local driver receives gets exfiltrated.
|
|
8
|
+
*
|
|
9
|
+
* Rule: hostname must be one of
|
|
10
|
+
* - localhost / 127.0.0.1 / ::1
|
|
11
|
+
* - RFC1918 private space (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
|
|
12
|
+
* - link-local (169.254.0.0/16, fe80::/10)
|
|
13
|
+
* - .local mDNS / .lan / .home / .internal suffixes
|
|
14
|
+
*
|
|
15
|
+
* To opt out (e.g. authorized remote inference cluster), the operator can
|
|
16
|
+
* set LOCAL_ENDPOINT_ALLOW_REMOTE=1 — explicit override, audited via env.
|
|
17
|
+
*/
|
|
18
|
+
export function isLoopbackOrPrivateEndpoint(rawUrl) {
|
|
19
|
+
let host;
|
|
20
|
+
let wasBracketed = false;
|
|
21
|
+
try {
|
|
22
|
+
host = new URL(rawUrl).hostname.toLowerCase();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (!host)
|
|
28
|
+
return false;
|
|
29
|
+
// Strip IPv6 brackets if any
|
|
30
|
+
if (host.startsWith("[") && host.endsWith("]")) {
|
|
31
|
+
host = host.slice(1, -1);
|
|
32
|
+
wasBracketed = true;
|
|
33
|
+
}
|
|
34
|
+
if (host === "localhost" || host === "127.0.0.1" || host === "::1") {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
// IPv4 RFC1918 + link-local + 127/8 (check first — strict literal match)
|
|
38
|
+
const v4 = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
39
|
+
if (v4) {
|
|
40
|
+
const a = Number(v4[1]);
|
|
41
|
+
const b = Number(v4[2]);
|
|
42
|
+
if (a === 10)
|
|
43
|
+
return true;
|
|
44
|
+
if (a === 127)
|
|
45
|
+
return true;
|
|
46
|
+
if (a === 169 && b === 254)
|
|
47
|
+
return true;
|
|
48
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
49
|
+
return true;
|
|
50
|
+
if (a === 192 && b === 168)
|
|
51
|
+
return true;
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
// IPv6 ranges — only when the input looked like an IPv6 literal (was
|
|
55
|
+
// bracketed in the URL, or matches an IPv6 syntactic shape). Without this
|
|
56
|
+
// gate, a hostname like `fc-services.example.com` or `fe8a-test` would
|
|
57
|
+
// match the legacy `startsWith("fc")` / `startsWith("fe8")` checks and
|
|
58
|
+
// bypass the validator. RFC3986 allows IPv6 literals only inside `[…]`,
|
|
59
|
+
// so requiring the brackets is the conservative path.
|
|
60
|
+
if (wasBracketed || /^[0-9a-f:]+$/.test(host)) {
|
|
61
|
+
if (host.startsWith("fe8") ||
|
|
62
|
+
host.startsWith("fe9") ||
|
|
63
|
+
host.startsWith("fea") ||
|
|
64
|
+
host.startsWith("feb")) {
|
|
65
|
+
return true; // link-local fe80::/10
|
|
66
|
+
}
|
|
67
|
+
if (host.startsWith("fc") || host.startsWith("fd")) {
|
|
68
|
+
return true; // unique-local fc00::/7
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// mDNS / common LAN suffixes — checked LAST and require a leading dot
|
|
72
|
+
// boundary so attacker-registered public domains like `evil.com.local`
|
|
73
|
+
// don't slip through (`.local` and `.lan` are not reserved TLDs at the
|
|
74
|
+
// DNS resolver level — only RFC6762 reserves them for mDNS, but public
|
|
75
|
+
// resolvers will happily look them up).
|
|
76
|
+
//
|
|
77
|
+
// The trade-off: a host literally named `printer.local` (single label
|
|
78
|
+
// before `.local`) is the legitimate mDNS form and matches; a host like
|
|
79
|
+
// `evil.com.local` (two-or-more labels before `.local`) is only the
|
|
80
|
+
// mDNS form by accident and is the bypass we're rejecting.
|
|
81
|
+
for (const suffix of [".local", ".lan", ".home", ".internal"]) {
|
|
82
|
+
if (host.endsWith(suffix)) {
|
|
83
|
+
const labels = host.split(".");
|
|
84
|
+
// 2 labels exactly: `<name>.local` — legitimate mDNS shape.
|
|
85
|
+
if (labels.length === 2)
|
|
86
|
+
return true;
|
|
87
|
+
// More labels: ambiguous — refuse by default. Operator can opt out
|
|
88
|
+
// via LOCAL_ENDPOINT_ALLOW_REMOTE for legitimate multi-label
|
|
89
|
+
// internal DNS zones (e.g. `service.team.internal`).
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
2
94
|
/**
|
|
3
95
|
* Local LLM driver — Ollama, LM Studio, vLLM, llama.cpp server, and most
|
|
4
96
|
* other self-hosted runtimes expose an OpenAI-compatible chat-completions
|
|
@@ -26,6 +118,13 @@ export class LocalApiDriver extends OpenAIApiDriver {
|
|
|
26
118
|
if (!baseURL) {
|
|
27
119
|
throw new Error("LocalApiDriver requires LOCAL_ENDPOINT environment variable (e.g. http://localhost:11434/v1)");
|
|
28
120
|
}
|
|
121
|
+
if (process.env.LOCAL_ENDPOINT_ALLOW_REMOTE !== "1" &&
|
|
122
|
+
!isLoopbackOrPrivateEndpoint(baseURL)) {
|
|
123
|
+
throw new Error(`LocalApiDriver: LOCAL_ENDPOINT="${baseURL}" is not loopback or private. ` +
|
|
124
|
+
`The local driver streams prompts + context to this URL — a public host ` +
|
|
125
|
+
`would exfiltrate them. Set LOCAL_ENDPOINT_ALLOW_REMOTE=1 to override ` +
|
|
126
|
+
`(only for audited internal inference clusters).`);
|
|
127
|
+
}
|
|
29
128
|
super(log, {
|
|
30
129
|
baseURL,
|
|
31
130
|
// Per-install default — caller can still override via input.model.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/drivers/local/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,cAAe,SAAQ,eAAe;IAC/B,IAAI,GAAG,OAAO,CAAC;IAEjC,YAAY,GAA0B;QACpC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;QAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,8FAA8F,CAC/F,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,GAAG,EAAE;YACT,OAAO;YACP,mEAAmE;YACnE,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,UAAU;SACpD,CAAC,CAAC;IACL,CAAC;IAEkB,MAAM;QACvB,mEAAmE;QACnE,6DAA6D;QAC7D,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,QAAQ,CAAC;IAC/C,CAAC;CACF"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/drivers/local/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,2BAA2B,CAAC,MAAc;IACxD,IAAI,IAAY,CAAC;IACjB,IAAI,YAAY,GAAG,KAAK,CAAC;IACzB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,6BAA6B;IAC7B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACzB,YAAY,GAAG,IAAI,CAAC;IACtB,CAAC;IACD,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACnE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,yEAAyE;IACzE,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;IACtE,IAAI,EAAE,EAAE,CAAC;QACP,MAAM,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACxB,MAAM,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACxB,IAAI,CAAC,KAAK,EAAE;YAAE,OAAO,IAAI,CAAC;QAC1B,IAAI,CAAC,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAC3B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QACxC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;YAAE,OAAO,IAAI,CAAC;QACjD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QACxC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,qEAAqE;IACrE,0EAA0E;IAC1E,uEAAuE;IACvE,uEAAuE;IACvE,wEAAwE;IACxE,sDAAsD;IACtD,IAAI,YAAY,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9C,IACE,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;YACtB,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;YACtB,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;YACtB,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EACtB,CAAC;YACD,OAAO,IAAI,CAAC,CAAC,uBAAuB;QACtC,CAAC;QACD,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,OAAO,IAAI,CAAC,CAAC,wBAAwB;QACvC,CAAC;IACH,CAAC;IACD,sEAAsE;IACtE,uEAAuE;IACvE,uEAAuE;IACvE,uEAAuE;IACvE,wCAAwC;IACxC,EAAE;IACF,sEAAsE;IACtE,wEAAwE;IACxE,oEAAoE;IACpE,2DAA2D;IAC3D,KAAK,MAAM,MAAM,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,CAAC;QAC9D,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC/B,4DAA4D;YAC5D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YACrC,mEAAmE;YACnE,6DAA6D;YAC7D,qDAAqD;QACvD,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,cAAe,SAAQ,eAAe;IAC/B,IAAI,GAAG,OAAO,CAAC;IAEjC,YAAY,GAA0B;QACpC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;QAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,8FAA8F,CAC/F,CAAC;QACJ,CAAC;QACD,IACE,OAAO,CAAC,GAAG,CAAC,2BAA2B,KAAK,GAAG;YAC/C,CAAC,2BAA2B,CAAC,OAAO,CAAC,EACrC,CAAC;YACD,MAAM,IAAI,KAAK,CACb,mCAAmC,OAAO,gCAAgC;gBACxE,yEAAyE;gBACzE,uEAAuE;gBACvE,iDAAiD,CACpD,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,GAAG,EAAE;YACT,OAAO;YACP,mEAAmE;YACnE,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,UAAU;SACpD,CAAC,CAAC;IACL,CAAC;IAEkB,MAAM;QACvB,mEAAmE;QACnE,6DAA6D;QAC7D,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,QAAQ,CAAC;IAC/C,CAAC;CACF"}
|