@gajae-code/coding-agent 0.11.6 → 0.11.8

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 (259) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/dist/types/capability/index.d.ts +10 -20
  3. package/dist/types/capability/types.d.ts +3 -0
  4. package/dist/types/cli/args.d.ts +0 -2
  5. package/dist/types/commands/daemon.d.ts +2 -2
  6. package/dist/types/commands/sdk.d.ts +20 -2
  7. package/dist/types/config/keybindings.d.ts +34 -4
  8. package/dist/types/config/model-profile-activation.d.ts +4 -3
  9. package/dist/types/config/model-profile-contract.d.ts +38 -0
  10. package/dist/types/config/settings-schema.d.ts +70 -7
  11. package/dist/types/edit/modes/patch.d.ts +13 -0
  12. package/dist/types/edit/path-mutation-lock.d.ts +15 -0
  13. package/dist/types/eval/py/executor.d.ts +1 -1
  14. package/dist/types/eval/py/runner-artifact.d.ts +8 -0
  15. package/dist/types/eval/py/tool-bridge.d.ts +1 -2
  16. package/dist/types/gjc-runtime/repository-binding.d.ts +63 -0
  17. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +6 -5
  18. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +20 -0
  19. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +43 -5
  20. package/dist/types/main.d.ts +32 -1
  21. package/dist/types/modes/components/btw-panel.d.ts +1 -0
  22. package/dist/types/modes/components/model-selector.d.ts +2 -0
  23. package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
  24. package/dist/types/modes/components/tool-status-header.d.ts +4 -2
  25. package/dist/types/modes/components/welcome.d.ts +2 -0
  26. package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
  27. package/dist/types/modes/controllers/command-controller.d.ts +2 -0
  28. package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
  29. package/dist/types/modes/interactive-mode.d.ts +7 -2
  30. package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
  31. package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
  32. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
  33. package/dist/types/modes/types.d.ts +4 -0
  34. package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
  35. package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
  36. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  37. package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
  38. package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
  39. package/dist/types/sdk/broker/broker.d.ts +2 -0
  40. package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
  41. package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -1
  42. package/dist/types/sdk/bus/config-commands.d.ts +2 -2
  43. package/dist/types/sdk/bus/config.d.ts +2 -0
  44. package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
  45. package/dist/types/sdk/bus/index.d.ts +26 -1
  46. package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
  47. package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
  48. package/dist/types/sdk/bus/notification-service.d.ts +34 -0
  49. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +105 -0
  50. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +18 -3
  51. package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
  52. package/dist/types/sdk/bus/telegram-daemon.d.ts +189 -4
  53. package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
  54. package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
  55. package/dist/types/sdk/client/discovery.d.ts +2 -0
  56. package/dist/types/sdk/client/liveness.d.ts +7 -0
  57. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  58. package/dist/types/sdk/host/host.d.ts +1 -1
  59. package/dist/types/sdk/host/query/handlers.d.ts +9 -0
  60. package/dist/types/sdk/index.d.ts +2 -0
  61. package/dist/types/sdk/prompt-status.d.ts +88 -0
  62. package/dist/types/sdk/session-directory.d.ts +1 -1
  63. package/dist/types/sdk/startup-capability.d.ts +3 -0
  64. package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
  65. package/dist/types/sdk/transport/index.d.ts +12 -0
  66. package/dist/types/sdk/transport/relay.d.ts +43 -0
  67. package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
  68. package/dist/types/sdk/transport/socket.d.ts +7 -0
  69. package/dist/types/sdk/transport/stdio.d.ts +3 -0
  70. package/dist/types/session/agent-session.d.ts +30 -5
  71. package/dist/types/session/btw-contract.d.ts +16 -0
  72. package/dist/types/session/internal/managed-session-scope.d.ts +32 -9
  73. package/dist/types/session/internal/managed-session-storage.d.ts +27 -10
  74. package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
  75. package/dist/types/session/session-manager.d.ts +25 -4
  76. package/dist/types/session/session-storage.d.ts +18 -0
  77. package/dist/types/setup/credential-import.d.ts +5 -1
  78. package/dist/types/setup/provider-onboarding.d.ts +2 -0
  79. package/dist/types/ssh/utils.d.ts +1 -0
  80. package/dist/types/task/discovery.d.ts +3 -1
  81. package/dist/types/task/index.d.ts +3 -1
  82. package/dist/types/task/receipt.d.ts +2 -0
  83. package/dist/types/task/types.d.ts +94 -0
  84. package/dist/types/tools/ask.d.ts +454 -174
  85. package/dist/types/tools/fetch.d.ts +5 -8
  86. package/dist/types/tools/image-gen.d.ts +63 -1
  87. package/dist/types/tools/index.d.ts +4 -0
  88. package/dist/types/tools/skill-discovery.d.ts +7 -0
  89. package/dist/types/tools/sqlite-reader.d.ts +7 -0
  90. package/dist/types/utils/prompt-suggestion.d.ts +33 -0
  91. package/dist/types/utils/shell-snapshot.d.ts +12 -0
  92. package/dist/types/web/insane/bridge.d.ts +1 -0
  93. package/dist/types/web/insane/url-guard.d.ts +17 -0
  94. package/package.json +8 -8
  95. package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
  96. package/scripts/dogfood-repository-binding.ts +209 -0
  97. package/scripts/generate-sdk-operation-inventory.ts +4 -0
  98. package/scripts/run-test-manifest.ts +4 -4
  99. package/src/capability/index.ts +50 -42
  100. package/src/capability/types.ts +3 -0
  101. package/src/cli/args.ts +2 -92
  102. package/src/cli/notify-cli.ts +5 -38
  103. package/src/cli/setup-cli.ts +10 -4
  104. package/src/cli.ts +52 -9
  105. package/src/commands/daemon.ts +22 -5
  106. package/src/commands/sdk.ts +44 -11
  107. package/src/config/keybindings.ts +158 -25
  108. package/src/config/model-profile-activation.ts +7 -18
  109. package/src/config/model-profile-contract.ts +161 -0
  110. package/src/config/model-profiles.ts +18 -0
  111. package/src/config/model-registry.ts +11 -1
  112. package/src/config/settings-schema.ts +73 -7
  113. package/src/coordinator-mcp/model-preset.ts +21 -74
  114. package/src/defaults/gjc/skills/deep-interview/SKILL.md +2 -2
  115. package/src/defaults/gjc/skills/ralplan/SKILL.md +3 -1
  116. package/src/defaults/gjc/skills/ultragoal/SKILL.md +42 -1
  117. package/src/discovery/ssh.ts +12 -1
  118. package/src/edit/index.ts +3 -6
  119. package/src/edit/modes/patch.ts +38 -1
  120. package/src/edit/modes/replace.ts +42 -0
  121. package/src/edit/path-mutation-lock.ts +112 -0
  122. package/src/eval/py/executor.ts +30 -11
  123. package/src/eval/py/kernel.ts +1 -21
  124. package/src/eval/py/prelude.py +2 -2
  125. package/src/eval/py/runner-artifact.ts +84 -0
  126. package/src/eval/py/tool-bridge.ts +34 -18
  127. package/src/extensibility/gjc-plugins/runtime-adapters.ts +2 -1
  128. package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
  129. package/src/gjc-runtime/launch-tmux.ts +1 -1
  130. package/src/gjc-runtime/managed-owner-supervisor.ts +83 -4
  131. package/src/gjc-runtime/ralplan-runtime.ts +75 -3
  132. package/src/gjc-runtime/repository-binding.ts +267 -0
  133. package/src/gjc-runtime/session-state-sidecar.ts +25 -9
  134. package/src/gjc-runtime/tmux-owner-isolation.ts +2 -1
  135. package/src/gjc-runtime/tmux-sessions.ts +45 -6
  136. package/src/gjc-runtime/ultragoal-guard.ts +93 -15
  137. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +103 -0
  138. package/src/gjc-runtime/ultragoal-runtime.ts +456 -31
  139. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  140. package/src/gjc-runtime/workflow-manifest.ts +23 -0
  141. package/src/internal-urls/docs-index.generated.ts +12 -10
  142. package/src/internal-urls/local-protocol.ts +64 -21
  143. package/src/internal-urls/mcp-protocol.ts +73 -11
  144. package/src/main.ts +111 -37
  145. package/src/modes/DESIGN.md +5 -0
  146. package/src/modes/acp/acp-agent.ts +50 -16
  147. package/src/modes/action-registry.ts +1 -1
  148. package/src/modes/components/btw-panel.ts +82 -16
  149. package/src/modes/components/model-selector.ts +73 -11
  150. package/src/modes/components/plan-preview-overlay.ts +1 -1
  151. package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
  152. package/src/modes/components/tool-status-header.ts +25 -4
  153. package/src/modes/components/welcome.ts +22 -18
  154. package/src/modes/controllers/btw-controller.ts +119 -36
  155. package/src/modes/controllers/command-controller.ts +97 -54
  156. package/src/modes/controllers/event-controller.ts +10 -0
  157. package/src/modes/controllers/input-controller.ts +38 -2
  158. package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
  159. package/src/modes/controllers/selector-controller.ts +149 -63
  160. package/src/modes/interactive-mode.ts +57 -34
  161. package/src/modes/prompt-action-autocomplete.ts +32 -13
  162. package/src/modes/prompt-suggestion-controller.ts +96 -0
  163. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
  164. package/src/modes/types.ts +4 -0
  165. package/src/modes/utils/hotkeys-markdown.ts +64 -36
  166. package/src/modes/utils/ui-helpers.ts +5 -3
  167. package/src/prompts/agents/architect.md +0 -1
  168. package/src/prompts/agents/critic.md +0 -1
  169. package/src/prompts/system/btw-user.md +3 -8
  170. package/src/prompts/system/prompt-suggestion-system.md +32 -0
  171. package/src/prompts/system/system-prompt.md +6 -4
  172. package/src/prompts/tools/cron.md +3 -1
  173. package/src/prompts/tools/skill-discovery.md +1 -0
  174. package/src/runtime-mcp/client.ts +41 -55
  175. package/src/runtime-mcp/content-limits.ts +71 -0
  176. package/src/runtime-mcp/json-rpc.ts +23 -9
  177. package/src/runtime-mcp/manager.ts +51 -53
  178. package/src/runtime-mcp/oauth-flow.ts +0 -72
  179. package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
  180. package/src/runtime-mcp/transports/http.ts +63 -22
  181. package/src/sdk/broker/broker.ts +34 -20
  182. package/src/sdk/broker/lifecycle.ts +185 -36
  183. package/src/sdk/bus/chat-daemon-control.ts +19 -5
  184. package/src/sdk/bus/config-commands.ts +2 -2
  185. package/src/sdk/bus/config.ts +9 -1
  186. package/src/sdk/bus/conversation-store.ts +40 -6
  187. package/src/sdk/bus/index.ts +244 -70
  188. package/src/sdk/bus/lifecycle-commands.ts +63 -10
  189. package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
  190. package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
  191. package/src/sdk/bus/notification-orchestration.ts +12 -0
  192. package/src/sdk/bus/notification-service.ts +68 -20
  193. package/src/sdk/bus/prompt-reconciliation.ts +239 -0
  194. package/src/sdk/bus/recent-activity.ts +90 -26
  195. package/src/sdk/bus/telegram-daemon-contract.ts +18 -3
  196. package/src/sdk/bus/telegram-daemon-control.ts +147 -35
  197. package/src/sdk/bus/telegram-daemon.ts +2463 -450
  198. package/src/sdk/bus/telegram-reference.ts +20 -2
  199. package/src/sdk/bus/topic-registry.ts +451 -12
  200. package/src/sdk/cli/session-cli.ts +15 -2
  201. package/src/sdk/client/discovery.ts +19 -3
  202. package/src/sdk/client/liveness.ts +49 -0
  203. package/src/sdk/host/control/dispatch.ts +1 -1
  204. package/src/sdk/host/control/operations.ts +1 -1
  205. package/src/sdk/host/host.ts +2 -6
  206. package/src/sdk/host/query/handlers.ts +70 -5
  207. package/src/sdk/index.ts +12 -0
  208. package/src/sdk/prompt-status.ts +97 -0
  209. package/src/sdk/protocol/operation-inventory.generated.json +58 -0
  210. package/src/sdk/protocol/operation-registry.ts +18 -4
  211. package/src/sdk/session-directory.ts +5 -1
  212. package/src/sdk/session.ts +31 -5
  213. package/src/sdk/startup-capability.ts +9 -1
  214. package/src/sdk/transport/auth-preface.ts +82 -0
  215. package/src/sdk/transport/index.ts +18 -0
  216. package/src/sdk/transport/relay.ts +252 -0
  217. package/src/sdk/transport/serve-cli.ts +105 -0
  218. package/src/sdk/transport/socket.ts +197 -0
  219. package/src/sdk/transport/stdio.ts +17 -0
  220. package/src/session/agent-session.ts +292 -154
  221. package/src/session/blob-store.ts +25 -7
  222. package/src/session/btw-contract.ts +59 -0
  223. package/src/session/internal/managed-session-scope.ts +1163 -137
  224. package/src/session/internal/managed-session-storage.ts +397 -234
  225. package/src/session/internal/native-publish-outcome.ts +266 -0
  226. package/src/session/session-manager.ts +620 -403
  227. package/src/session/session-storage.ts +128 -11
  228. package/src/setup/credential-import.ts +28 -3
  229. package/src/setup/provider-onboarding.ts +30 -1
  230. package/src/setup/provider-presets.json +2 -1
  231. package/src/slash-commands/builtin-registry.ts +1 -1
  232. package/src/ssh/utils.ts +14 -0
  233. package/src/task/discovery.ts +8 -2
  234. package/src/task/gjc-command.ts +2 -2
  235. package/src/task/index.ts +121 -15
  236. package/src/task/receipt.ts +3 -0
  237. package/src/task/types.ts +34 -3
  238. package/src/tools/ask.ts +199 -89
  239. package/src/tools/ast-edit.ts +2 -2
  240. package/src/tools/bash-pty-selection.ts +2 -2
  241. package/src/tools/browser/screenshot-format.ts +6 -1
  242. package/src/tools/cron.ts +1 -1
  243. package/src/tools/fetch.ts +13 -131
  244. package/src/tools/image-gen.ts +476 -23
  245. package/src/tools/index.ts +4 -0
  246. package/src/tools/read.ts +2 -0
  247. package/src/tools/render-utils.ts +7 -1
  248. package/src/tools/skill-discovery.ts +39 -3
  249. package/src/tools/sqlite-reader.ts +63 -5
  250. package/src/utils/edit-mode.ts +2 -2
  251. package/src/utils/pasted-image-path.ts +15 -6
  252. package/src/utils/prompt-suggestion.ts +265 -0
  253. package/src/utils/shell-snapshot.ts +173 -63
  254. package/src/utils/sixel.ts +3 -3
  255. package/src/web/insane/bridge.ts +5 -1
  256. package/src/web/insane/url-guard.ts +140 -27
  257. package/src/web/scrapers/docs-rs.ts +30 -5
  258. package/src/web/scrapers/types.ts +19 -49
  259. package/src/web/scrapers/utils.ts +12 -26
@@ -5,7 +5,7 @@ import { ptree } from "@gajae-code/utils";
5
5
  import type { AgentStorage } from "../../session/agent-storage";
6
6
  import { ToolAbortError } from "../../tools/tool-errors";
7
7
  import type { AddressResolver } from "../insane/url-guard";
8
- import { validatePublicHttpUrl } from "../insane/url-guard";
8
+ import { guardedPublicFetch } from "../insane/url-guard";
9
9
 
10
10
  export { formatNumber } from "@gajae-code/utils";
11
11
 
@@ -85,20 +85,6 @@ export interface LoadPageResult {
85
85
  error?: string;
86
86
  }
87
87
 
88
- async function guardPublicFetchUrl(
89
- rawUrl: string,
90
- resolver: AddressResolver | undefined,
91
- context: string,
92
- ): Promise<{ ok: true; url: string } | { ok: false; error: string; finalUrl: string }> {
93
- const guard = await validatePublicHttpUrl(rawUrl, { resolver });
94
- if (guard.ok) return { ok: true, url: guard.url.toString() };
95
- return {
96
- ok: false,
97
- error: `${context}: target URL is not public HTTP(S): ${guard.reason}`,
98
- finalUrl: rawUrl,
99
- };
100
- }
101
-
102
88
  function shouldRewriteRedirectMethod(status: number, method: string): boolean {
103
89
  const normalized = method.toUpperCase();
104
90
  return status === 303 || ((status === 301 || status === 302) && normalized === "POST");
@@ -120,20 +106,7 @@ export async function loadPage(url: string, options: LoadPageOptions = {}): Prom
120
106
  maxRedirects = 10,
121
107
  } = options;
122
108
 
123
- let initialUrl = url;
124
- if (publicUrlGuard) {
125
- const guarded = await guardPublicFetchUrl(url, resolver, "Blocked URL fetch");
126
- if (!guarded.ok) {
127
- return {
128
- content: "",
129
- contentType: "",
130
- finalUrl: guarded.finalUrl,
131
- ok: false,
132
- error: guarded.error,
133
- };
134
- }
135
- initialUrl = guarded.url;
136
- }
109
+ const initialUrl = url;
137
110
 
138
111
  attempts: for (let attempt = 0; attempt < USER_AGENTS.length; attempt++) {
139
112
  if (signal?.aborted) {
@@ -165,36 +138,33 @@ export async function loadPage(url: string, options: LoadPageOptions = {}): Prom
165
138
  requestInit.body = currentBody;
166
139
  }
167
140
 
168
- const response = await fetch(currentUrl, requestInit);
141
+ const dial = publicUrlGuard
142
+ ? await guardedPublicFetch(currentUrl, requestInit, { resolver })
143
+ : { ok: true as const, response: await fetch(currentUrl, requestInit), logicalUrl: new URL(currentUrl) };
144
+ if (!dial.ok) {
145
+ return {
146
+ content: "",
147
+ contentType: "",
148
+ finalUrl: dial.logicalUrl,
149
+ ok: false,
150
+ error: `Blocked URL fetch: target URL is not public HTTP(S): ${dial.reason}`,
151
+ };
152
+ }
153
+ const { response } = dial;
154
+ const logicalUrl = dial.logicalUrl.toString();
169
155
  if (REDIRECT_STATUSES.has(response.status)) {
170
156
  const location = response.headers.get("location");
171
157
  if (!location) {
172
158
  return {
173
159
  content: "",
174
160
  contentType: "",
175
- finalUrl: currentUrl,
161
+ finalUrl: logicalUrl,
176
162
  ok: false,
177
163
  status: response.status,
178
164
  error: "Redirect response missing Location header",
179
165
  };
180
166
  }
181
- const redirectUrl = new URL(location, currentUrl).toString();
182
- if (publicUrlGuard) {
183
- const guarded = await guardPublicFetchUrl(redirectUrl, resolver, "Blocked URL redirect");
184
- if (!guarded.ok) {
185
- return {
186
- content: "",
187
- contentType: "",
188
- finalUrl: guarded.finalUrl,
189
- ok: false,
190
- status: response.status,
191
- error: guarded.error,
192
- };
193
- }
194
- currentUrl = guarded.url;
195
- } else {
196
- currentUrl = redirectUrl;
197
- }
167
+ currentUrl = new URL(location, logicalUrl).toString();
198
168
  if (shouldRewriteRedirectMethod(response.status, currentMethod)) {
199
169
  currentMethod = "GET";
200
170
  currentBody = undefined;
@@ -203,7 +173,7 @@ export async function loadPage(url: string, options: LoadPageOptions = {}): Prom
203
173
  }
204
174
 
205
175
  const contentType = response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() ?? "";
206
- const finalUrl = response.url || currentUrl;
176
+ const finalUrl = logicalUrl;
207
177
 
208
178
  const reader = response.body?.getReader();
209
179
  if (!reader) {
@@ -5,7 +5,7 @@ export { isRecord };
5
5
  import { ToolAbortError } from "../../tools/tool-errors";
6
6
  import { convertBufferWithMarkit } from "../../utils/markit";
7
7
  import type { AddressResolver } from "../insane/url-guard";
8
- import { validatePublicHttpUrl } from "../insane/url-guard";
8
+ import { guardedPublicFetch } from "../insane/url-guard";
9
9
  import { MAX_BYTES } from "./types";
10
10
 
11
11
  export function asRecord(value: unknown): Record<string, unknown> | null {
@@ -70,16 +70,6 @@ async function readResponseWithLimit(response: Response, maxBytes: number, signa
70
70
  return new Uint8Array(Buffer.concat(chunks, totalBytes));
71
71
  }
72
72
 
73
- async function guardPublicBinaryUrl(
74
- rawUrl: string,
75
- resolver: AddressResolver | undefined,
76
- context: string,
77
- ): Promise<{ ok: true; url: string } | { ok: false; error: string }> {
78
- const guard = await validatePublicHttpUrl(rawUrl, { resolver });
79
- if (guard.ok) return { ok: true, url: guard.url.toString() };
80
- return { ok: false, error: `${context}: target URL is not public HTTP(S): ${guard.reason}` };
81
- }
82
-
83
73
  /**
84
74
  * Fetch binary content from a URL
85
75
  */
@@ -93,32 +83,28 @@ export async function fetchBinary(
93
83
  const { publicUrlGuard = true, resolver, maxRedirects = 10 } = options;
94
84
  try {
95
85
  let currentUrl = url;
96
- if (publicUrlGuard) {
97
- const guarded = await guardPublicBinaryUrl(url, resolver, "Blocked binary fetch");
98
- if (!guarded.ok) return { ok: false, error: guarded.error };
99
- currentUrl = guarded.url;
100
- }
101
86
 
102
87
  for (let redirectCount = 0; redirectCount <= maxRedirects; redirectCount++) {
103
- const response = await fetch(currentUrl, {
88
+ const requestInit: BunFetchRequestInit = {
104
89
  signal: requestSignal,
105
90
  headers: {
106
91
  "User-Agent": "Mozilla/5.0 (compatible; TextBot/1.0)",
107
92
  },
108
93
  redirect: "manual",
109
- });
94
+ };
95
+ const dial = publicUrlGuard
96
+ ? await guardedPublicFetch(currentUrl, requestInit, { resolver })
97
+ : { ok: true as const, response: await fetch(currentUrl, requestInit), logicalUrl: new URL(currentUrl) };
98
+ if (!dial.ok) {
99
+ return { ok: false, error: `Blocked binary fetch: target URL is not public HTTP(S): ${dial.reason}` };
100
+ }
101
+ const { response } = dial;
102
+ const logicalUrl = dial.logicalUrl.toString();
110
103
 
111
104
  if (REDIRECT_STATUSES.has(response.status)) {
112
105
  const location = response.headers.get("location");
113
106
  if (!location) return { ok: false, error: "Redirect response missing Location header" };
114
- const redirectUrl = new URL(location, currentUrl).toString();
115
- if (publicUrlGuard) {
116
- const guarded = await guardPublicBinaryUrl(redirectUrl, resolver, "Blocked binary redirect");
117
- if (!guarded.ok) return { ok: false, error: guarded.error };
118
- currentUrl = guarded.url;
119
- } else {
120
- currentUrl = redirectUrl;
121
- }
107
+ currentUrl = new URL(location, logicalUrl).toString();
122
108
  continue;
123
109
  }
124
110