patchwork-os 0.2.0-beta.1 → 0.2.0-beta.3

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 (191) hide show
  1. package/README.bridge.md +5 -5
  2. package/README.md +156 -12
  3. package/deploy/deploy-dashboard.sh +25 -1
  4. package/deploy/macos/README.md +153 -0
  5. package/deploy/macos/com.patchwork.bridge.plist.template +54 -0
  6. package/deploy/macos/com.patchwork.tunnel.plist.template +76 -0
  7. package/deploy/macos/install-mac-bridge.sh +244 -0
  8. package/deploy/macos/uninstall-mac-bridge.sh +22 -0
  9. package/dist/activityLog.d.ts +6 -0
  10. package/dist/activityLog.js +8 -0
  11. package/dist/activityLog.js.map +1 -1
  12. package/dist/analyticsPrefs.d.ts +35 -2
  13. package/dist/analyticsPrefs.js +120 -21
  14. package/dist/analyticsPrefs.js.map +1 -1
  15. package/dist/analyticsSend.js +5 -1
  16. package/dist/analyticsSend.js.map +1 -1
  17. package/dist/approvalHttp.d.ts +14 -0
  18. package/dist/approvalHttp.js +172 -1
  19. package/dist/approvalHttp.js.map +1 -1
  20. package/dist/approvalQueue.d.ts +27 -2
  21. package/dist/approvalQueue.js +44 -7
  22. package/dist/approvalQueue.js.map +1 -1
  23. package/dist/automation.d.ts +34 -3
  24. package/dist/automation.js +85 -10
  25. package/dist/automation.js.map +1 -1
  26. package/dist/bridge.d.ts +2 -0
  27. package/dist/bridge.js +114 -8
  28. package/dist/bridge.js.map +1 -1
  29. package/dist/bridgeLockDiscovery.d.ts +27 -1
  30. package/dist/bridgeLockDiscovery.js +37 -11
  31. package/dist/bridgeLockDiscovery.js.map +1 -1
  32. package/dist/claudeOrchestrator.js +5 -2
  33. package/dist/claudeOrchestrator.js.map +1 -1
  34. package/dist/commands/patchworkInit.d.ts +5 -0
  35. package/dist/commands/patchworkInit.js +86 -7
  36. package/dist/commands/patchworkInit.js.map +1 -1
  37. package/dist/commands/recipe.d.ts +51 -0
  38. package/dist/commands/recipe.js +363 -3
  39. package/dist/commands/recipe.js.map +1 -1
  40. package/dist/commands/recipeInstall.js +6 -3
  41. package/dist/commands/recipeInstall.js.map +1 -1
  42. package/dist/commands/task.js +2 -2
  43. package/dist/commands/task.js.map +1 -1
  44. package/dist/config.d.ts +17 -2
  45. package/dist/config.js +54 -17
  46. package/dist/config.js.map +1 -1
  47. package/dist/connectors/baseConnector.js +25 -3
  48. package/dist/connectors/baseConnector.js.map +1 -1
  49. package/dist/connectors/tokenStorage.js +46 -10
  50. package/dist/connectors/tokenStorage.js.map +1 -1
  51. package/dist/drivers/gemini/index.d.ts +22 -0
  52. package/dist/drivers/gemini/index.js +240 -129
  53. package/dist/drivers/gemini/index.js.map +1 -1
  54. package/dist/drivers/local/index.d.ts +17 -0
  55. package/dist/drivers/local/index.js +99 -0
  56. package/dist/drivers/local/index.js.map +1 -1
  57. package/dist/drivers/openai/index.js +30 -2
  58. package/dist/drivers/openai/index.js.map +1 -1
  59. package/dist/extensionClient.d.ts +8 -0
  60. package/dist/extensionClient.js +24 -2
  61. package/dist/extensionClient.js.map +1 -1
  62. package/dist/featureFlags.d.ts +76 -0
  63. package/dist/featureFlags.js +166 -2
  64. package/dist/featureFlags.js.map +1 -1
  65. package/dist/fp/automationInterpreter.d.ts +9 -1
  66. package/dist/fp/automationInterpreter.js +151 -34
  67. package/dist/fp/automationInterpreter.js.map +1 -1
  68. package/dist/fp/automationProgram.d.ts +30 -0
  69. package/dist/fp/automationProgram.js.map +1 -1
  70. package/dist/fp/automationState.d.ts +23 -4
  71. package/dist/fp/automationState.js +28 -4
  72. package/dist/fp/automationState.js.map +1 -1
  73. package/dist/fp/interpreterContext.d.ts +66 -1
  74. package/dist/fp/interpreterContext.js +140 -1
  75. package/dist/fp/interpreterContext.js.map +1 -1
  76. package/dist/fp/policyParser.js +29 -1
  77. package/dist/fp/policyParser.js.map +1 -1
  78. package/dist/index.js +765 -69
  79. package/dist/index.js.map +1 -1
  80. package/dist/lockfile.js +4 -1
  81. package/dist/lockfile.js.map +1 -1
  82. package/dist/oauth.d.ts +9 -0
  83. package/dist/oauth.js +33 -0
  84. package/dist/oauth.js.map +1 -1
  85. package/dist/patchworkConfig.d.ts +16 -0
  86. package/dist/patchworkConfig.js +5 -0
  87. package/dist/patchworkConfig.js.map +1 -1
  88. package/dist/recipeOrchestration.js +35 -1
  89. package/dist/recipeOrchestration.js.map +1 -1
  90. package/dist/recipeRoutes.d.ts +36 -0
  91. package/dist/recipeRoutes.js +231 -32
  92. package/dist/recipeRoutes.js.map +1 -1
  93. package/dist/recipes/agentExecutor.d.ts +25 -5
  94. package/dist/recipes/agentExecutor.js.map +1 -1
  95. package/dist/recipes/chainedRunner.js +16 -2
  96. package/dist/recipes/chainedRunner.js.map +1 -1
  97. package/dist/recipes/connectorPreflight.d.ts +53 -0
  98. package/dist/recipes/connectorPreflight.js +79 -0
  99. package/dist/recipes/connectorPreflight.js.map +1 -0
  100. package/dist/recipes/githubInstallSource.d.ts +62 -0
  101. package/dist/recipes/githubInstallSource.js +125 -0
  102. package/dist/recipes/githubInstallSource.js.map +1 -0
  103. package/dist/recipes/haltCategory.d.ts +80 -0
  104. package/dist/recipes/haltCategory.js +125 -0
  105. package/dist/recipes/haltCategory.js.map +1 -0
  106. package/dist/recipes/idempotencyKey.d.ts +126 -0
  107. package/dist/recipes/idempotencyKey.js +298 -0
  108. package/dist/recipes/idempotencyKey.js.map +1 -0
  109. package/dist/recipes/judgeSummary.d.ts +50 -0
  110. package/dist/recipes/judgeSummary.js +47 -0
  111. package/dist/recipes/judgeSummary.js.map +1 -0
  112. package/dist/recipes/judgeVerdict.d.ts +48 -0
  113. package/dist/recipes/judgeVerdict.js +174 -0
  114. package/dist/recipes/judgeVerdict.js.map +1 -0
  115. package/dist/recipes/migrations/index.d.ts +9 -0
  116. package/dist/recipes/migrations/index.js +133 -0
  117. package/dist/recipes/migrations/index.js.map +1 -1
  118. package/dist/recipes/runBudget.d.ts +70 -0
  119. package/dist/recipes/runBudget.js +109 -0
  120. package/dist/recipes/runBudget.js.map +1 -0
  121. package/dist/recipes/scheduler.d.ts +7 -0
  122. package/dist/recipes/scheduler.js +31 -14
  123. package/dist/recipes/scheduler.js.map +1 -1
  124. package/dist/recipes/schema.d.ts +36 -0
  125. package/dist/recipes/toolRegistry.js +19 -0
  126. package/dist/recipes/toolRegistry.js.map +1 -1
  127. package/dist/recipes/tools/file.js +5 -2
  128. package/dist/recipes/tools/file.js.map +1 -1
  129. package/dist/recipes/tools/http.d.ts +10 -0
  130. package/dist/recipes/tools/http.js +176 -0
  131. package/dist/recipes/tools/http.js.map +1 -0
  132. package/dist/recipes/tools/index.d.ts +1 -0
  133. package/dist/recipes/tools/index.js +1 -0
  134. package/dist/recipes/tools/index.js.map +1 -1
  135. package/dist/recipes/validation.js +1 -1
  136. package/dist/recipes/validation.js.map +1 -1
  137. package/dist/recipes/yamlRunner.d.ts +88 -7
  138. package/dist/recipes/yamlRunner.js +216 -25
  139. package/dist/recipes/yamlRunner.js.map +1 -1
  140. package/dist/recipesHttp.d.ts +3 -1
  141. package/dist/recipesHttp.js +9 -3
  142. package/dist/recipesHttp.js.map +1 -1
  143. package/dist/runLog.d.ts +28 -0
  144. package/dist/runLog.js +5 -0
  145. package/dist/runLog.js.map +1 -1
  146. package/dist/server.d.ts +111 -1
  147. package/dist/server.js +480 -6
  148. package/dist/server.js.map +1 -1
  149. package/dist/streamableHttp.d.ts +9 -4
  150. package/dist/streamableHttp.js +34 -15
  151. package/dist/streamableHttp.js.map +1 -1
  152. package/dist/tools/bridgeDoctor.js +6 -2
  153. package/dist/tools/bridgeDoctor.js.map +1 -1
  154. package/dist/tools/ccRoutines.d.ts +221 -0
  155. package/dist/tools/ccRoutines.js +264 -0
  156. package/dist/tools/ccRoutines.js.map +1 -0
  157. package/dist/tools/getCodeCoverage.js +7 -3
  158. package/dist/tools/getCodeCoverage.js.map +1 -1
  159. package/dist/tools/index.js +6 -0
  160. package/dist/tools/index.js.map +1 -1
  161. package/dist/tools/openInBrowser.js +6 -1
  162. package/dist/tools/openInBrowser.js.map +1 -1
  163. package/dist/tools/recentTracesDigest.js +56 -11
  164. package/dist/tools/recentTracesDigest.js.map +1 -1
  165. package/dist/tools/testRunners/vitestJest.js +3 -1
  166. package/dist/tools/testRunners/vitestJest.js.map +1 -1
  167. package/dist/tools/utils.js +13 -7
  168. package/dist/tools/utils.js.map +1 -1
  169. package/package.json +16 -5
  170. package/scripts/postinstall.mjs +27 -0
  171. package/scripts/smoke/run-all.mjs +162 -0
  172. package/scripts/start-all.mjs +513 -0
  173. package/scripts/start-all.ps1 +209 -0
  174. package/scripts/start-all.sh +73 -17
  175. package/scripts/start-orchestrator.ps1 +158 -0
  176. package/scripts/start-remote.mjs +122 -0
  177. package/templates/automation-policies/recipe-authoring.json +1 -1
  178. package/templates/automation-policies/security-first.json +1 -1
  179. package/templates/automation-policies/strict-lint.json +1 -1
  180. package/templates/automation-policies/test-driven.json +1 -1
  181. package/templates/automation-policy.example.json +1 -1
  182. package/templates/co.patchwork-os.bridge.plist +1 -1
  183. package/templates/recipes/approval-queue-ui-test.yaml +1 -1
  184. package/templates/recipes/ctx-loop-test.yaml +1 -1
  185. package/templates/recipes/webhook/apple-watch-health-log.yaml +145 -0
  186. package/dist/commands/marketplace.d.ts +0 -16
  187. package/dist/commands/marketplace.js +0 -32
  188. package/dist/commands/marketplace.js.map +0 -1
  189. package/dist/recipes/legacyRecipeCompat.d.ts +0 -10
  190. package/dist/recipes/legacyRecipeCompat.js +0 -131
  191. package/dist/recipes/legacyRecipeCompat.js.map +0 -1
@@ -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 startup.
34
- const mcp = this.bridgeMcp?.();
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
- settings = JSON.parse(readFileSync(settingsFile, "utf-8"));
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: mcp.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
- const args = [
58
- "-p",
59
- input.prompt,
60
- "--output-format",
61
- "stream-json",
62
- "--approval-mode",
63
- approvalMode,
64
- ];
65
- if (input.model)
66
- args.push("-m", input.model);
67
- // contextFiles: pass as --include-directories; normalize relative paths against workspace
68
- for (const f of input.contextFiles ?? []) {
69
- if (typeof f === "string" && f.length > 0 && !f.startsWith("-")) {
70
- const abs = isAbsolute(f) ? f : resolve(input.workspace, f);
71
- args.push("--include-directories", abs);
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
- catch {
111
- // Non-JSON stderr/warning lines skip (Gemini prints "YOLO mode..." to stdout)
112
- continue;
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
- if (event.type === "message" &&
115
- event.role === "assistant" &&
116
- event.content) {
117
- if (firstAssistantAt === undefined)
118
- firstAssistantAt = Date.now();
119
- const text = event.content;
120
- accumulated += text;
121
- if (outputBytesSent < OUTPUT_CAP) {
122
- const send = text.slice(0, OUTPUT_CAP - outputBytesSent);
123
- if (send.length > 0) {
124
- input.onChunk?.(send);
125
- outputBytesSent += send.length;
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
- else if (event.type === "result") {
130
- doneFromResult = true;
131
- resultSuccess = event.status === "success";
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
- let stderr = "";
136
- child.stderr.setEncoding("utf-8");
137
- child.stderr.on("data", (chunk) => {
138
- if (stderr.length < OUTPUT_CAP) {
139
- stderr += chunk;
140
- if (stderr.length > OUTPUT_CAP)
141
- stderr = stderr.slice(0, OUTPUT_CAP);
142
- }
143
- });
144
- const start = Date.now();
145
- const stderrTailOf = (s) => s.length > 0 ? scrubSecrets(s.slice(-2048)) : undefined;
146
- const startupMsOf = () => firstAssistantAt !== undefined ? firstAssistantAt - start : undefined;
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
- }, input.startupTimeoutMs)
155
- : null;
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
- const isAbort = (err instanceof Error && err.name === "AbortError") ||
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
- startupMs: startupMsOf(),
291
+ startupTimedOut: true,
174
292
  stderrTail: stderrTailOf(stderr),
175
293
  };
176
294
  }
177
- throw err;
178
- }
179
- if (startupHandle)
180
- clearTimeout(startupHandle);
181
- if (startupTimedOut) {
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
- const effectiveExitCode = doneFromResult
191
- ? resultSuccess
192
- ? 0
193
- : 1
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":"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"}
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,EACL,SAAS,EACT,UAAU,EACV,YAAY,EACZ,UAAU,EACV,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAMvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,UAAU,GAAG,EAAE,GAAG,IAAI,CAAC;AAmB7B,SAAS,YAAY,CAAC,IAAY;IAChC,OAAO,IAAI;SACR,OAAO,CAAC,wBAAwB,EAAE,oBAAoB,CAAC;SACvD,OAAO,CAAC,gCAAgC,EAAE,mBAAmB,CAAC,CAAC;AACpE,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,sBAAsB;IA0Bd;IACA;IACA;IA3BV,IAAI,GAAG,QAAQ,CAAC;IAEzB;;;;;;;;;;;;;;;;;;;OAmBG;IACK,MAAM,CAAC,aAAa,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IAEhE,YACmB,MAAc,EACd,GAA0B,EAC1B,SAEJ;QAJI,WAAM,GAAN,MAAM,CAAQ;QACd,QAAG,GAAH,GAAG,CAAuB;QAC1B,cAAS,GAAT,SAAS,CAEb;IACZ,CAAC;IAEJ,KAAK,CAAC,GAAG,CAAC,KAAwB;QAChC,iEAAiE;QACjE,qEAAqE;QACrE,oEAAoE;QACpE,oEAAoE;QACpE,4DAA4D;QAC5D,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;QAC/B,IAAI,GAAG,EAAE,CAAC;YACR,uEAAuE;YACvE,oDAAoD;YACpD,MAAM,KAAK,GAAG,sBAAsB,CAAC,aAAa,CAAC;YACnD,IAAI,WAAwB,CAAC;YAC7B,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBAC5C,WAAW,GAAG,OAAO,CAAC;YACxB,CAAC,CAAC,CAAC;YACH,sBAAsB,CAAC,aAAa,GAAG,OAAO,CAAC;YAC/C,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC;gBACZ,OAAO,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YAC3C,CAAC;oBAAS,CAAC;gBACT,WAAW,EAAE,CAAC;YAChB,CAAC;QACH,CAAC;QACD,+DAA+D;QAC/D,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAC3C,CAAC;IAEO,KAAK,CAAC,UAAU,CACtB,KAAwB,EACxB,GAAmD;QAEnD,MAAM,IAAI,GAAG,KAAK,CAAC,eAAe,IAAI,EAAE,CAAC;QACzC,MAAM,YAAY,GAChB,OAAO,IAAI,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC;QAErE,wEAAwE;QACxE,sEAAsE;QACtE,wEAAwE;QACxE,qEAAqE;QACrE,uEAAuE;QACvE,EAAE;QACF,uEAAuE;QACvE,wEAAwE;QACxE,qEAAqE;QACrE,2BAA2B;QAC3B,IAAI,eAAe,GAAwB,IAAI,CAAC;QAChD,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;YACjE,IAAI,CAAC;gBACH,IAAI,eAAe,GAAkB,IAAI,CAAC;gBAC1C,IAAI,QAAQ,GAA4B,EAAE,CAAC;gBAC3C,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;oBAC7B,eAAe,GAAG,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;oBACtD,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAA4B,CAAC;gBACpE,CAAC;gBACD,MAAM,UAAU,GAAG,CAAC,QAAQ,CAAC,UAAU,IAAI,EAAE,CAG5C,CAAC;gBACF,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,CACvC,UAAU,EACV,mBAAmB,CACpB;oBACC,CAAC,CAAC,UAAU,CAAC,mBAAmB,CAAC;oBACjC,CAAC,CAAC,SAAS,CAAC;gBACd,MAAM,QAAQ,GAAG,CAAC,GAAG,EAAE;oBACrB,IAAI,CAAC;wBACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;wBAC3B,yEAAyE;wBACzE,CAAC,CAAC,QAAQ,GAAG,WAAW,CAAC;wBACzB,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;oBACtB,CAAC;oBAAC,MAAM,CAAC;wBACP,OAAO,GAAG,CAAC,GAAG,CAAC;oBACjB,CAAC;gBACH,CAAC,CAAC,EAAE,CAAC;gBACL,UAAU,CAAC,mBAAmB,CAAC,GAAG;oBAChC,GAAG,EAAE,QAAQ;oBACb,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,GAAG,CAAC,SAAS,EAAE,EAAE;iBACtD,CAAC;gBACF,QAAQ,CAAC,UAAU,GAAG,UAAU,CAAC;gBACjC,aAAa,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;oBAC7D,IAAI,EAAE,KAAK;iBACZ,CAAC,CAAC;gBACH,SAAS,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;gBAC/B,qEAAqE;gBACrE,sEAAsE;gBACtE,wEAAwE;gBACxE,eAAe,GAAG,GAAG,EAAE;oBACrB,IAAI,CAAC;wBACH,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;4BAC7B,2DAA2D;4BAC3D,IAAI,CAAC;gCACH,UAAU,CAAC,YAAY,CAAC,CAAC;4BAC3B,CAAC;4BAAC,MAAM,CAAC;gCACP,YAAY;4BACd,CAAC;4BACD,OAAO;wBACT,CAAC;wBACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAGxC,CAAC;wBACF,IAAI,mBAAmB,KAAK,SAAS,EAAE,CAAC;4BACtC,MAAM,eAAe,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAG/C,CAAC;4BACF,IAAI,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,mBAAmB,CAAC,EAAE,CAAC;gCACxD,OAAO,eAAe,CAAC,mBAAmB,CAAC,CAAC;4BAC9C,CAAC;4BACD,MAAM,CAAC,UAAU,GAAG,eAAe,CAAC;wBACtC,CAAC;wBACD,aAAa,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;4BAC3D,IAAI,EAAE,KAAK;yBACZ,CAAC,CAAC;wBACH,SAAS,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;oBACjC,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,IAAI,CAAC,GAAG,CACN,6EAA6E,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAChI,CAAC;oBACJ,CAAC;gBACH,CAAC,CAAC;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC,GAAG,CACN,4EAA4E,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC/H,CAAC;YACJ,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,GAAG;gBACX,IAAI;gBACJ,KAAK,CAAC,MAAM;gBACZ,iBAAiB;gBACjB,aAAa;gBACb,iBAAiB;gBACjB,YAAY;aACb,CAAC;YACF,IAAI,KAAK,CAAC,KAAK;gBAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;YAC9C,0FAA0F;YAC1F,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC;gBACzC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;oBAChE,MAAM,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;oBAC5D,IAAI,CAAC,IAAI,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;gBAC1C,CAAC;YACH,CAAC;YAED,2EAA2E;YAC3E,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACrC,iEAAiE;YACjE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBACnC,IAAI,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,GAAG,KAAK,gBAAgB,EAAE,CAAC;oBAC7D,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;gBAClB,CAAC;YACH,CAAC;YAED,IAAI,CAAC,GAAG,CACN,sCAAsC,IAAI,CAAC,MAAM,4BAA4B,KAAK,CAAC,SAAS,GAAG,CAChG,CAAC;YAEF,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE;gBACrC,GAAG,EAAE,OAAO,EAAE;gBACd,GAAG;gBACH,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;aAClC,CAAC,CAAC;YACH,qEAAqE;YACrE,yEAAyE;YACzE,4DAA4D;YAC5D,KAAK,CAAC,KAAK,EAAE,CAAC;YAEd,IAAI,OAAO,GAAG,EAAE,CAAC;YACjB,IAAI,WAAW,GAAG,EAAE,CAAC;YACrB,IAAI,eAAe,GAAG,CAAC,CAAC;YACxB,IAAI,gBAAoC,CAAC;YACzC,IAAI,cAAc,GAAG,KAAK,CAAC;YAC3B,IAAI,aAAa,GAAG,IAAI,CAAC;YAEzB,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YAClC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;gBACxC,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;gBACxD,OAAO,GAAG,SAAS,CAAC;gBAEpB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;oBACzB,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;wBAAE,SAAS;oBACjC,IAAI,KAAkB,CAAC;oBACvB,IAAI,CAAC;wBACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAgB,CAAC;oBAC1C,CAAC;oBAAC,MAAM,CAAC;wBACP,gFAAgF;wBAChF,SAAS;oBACX,CAAC;oBAED,IACE,KAAK,CAAC,IAAI,KAAK,SAAS;wBACxB,KAAK,CAAC,IAAI,KAAK,WAAW;wBAC1B,KAAK,CAAC,OAAO,EACb,CAAC;wBACD,IAAI,gBAAgB,KAAK,SAAS;4BAAE,gBAAgB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;wBAClE,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC;wBAC3B,WAAW,IAAI,IAAI,CAAC;wBACpB,IAAI,eAAe,GAAG,UAAU,EAAE,CAAC;4BACjC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,GAAG,eAAe,CAAC,CAAC;4BACzD,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gCACpB,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;gCACtB,eAAe,IAAI,IAAI,CAAC,MAAM,CAAC;4BACjC,CAAC;wBACH,CAAC;oBACH,CAAC;yBAAM,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;wBACnC,cAAc,GAAG,IAAI,CAAC;wBACtB,aAAa,GAAG,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC;oBAC7C,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YAClC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;gBACxC,IAAI,MAAM,CAAC,MAAM,GAAG,UAAU,EAAE,CAAC;oBAC/B,MAAM,IAAI,KAAK,CAAC;oBAChB,IAAI,MAAM,CAAC,MAAM,GAAG,UAAU;wBAAE,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;gBACvE,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACzB,MAAM,YAAY,GAAG,CAAC,CAAS,EAAsB,EAAE,CACrD,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAC1D,MAAM,WAAW,GAAG,GAAuB,EAAE,CAC3C,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,gBAAgB,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;YAExE,IAAI,eAAe,GAAG,KAAK,CAAC;YAC5B,MAAM,aAAa,GAAG,KAAK,CAAC,gBAAgB;gBAC1C,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE;oBACd,IAAI,gBAAgB,KAAK,SAAS,IAAI,CAAC,cAAc,EAAE,CAAC;wBACtD,eAAe,GAAG,IAAI,CAAC;wBACvB,KAAK,CAAC,IAAI,EAAE,CAAC;oBACf,CAAC;gBACH,CAAC,EAAE,KAAK,CAAC,gBAAgB,CAAC;gBAC5B,CAAC,CAAC,IAAI,CAAC;YAET,IAAI,QAAgB,CAAC;YACrB,IAAI,CAAC;gBACH,QAAQ,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;oBACvD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;oBAChD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;gBAC5B,CAAC,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,aAAa;oBAAE,YAAY,CAAC,aAAa,CAAC,CAAC;gBAC/C,MAAM,OAAO,GACX,CAAC,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,CAAC;oBACnD,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC;gBACvB,IAAI,OAAO,EAAE,CAAC;oBACZ,OAAO;wBACL,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC;wBACtC,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;wBAC9B,UAAU,EAAE,IAAI;wBAChB,SAAS,EAAE,WAAW,EAAE;wBACxB,UAAU,EAAE,YAAY,CAAC,MAAM,CAAC;qBACjC,CAAC;gBACJ,CAAC;gBACD,MAAM,GAAG,CAAC;YACZ,CAAC;YACD,IAAI,aAAa;gBAAE,YAAY,CAAC,aAAa,CAAC,CAAC;YAE/C,IAAI,eAAe,EAAE,CAAC;gBACpB,OAAO;oBACL,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC;oBACtC,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;oBAC9B,UAAU,EAAE,IAAI;oBAChB,eAAe,EAAE,IAAI;oBACrB,UAAU,EAAE,YAAY,CAAC,MAAM,CAAC;iBACjC,CAAC;YACJ,CAAC;YAED,MAAM,iBAAiB,GAAG,cAAc;gBACtC,CAAC,CAAC,aAAa;oBACb,CAAC,CAAC,CAAC;oBACH,CAAC,CAAC,CAAC;gBACL,CAAC,CAAC,QAAQ,CAAC;YACb,IAAI,iBAAiB,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;gBACtC,IAAI,CAAC,GAAG,CAAC,oCAAoC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;YACvE,CAAC;YAED,OAAO;gBACL,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC;gBACtC,QAAQ,EAAE,iBAAiB;gBAC3B,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;gBAC9B,UAAU,EAAE,YAAY,CAAC,MAAM,CAAC;gBAChC,SAAS,EAAE,WAAW,EAAE;aACzB,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,oEAAoE;YACpE,4DAA4D;YAC5D,eAAe,EAAE,EAAE,CAAC;QACtB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,KAAwB;QACvC,OAAO,qBAAqB,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IACtD,CAAC"}
@@ -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":"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"}