@gajae-code/coding-agent 0.18.11 → 0.18.12
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/CHANGELOG.md +293 -0
- package/dist/types/async/job-manager.d.ts +7 -3
- package/dist/types/async/result-formatting.d.ts +2 -0
- package/dist/types/autoresearch/git.d.ts +1 -1
- package/dist/types/capability/fs.d.ts +2 -2
- package/dist/types/cli/accounts-cli.d.ts +2 -2
- package/dist/types/cli/doctor/install-repairs.d.ts +4 -1
- package/dist/types/cli/install-activation.d.ts +10 -4
- package/dist/types/cli/public-command-errors.d.ts +1 -0
- package/dist/types/cli/update-cli.d.ts +14 -1
- package/dist/types/cli-ordinary.d.ts +2 -2
- package/dist/types/commands/accounts.d.ts +2 -1
- package/dist/types/commands/migrate.d.ts +1 -1
- package/dist/types/commands/sdk.d.ts +5 -0
- package/dist/types/commit/changelog/index.d.ts +1 -0
- package/dist/types/config/autorouting-tier-map.d.ts +30 -0
- package/dist/types/config/config-hot-reload-trust.d.ts +12 -0
- package/dist/types/config/config-hot-reload.d.ts +39 -0
- package/dist/types/config/file-lock.d.ts +8 -1
- package/dist/types/config/model-discovery-manager.d.ts +2 -0
- package/dist/types/config/model-profile-activation.d.ts +62 -6
- package/dist/types/config/model-registry.d.ts +56 -5
- package/dist/types/config/model-resolver.d.ts +2 -0
- package/dist/types/config/models-config-schema.d.ts +10 -0
- package/dist/types/config/settings-schema.d.ts +46 -3
- package/dist/types/config/settings.d.ts +33 -1
- package/dist/types/dap/config.d.ts +8 -4
- package/dist/types/debug/system-info.d.ts +6 -1
- package/dist/types/discovery/mcp-compat.d.ts +3 -1
- package/dist/types/edit/modes/patch.d.ts +5 -0
- package/dist/types/edit/modes/replace.d.ts +3 -0
- package/dist/types/exec/bash-executor.d.ts +12 -0
- package/dist/types/gjc-runtime/managed-owner-admission.d.ts +3 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +0 -2
- package/dist/types/migrate/adapters/codex.d.ts +2 -1
- package/dist/types/migrate/adapters/index.d.ts +10 -1
- package/dist/types/migrate/executor.d.ts +3 -1
- package/dist/types/migrate/types.d.ts +15 -3
- package/dist/types/modes/components/settings-selector.d.ts +6 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -0
- package/dist/types/modes/theme/theme.d.ts +1 -0
- package/dist/types/runtime-mcp/content-limits.d.ts +4 -0
- package/dist/types/runtime-mcp/manager.d.ts +7 -0
- package/dist/types/runtime-mcp/plugin-mcp-name-filter.d.ts +22 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +4 -0
- package/dist/types/sdk/broker/broker-exit.d.ts +2 -2
- package/dist/types/sdk/broker/broker.d.ts +7 -1
- package/dist/types/sdk/broker/daemon-entry.d.ts +11 -1
- package/dist/types/sdk/broker/doctor-restart.d.ts +1 -1
- package/dist/types/sdk/broker/ensure.d.ts +136 -7
- package/dist/types/sdk/broker/hop.d.ts +36 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +18 -10
- package/dist/types/sdk/broker/lock-host-identity.d.ts +5 -0
- package/dist/types/sdk/broker/session-index.d.ts +4 -1
- package/dist/types/sdk/bus/chat-command-policy.d.ts +2 -1
- package/dist/types/sdk/bus/index.d.ts +23 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +10 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
- package/dist/types/sdk/cli/session-cli.d.ts +0 -1
- package/dist/types/sdk/host/session-runtime.d.ts +32 -0
- package/dist/types/sdk/prompt-deadline-flush.d.ts +42 -4
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -0
- package/dist/types/sdk/protocol/adapter-validation.d.ts +10 -4
- package/dist/types/sdk/reconciliation-extensions.d.ts +1 -1
- package/dist/types/sdk/session-list.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +58 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +2 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +1 -1
- package/dist/types/session/internal/native-publish-outcome.d.ts +1 -1
- package/dist/types/session/messages.d.ts +5 -1
- package/dist/types/session/session-manager.d.ts +13 -3
- package/dist/types/session/startup-auth-config.d.ts +24 -2
- package/dist/types/session/streaming-output.d.ts +11 -0
- package/dist/types/session/terminal-abort.d.ts +3 -1
- package/dist/types/session-import/service.d.ts +1 -1
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +7 -3
- package/dist/types/slash-commands/helpers/provider-auth-status.d.ts +8 -0
- package/dist/types/task/executor.d.ts +19 -2
- package/dist/types/tools/ast-edit-write-path.d.ts +10 -0
- package/dist/types/tools/atomic-file-write.d.ts +9 -7
- package/dist/types/tools/bash-allowed-prefixes.d.ts +11 -0
- package/dist/types/tools/descriptors.d.ts +2 -0
- package/dist/types/tools/fetch.d.ts +1 -1
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/python.d.ts +1 -1
- package/dist/types/tools/report-issue.d.ts +41 -0
- package/dist/types/tools/subagent.d.ts +23 -0
- package/dist/types/web/scrapers/types.d.ts +8 -1
- package/dist/types/web/search/providers/insane.d.ts +7 -1
- package/dist/types/web/search/providers/searxng.d.ts +5 -0
- package/package.json +8 -8
- package/scripts/build-binary.ts +2 -1
- package/scripts/capture-subagent-compact-panel-showcase.ts +105 -0
- package/scripts/generate-docs-index.ts +8 -1
- package/scripts/generate-sdk-operation-inventory.ts +22 -4
- package/src/async/job-manager.ts +356 -56
- package/src/async/result-formatting.ts +40 -0
- package/src/autoresearch/git.ts +9 -2
- package/src/autoresearch/prompts/prompt-setup.md +1 -1
- package/src/capability/fs.ts +27 -19
- package/src/capability/index.ts +2 -2
- package/src/cli/accounts-cli.ts +176 -8
- package/src/cli/auth-broker-cli.ts +21 -11
- package/src/cli/doctor/install-repairs.ts +35 -5
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/install-activation.ts +47 -4
- package/src/cli/migrate-cli.ts +7 -5
- package/src/cli/public-command-errors.ts +4 -0
- package/src/cli/root-flags.ts +2 -2
- package/src/cli/stats-cli.ts +9 -5
- package/src/cli/update-cli.ts +224 -7
- package/src/cli-ordinary.ts +7 -2
- package/src/commands/accounts.ts +10 -5
- package/src/commands/migrate.ts +3 -3
- package/src/commands/sdk.ts +199 -42
- package/src/commands/ultragoal.ts +16 -8
- package/src/commit/changelog/index.ts +39 -1
- package/src/commit/message.ts +10 -3
- package/src/config/autorouting-tier-map.ts +30 -0
- package/src/config/config-hot-reload-trust.ts +148 -0
- package/src/config/config-hot-reload.ts +734 -0
- package/src/config/file-lock.ts +216 -56
- package/src/config/model-bindings-applier.ts +85 -47
- package/src/config/model-discovery-manager.ts +10 -0
- package/src/config/model-profile-activation.ts +716 -113
- package/src/config/model-registry.ts +1334 -158
- package/src/config/model-resolver.ts +3 -1
- package/src/config/models-config-schema.ts +6 -0
- package/src/config/settings-schema.ts +60 -3
- package/src/config/settings.ts +458 -24
- package/src/coordinator-mcp/policy.ts +26 -1
- package/src/crash/sanitize.ts +4 -0
- package/src/customization/import.ts +3 -1
- package/src/dap/config.ts +31 -11
- package/src/dap/defaults.json +1 -1
- package/src/debug/crash-diagnostics.ts +2 -1
- package/src/debug/report-bundle.ts +19 -0
- package/src/debug/system-info.ts +8 -3
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +21 -7
- package/src/discovery/agents-md.ts +1 -1
- package/src/discovery/helpers.ts +1 -2
- package/src/discovery/mcp-compat.ts +120 -17
- package/src/edit/apply-patch/index.ts +12 -1
- package/src/edit/index.ts +13 -17
- package/src/edit/modes/patch.ts +187 -8
- package/src/edit/modes/replace.ts +136 -0
- package/src/eval/js/context-manager.ts +29 -3
- package/src/eval/js/worker-entry.ts +51 -18
- package/src/eval/py/executor.ts +14 -4
- package/src/eval/py/runner.py +144 -5
- package/src/exec/bash-executor.ts +81 -4
- package/src/exec/bash-shell-guardian.ts +4 -1
- package/src/exec/bash-shell-supervisor.ts +8 -4
- package/src/exec/isolated-shell.ts +5 -1
- package/src/export/html/index.ts +46 -6
- package/src/extensibility/custom-tools/wrapper.ts +3 -1
- package/src/extensibility/extensions/loader.ts +8 -1
- package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
- package/src/gjc-runtime/gc-runtime.ts +33 -6
- package/src/gjc-runtime/launch-tmux.ts +7 -2
- package/src/gjc-runtime/launch-worktree.test.ts +148 -0
- package/src/gjc-runtime/launch-worktree.ts +101 -14
- package/src/gjc-runtime/managed-owner-admission.ts +46 -2
- package/src/gjc-runtime/session-resolution.ts +38 -6
- package/src/gjc-runtime/session-state-lock.ts +59 -14
- package/src/gjc-runtime/state-runtime.ts +23 -19
- package/src/hooks/native-skill-hook.ts +14 -11
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/internal-urls/local-protocol.ts +142 -8
- package/src/lsp/index.ts +29 -0
- package/src/lsp/workspace-path.ts +23 -0
- package/src/migrate/action-planner.ts +57 -0
- package/src/migrate/adapters/claude-code.ts +23 -4
- package/src/migrate/adapters/codex.ts +22 -4
- package/src/migrate/adapters/index.ts +22 -4
- package/src/migrate/adapters/opencode.ts +6 -1
- package/src/migrate/executor.ts +84 -7
- package/src/migrate/report.ts +19 -2
- package/src/migrate/types.ts +14 -4
- package/src/modes/DESIGN.md +51 -0
- package/src/modes/acp/permission-mode.ts +18 -1
- package/src/modes/components/assistant-message.ts +55 -1
- package/src/modes/components/settings-selector.ts +103 -37
- package/src/modes/controllers/command-controller.ts +38 -2
- package/src/modes/controllers/plan-mode-controller.ts +17 -7
- package/src/modes/controllers/selector-controller.ts +62 -6
- package/src/modes/interactive-mode.ts +68 -0
- package/src/modes/theme/theme.ts +15 -0
- package/src/prompts/tools/debug.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/report-issue.md +13 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/runtime-credential-selector.ts +2 -2
- package/src/runtime-mcp/content-limits.ts +7 -0
- package/src/runtime-mcp/manager-pool.test.ts +5 -4
- package/src/runtime-mcp/manager.ts +32 -0
- package/src/runtime-mcp/oauth-flow.ts +32 -6
- package/src/runtime-mcp/plugin-mcp-name-filter.ts +62 -0
- package/src/runtime-mcp/render.ts +16 -1
- package/src/runtime-mcp/tool-bridge.ts +126 -17
- package/src/sdk/broker/broker-exit.ts +8 -5
- package/src/sdk/broker/broker.ts +342 -82
- package/src/sdk/broker/daemon-entry.ts +338 -116
- package/src/sdk/broker/doctor-restart.ts +2 -1
- package/src/sdk/broker/ensure.ts +1143 -265
- package/src/sdk/broker/hop.ts +274 -0
- package/src/sdk/broker/lifecycle-ledger.ts +35 -11
- package/src/sdk/broker/lock-host-identity.ts +29 -0
- package/src/sdk/broker/session-index.ts +62 -12
- package/src/sdk/bus/chat-command-policy.ts +16 -3
- package/src/sdk/bus/index.ts +52 -21
- package/src/sdk/bus/reconciliation-store.ts +201 -8
- package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
- package/src/sdk/cli/session-cli.ts +21 -6
- package/src/sdk/host/session-runtime.test.ts +118 -23
- package/src/sdk/host/session-runtime.ts +352 -47
- package/src/sdk/lifecycle/service.ts +1 -0
- package/src/sdk/prompt-deadline-flush.ts +78 -5
- package/src/sdk/prompt-deadline-manager.ts +68 -14
- package/src/sdk/protocol/adapter-validation.ts +53 -11
- package/src/sdk/protocol/operation-inventory.generated.json +101 -2
- package/src/sdk/reconciliation-extensions.ts +1 -0
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +114 -61
- package/src/session/agent-session.ts +1043 -81
- package/src/session/internal/managed-session-scope.ts +126 -39
- package/src/session/internal/managed-session-storage.ts +14 -11
- package/src/session/internal/native-publish-outcome.ts +5 -1
- package/src/session/messages.ts +47 -6
- package/src/session/session-manager.ts +236 -14
- package/src/session/startup-auth-config.ts +116 -8
- package/src/session/streaming-output.ts +262 -4
- package/src/session/terminal-abort.ts +14 -2
- package/src/session-import/service.ts +1 -0
- package/src/skill-state/workflow-mutation-guard.ts +491 -32
- package/src/slash-commands/builtin-registry.ts +15 -3
- package/src/slash-commands/helpers/credential-switch.ts +10 -5
- package/src/slash-commands/helpers/provider-auth-status.ts +456 -0
- package/src/task/executor.ts +103 -12
- package/src/task/index.ts +389 -175
- package/src/tools/ast-edit-write-path.ts +64 -0
- package/src/tools/ast-edit.ts +18 -1
- package/src/tools/atomic-file-write.ts +46 -15
- package/src/tools/auto-generated-guard.ts +3 -0
- package/src/tools/bash-allowed-prefixes.ts +153 -5
- package/src/tools/bash-interactive.ts +14 -2
- package/src/tools/bash.ts +14 -0
- package/src/tools/debug.ts +7 -6
- package/src/tools/descriptors.test.ts +44 -0
- package/src/tools/descriptors.ts +34 -2
- package/src/tools/fetch.ts +3 -2
- package/src/tools/index.ts +2 -0
- package/src/tools/output-meta.ts +23 -2
- package/src/tools/python.ts +1 -1
- package/src/tools/read.ts +17 -35
- package/src/tools/report-issue.ts +184 -0
- package/src/tools/subagent-render.ts +114 -12
- package/src/tools/subagent.ts +108 -1
- package/src/tools/todo-write.ts +41 -3
- package/src/tools/tool-catalog.generated.ts +31 -3
- package/src/tools/write.ts +35 -3
- package/src/utils/commit-message-generator.ts +9 -2
- package/src/utils/file-mentions.ts +29 -1
- package/src/utils/title-generator.ts +7 -2
- package/src/web/scrapers/index.ts +3 -1
- package/src/web/scrapers/types.ts +8 -0
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/insane.ts +91 -28
- package/src/web/search/providers/searxng.ts +62 -10
|
@@ -15,6 +15,8 @@ import type { AuthStorage } from "@gajae-code/ai/core";
|
|
|
15
15
|
|
|
16
16
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
17
17
|
import { SearchProviderError } from "../../../web/search/types";
|
|
18
|
+
import type { AddressResolver } from "../../insane/url-guard";
|
|
19
|
+
import { guardedPublicFetch, validatePublicHttpUrl } from "../../insane/url-guard";
|
|
18
20
|
import { clampNumResults } from "../utils";
|
|
19
21
|
import type { SearchParams } from "./base";
|
|
20
22
|
import { SearchProvider } from "./base";
|
|
@@ -24,6 +26,8 @@ import { withHardTimeout } from "./utils";
|
|
|
24
26
|
const DEFAULT_NUM_RESULTS = 10;
|
|
25
27
|
const MAX_NUM_RESULTS = 20;
|
|
26
28
|
const PUBLIC_ROUTE_TIMEOUT_MS = 15_000;
|
|
29
|
+
const MAX_PUBLIC_ROUTE_REDIRECTS = 5;
|
|
30
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
27
31
|
const DISCOVERY_LIMIT = 8;
|
|
28
32
|
const MAX_PUBLIC_ROUTE_RESPONSE_BYTES = 1024 * 1024;
|
|
29
33
|
|
|
@@ -151,6 +155,10 @@ class ResponseTooLargeError extends Error {
|
|
|
151
155
|
override readonly name = "response_too_large";
|
|
152
156
|
}
|
|
153
157
|
|
|
158
|
+
class PublicUrlRejectedError extends Error {
|
|
159
|
+
override readonly name = "public_url_rejected";
|
|
160
|
+
}
|
|
161
|
+
|
|
154
162
|
async function cancelBody(body: ReadableStream<Uint8Array> | null): Promise<void> {
|
|
155
163
|
if (!body) return;
|
|
156
164
|
try {
|
|
@@ -203,17 +211,51 @@ async function readResponseText(response: Response): Promise<string> {
|
|
|
203
211
|
async function fetchText(
|
|
204
212
|
url: string,
|
|
205
213
|
signal?: AbortSignal,
|
|
214
|
+
resolverOption?: { resolver?: AddressResolver },
|
|
206
215
|
): Promise<{ status: number; text: string; contentType: string }> {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
216
|
+
const deadline = withHardTimeout(signal, PUBLIC_ROUTE_TIMEOUT_MS);
|
|
217
|
+
let currentUrl = url;
|
|
218
|
+
|
|
219
|
+
for (let redirectCount = 0; ; redirectCount++) {
|
|
220
|
+
// Fresh GET each hop: no body and no caller auth headers. guardedPublicFetch forces redirect:"manual".
|
|
221
|
+
const dial = await guardedPublicFetch(
|
|
222
|
+
currentUrl,
|
|
223
|
+
{
|
|
224
|
+
method: "GET",
|
|
225
|
+
headers: {
|
|
226
|
+
Accept:
|
|
227
|
+
"application/json, application/atom+xml, application/rss+xml, text/xml, text/html;q=0.8, */*;q=0.5",
|
|
228
|
+
"User-Agent": USER_AGENT,
|
|
229
|
+
},
|
|
230
|
+
redirect: "manual",
|
|
231
|
+
signal: deadline,
|
|
232
|
+
},
|
|
233
|
+
resolverOption,
|
|
234
|
+
);
|
|
235
|
+
if (!dial.ok) throw new PublicUrlRejectedError(dial.reason);
|
|
236
|
+
|
|
237
|
+
const { response, logicalUrl } = dial;
|
|
238
|
+
if (!REDIRECT_STATUSES.has(response.status)) {
|
|
239
|
+
const text = await readResponseText(response);
|
|
240
|
+
return { status: response.status, text, contentType: response.headers.get("content-type") ?? "" };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const location = response.headers.get("location")?.trim();
|
|
244
|
+
await cancelBody(response.body);
|
|
245
|
+
if (!location) throw new PublicUrlRejectedError("redirect missing Location");
|
|
246
|
+
if (redirectCount >= MAX_PUBLIC_ROUTE_REDIRECTS) throw new PublicUrlRejectedError("redirect limit exceeded");
|
|
247
|
+
|
|
248
|
+
let nextUrl: URL;
|
|
249
|
+
try {
|
|
250
|
+
nextUrl = new URL(location, logicalUrl);
|
|
251
|
+
} catch {
|
|
252
|
+
throw new PublicUrlRejectedError("invalid redirect Location");
|
|
253
|
+
}
|
|
254
|
+
const guard = await validatePublicHttpUrl(nextUrl.href, { signal: deadline, resolver: resolverOption?.resolver });
|
|
255
|
+
if (deadline.aborted) throw deadline.reason;
|
|
256
|
+
if (!guard.ok) throw new PublicUrlRejectedError(guard.reason);
|
|
257
|
+
currentUrl = guard.url.href;
|
|
258
|
+
}
|
|
217
259
|
}
|
|
218
260
|
|
|
219
261
|
function attr(input: string, name: string): string | undefined {
|
|
@@ -267,11 +309,15 @@ function redditFeedUrls(url: URL): string[] {
|
|
|
267
309
|
return [`${base}/.rss`, `${base}.rss`];
|
|
268
310
|
}
|
|
269
311
|
|
|
270
|
-
async function routeReddit(
|
|
312
|
+
async function routeReddit(
|
|
313
|
+
url: URL,
|
|
314
|
+
signal?: AbortSignal,
|
|
315
|
+
resolverOption?: { resolver?: AddressResolver },
|
|
316
|
+
): Promise<RouteResult> {
|
|
271
317
|
const attempts: InsaneRouteAttempt[] = [];
|
|
272
318
|
for (const feedUrl of redditFeedUrls(url)) {
|
|
273
319
|
try {
|
|
274
|
-
const response = await fetchText(feedUrl, signal);
|
|
320
|
+
const response = await fetchText(feedUrl, signal, resolverOption);
|
|
275
321
|
const ok = response.status === 200 && /<(feed|rss)\b/i.test(response.text) && !isBlockedBody(response.text);
|
|
276
322
|
attempts.push(attempt("reddit", "rss", ok, response.status, response.text, ok ? "feed" : "no-feed-markers"));
|
|
277
323
|
if (ok)
|
|
@@ -325,13 +371,17 @@ function sourceFromOEmbed(raw: string, url: string): SearchSource | null {
|
|
|
325
371
|
return { title, url: data.url ?? url, snippet, author: data.author_name };
|
|
326
372
|
}
|
|
327
373
|
|
|
328
|
-
async function routeX(
|
|
374
|
+
async function routeX(
|
|
375
|
+
url: URL,
|
|
376
|
+
signal?: AbortSignal,
|
|
377
|
+
resolverOption?: { resolver?: AddressResolver },
|
|
378
|
+
): Promise<RouteResult> {
|
|
329
379
|
const attempts: InsaneRouteAttempt[] = [];
|
|
330
380
|
const id = tweetId(url);
|
|
331
381
|
if (id) {
|
|
332
382
|
const tweetResultUrl = `https://cdn.syndication.twimg.com/tweet-result?id=${encodeURIComponent(id)}&token=a`;
|
|
333
383
|
try {
|
|
334
|
-
const response = await fetchText(tweetResultUrl, signal);
|
|
384
|
+
const response = await fetchText(tweetResultUrl, signal, resolverOption);
|
|
335
385
|
const source =
|
|
336
386
|
response.status === 200 && !isBlockedBody(response.text)
|
|
337
387
|
? sourceFromTweetJson(response.text, url.toString())
|
|
@@ -346,7 +396,7 @@ async function routeX(url: URL, signal?: AbortSignal): Promise<RouteResult> {
|
|
|
346
396
|
}
|
|
347
397
|
const oembedUrl = `https://publish.twitter.com/oembed?url=${encodeURIComponent(`https://twitter.com/i/status/${id}`)}&omit_script=1`;
|
|
348
398
|
try {
|
|
349
|
-
const response = await fetchText(oembedUrl, signal);
|
|
399
|
+
const response = await fetchText(oembedUrl, signal, resolverOption);
|
|
350
400
|
const source =
|
|
351
401
|
response.status === 200 && !isBlockedBody(response.text)
|
|
352
402
|
? sourceFromOEmbed(response.text, oembedUrl)
|
|
@@ -363,7 +413,7 @@ async function routeX(url: URL, signal?: AbortSignal): Promise<RouteResult> {
|
|
|
363
413
|
if (handle) {
|
|
364
414
|
const timelineUrl = `https://syndication.twitter.com/srv/timeline-profile/screen-name/${encodeURIComponent(handle)}`;
|
|
365
415
|
try {
|
|
366
|
-
const response = await fetchText(timelineUrl, signal);
|
|
416
|
+
const response = await fetchText(timelineUrl, signal, resolverOption);
|
|
367
417
|
const ok = response.status === 200 && response.text.includes("__NEXT_DATA__") && !isBlockedBody(response.text);
|
|
368
418
|
attempts.push(
|
|
369
419
|
attempt("x", "syndication-timeline", ok, response.status, response.text, ok ? "timeline" : "no-next-data"),
|
|
@@ -396,14 +446,18 @@ function youtubeVideoId(url: URL): string | null {
|
|
|
396
446
|
return url.searchParams.get("v") ?? url.pathname.match(/\/shorts\/([^/?#]+)/)?.[1] ?? null;
|
|
397
447
|
}
|
|
398
448
|
|
|
399
|
-
async function routeYouTube(
|
|
449
|
+
async function routeYouTube(
|
|
450
|
+
url: URL,
|
|
451
|
+
signal?: AbortSignal,
|
|
452
|
+
resolverOption?: { resolver?: AddressResolver },
|
|
453
|
+
): Promise<RouteResult> {
|
|
400
454
|
const attempts: InsaneRouteAttempt[] = [];
|
|
401
455
|
const videoId = youtubeVideoId(url);
|
|
402
456
|
if (videoId) {
|
|
403
457
|
const watchUrl = `https://www.youtube.com/watch?v=${encodeURIComponent(videoId)}`;
|
|
404
458
|
const oembedUrl = `https://www.youtube.com/oembed?url=${encodeURIComponent(watchUrl)}&format=json`;
|
|
405
459
|
try {
|
|
406
|
-
const response = await fetchText(oembedUrl, signal);
|
|
460
|
+
const response = await fetchText(oembedUrl, signal, resolverOption);
|
|
407
461
|
const source =
|
|
408
462
|
response.status === 200 && !isBlockedBody(response.text) ? sourceFromOEmbed(response.text, watchUrl) : null;
|
|
409
463
|
attempts.push(
|
|
@@ -418,7 +472,7 @@ async function routeYouTube(url: URL, signal?: AbortSignal): Promise<RouteResult
|
|
|
418
472
|
if (channelId) {
|
|
419
473
|
const feedUrl = `https://www.youtube.com/feeds/videos.xml?channel_id=${encodeURIComponent(channelId)}`;
|
|
420
474
|
try {
|
|
421
|
-
const response = await fetchText(feedUrl, signal);
|
|
475
|
+
const response = await fetchText(feedUrl, signal, resolverOption);
|
|
422
476
|
const ok = response.status === 200 && /<feed\b/i.test(response.text) && !isBlockedBody(response.text);
|
|
423
477
|
attempts.push(attempt("youtube", "feed", ok, response.status, response.text, ok ? "feed" : "no-feed"));
|
|
424
478
|
if (ok)
|
|
@@ -466,13 +520,17 @@ function hnSourceFromItem(raw: string): SearchSource | null {
|
|
|
466
520
|
};
|
|
467
521
|
}
|
|
468
522
|
|
|
469
|
-
async function routeHackerNews(
|
|
523
|
+
async function routeHackerNews(
|
|
524
|
+
url: URL,
|
|
525
|
+
signal?: AbortSignal,
|
|
526
|
+
resolverOption?: { resolver?: AddressResolver },
|
|
527
|
+
): Promise<RouteResult> {
|
|
470
528
|
const attempts: InsaneRouteAttempt[] = [];
|
|
471
529
|
const id = hnItemId(url);
|
|
472
530
|
if (!id) return { platform: "hackernews", attempts };
|
|
473
531
|
const itemUrl = `https://hacker-news.firebaseio.com/v0/item/${encodeURIComponent(id)}.json`;
|
|
474
532
|
try {
|
|
475
|
-
const response = await fetchText(itemUrl, signal);
|
|
533
|
+
const response = await fetchText(itemUrl, signal, resolverOption);
|
|
476
534
|
const source = response.status === 200 && !isBlockedBody(response.text) ? hnSourceFromItem(response.text) : null;
|
|
477
535
|
attempts.push(
|
|
478
536
|
attempt("hackernews", "firebase-item", !!source, response.status, response.text, source ? "item" : "no-item"),
|
|
@@ -487,14 +545,18 @@ async function routeHackerNews(url: URL, signal?: AbortSignal): Promise<RouteRes
|
|
|
487
545
|
return { platform: "hackernews", attempts };
|
|
488
546
|
}
|
|
489
547
|
|
|
490
|
-
export async function routeInsanePublicUrl(
|
|
548
|
+
export async function routeInsanePublicUrl(
|
|
549
|
+
rawUrl: string,
|
|
550
|
+
signal?: AbortSignal,
|
|
551
|
+
resolverOption?: { resolver?: AddressResolver },
|
|
552
|
+
): Promise<RouteResult> {
|
|
491
553
|
const url = parseHttpUrl(rawUrl);
|
|
492
554
|
if (!url) return null;
|
|
493
555
|
const platform = detectPlatform(url);
|
|
494
|
-
if (platform === "reddit") return routeReddit(url, signal);
|
|
495
|
-
if (platform === "x") return routeX(url, signal);
|
|
496
|
-
if (platform === "youtube") return routeYouTube(url, signal);
|
|
497
|
-
if (platform === "hackernews") return routeHackerNews(url, signal);
|
|
556
|
+
if (platform === "reddit") return routeReddit(url, signal, resolverOption);
|
|
557
|
+
if (platform === "x") return routeX(url, signal, resolverOption);
|
|
558
|
+
if (platform === "youtube") return routeYouTube(url, signal, resolverOption);
|
|
559
|
+
if (platform === "hackernews") return routeHackerNews(url, signal, resolverOption);
|
|
498
560
|
return null;
|
|
499
561
|
}
|
|
500
562
|
|
|
@@ -528,9 +590,10 @@ export async function searchInsane(params: {
|
|
|
528
590
|
num_results?: number;
|
|
529
591
|
recency?: "day" | "week" | "month" | "year";
|
|
530
592
|
signal?: AbortSignal;
|
|
593
|
+
resolverOption?: { resolver?: AddressResolver };
|
|
531
594
|
}): Promise<SearchResponse> {
|
|
532
595
|
const numResults = clampNumResults(params.num_results, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
|
|
533
|
-
const direct = await routeInsanePublicUrl(params.query, params.signal);
|
|
596
|
+
const direct = await routeInsanePublicUrl(params.query, params.signal, params.resolverOption);
|
|
534
597
|
if (isSuccess(direct) && direct.sources.length > 0) {
|
|
535
598
|
return {
|
|
536
599
|
provider: "insane",
|
|
@@ -560,7 +623,7 @@ export async function searchInsane(params: {
|
|
|
560
623
|
const seenUrls = new Set<string>();
|
|
561
624
|
for (const candidate of discovery.sources) {
|
|
562
625
|
if (routedSources.length >= numResults) break;
|
|
563
|
-
const routed = await routeInsanePublicUrl(candidate.url, params.signal);
|
|
626
|
+
const routed = await routeInsanePublicUrl(candidate.url, params.signal, params.resolverOption);
|
|
564
627
|
if (routed && "attempts" in routed) attempts.push(...routed.attempts);
|
|
565
628
|
if (!isSuccess(routed)) continue;
|
|
566
629
|
for (const source of routed.sources) {
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
* SEARXNG_BASIC_USERNAME - Optional RFC 7617 Basic auth username
|
|
23
23
|
* SEARXNG_BASIC_PASSWORD - Optional RFC 7617 Basic auth password
|
|
24
24
|
*
|
|
25
|
+
* A project settings endpoint must not receive a credential that exists only in
|
|
26
|
+
* the operator environment. The project file can still authenticate with a
|
|
27
|
+
* credential it sets itself, and an operator endpoint still receives an
|
|
28
|
+
* operator environment credential.
|
|
29
|
+
*
|
|
25
30
|
* Reference: https://docs.searxng.org/dev/search_api.html
|
|
26
31
|
*/
|
|
27
32
|
|
|
@@ -80,7 +85,6 @@ interface SearXNGAuth {
|
|
|
80
85
|
value: string;
|
|
81
86
|
}
|
|
82
87
|
|
|
83
|
-
/** Find SearXNG endpoint from settings or environment. */
|
|
84
88
|
/**
|
|
85
89
|
* SearXNG environment value, from trusted sources only, preserving an
|
|
86
90
|
* intentionally empty setting.
|
|
@@ -122,6 +126,7 @@ export function resolveSearxngConfigForTest(): {
|
|
|
122
126
|
};
|
|
123
127
|
}
|
|
124
128
|
|
|
129
|
+
/** Find SearXNG endpoint from settings or environment. */
|
|
125
130
|
function findEndpoint(activeSettings?: Settings): string | null {
|
|
126
131
|
try {
|
|
127
132
|
const endpoint = (activeSettings ?? settings).get("searxng.endpoint");
|
|
@@ -132,36 +137,66 @@ function findEndpoint(activeSettings?: Settings): string | null {
|
|
|
132
137
|
return trustedSearxngEnv("SEARXNG_ENDPOINT");
|
|
133
138
|
}
|
|
134
139
|
|
|
140
|
+
/**
|
|
141
|
+
* True when `endpoint` is the value supplied by the project settings layer.
|
|
142
|
+
*
|
|
143
|
+
* `get` is global, then project, then runtime override. `getOverride` and
|
|
144
|
+
* `getGlobal` are the layers a project file cannot write, so a value that is
|
|
145
|
+
* present, not overridden, and different from the global layer came from the
|
|
146
|
+
* project file. An equal global value is the operator endpoint even if the
|
|
147
|
+
* project file repeats it.
|
|
148
|
+
*/
|
|
149
|
+
function endpointComesFromProjectLayer(activeSettings: Settings | undefined, endpoint: string | null): boolean {
|
|
150
|
+
if (!endpoint) return false;
|
|
151
|
+
try {
|
|
152
|
+
const active = activeSettings ?? settings;
|
|
153
|
+
if (active.getOverride("searxng.endpoint") !== undefined) return false;
|
|
154
|
+
if (!active.has("searxng.endpoint")) return false;
|
|
155
|
+
const effective = active.get("searxng.endpoint");
|
|
156
|
+
if (effective !== endpoint) return false;
|
|
157
|
+
const globalValue = active.getGlobal("searxng.endpoint");
|
|
158
|
+
return globalValue === undefined || globalValue !== effective;
|
|
159
|
+
} catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
135
164
|
/** Find SearXNG bearer token from settings or environment. */
|
|
136
|
-
function findToken(activeSettings?: Settings): string | null {
|
|
165
|
+
function findToken(activeSettings?: Settings, allowEnvironment = true): string | null {
|
|
137
166
|
try {
|
|
138
167
|
const token = (activeSettings ?? settings).get("searxng.token");
|
|
139
168
|
if (token) return token;
|
|
140
169
|
} catch {
|
|
141
170
|
// Settings not initialized yet
|
|
171
|
+
return allowEnvironment ? trustedSearxngEnv("SEARXNG_TOKEN") : null;
|
|
142
172
|
}
|
|
173
|
+
if (!allowEnvironment) return null;
|
|
143
174
|
return trustedSearxngEnv("SEARXNG_TOKEN");
|
|
144
175
|
}
|
|
145
176
|
|
|
146
177
|
/** Find SearXNG Basic auth username from settings or environment. */
|
|
147
|
-
function findBasicUsername(activeSettings?: Settings): string | null {
|
|
178
|
+
function findBasicUsername(activeSettings?: Settings, allowEnvironment = true): string | null {
|
|
148
179
|
try {
|
|
149
180
|
const username = (activeSettings ?? settings).get("searxng.basicUsername");
|
|
150
181
|
if (username !== undefined) return username;
|
|
151
182
|
} catch {
|
|
152
183
|
// Settings not initialized yet
|
|
184
|
+
return allowEnvironment ? trustedSearxngEnv("SEARXNG_BASIC_USERNAME") : null;
|
|
153
185
|
}
|
|
186
|
+
if (!allowEnvironment) return null;
|
|
154
187
|
return trustedSearxngEnv("SEARXNG_BASIC_USERNAME");
|
|
155
188
|
}
|
|
156
189
|
|
|
157
190
|
/** Find SearXNG Basic auth password from settings or environment. */
|
|
158
|
-
function findBasicPassword(activeSettings?: Settings): string | null {
|
|
191
|
+
function findBasicPassword(activeSettings?: Settings, allowEnvironment = true): string | null {
|
|
159
192
|
try {
|
|
160
193
|
const password = (activeSettings ?? settings).get("searxng.basicPassword");
|
|
161
194
|
if (password !== undefined) return password;
|
|
162
195
|
} catch {
|
|
163
196
|
// Settings not initialized yet
|
|
197
|
+
return allowEnvironment ? trustedSearxngEnv("SEARXNG_BASIC_PASSWORD") : null;
|
|
164
198
|
}
|
|
199
|
+
if (!allowEnvironment) return null;
|
|
165
200
|
return trustedSearxngEnv("SEARXNG_BASIC_PASSWORD");
|
|
166
201
|
}
|
|
167
202
|
|
|
@@ -175,11 +210,26 @@ function hasControlCharacters(value: string): boolean {
|
|
|
175
210
|
return /[\u0000-\u001F\u007F-\u009F]/u.test(value);
|
|
176
211
|
}
|
|
177
212
|
|
|
178
|
-
/**
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
213
|
+
/**
|
|
214
|
+
* Find SearXNG authentication from settings or environment. Basic auth takes
|
|
215
|
+
* precedence over bearer tokens.
|
|
216
|
+
*
|
|
217
|
+
* When `endpoint` came from the project settings layer, environment credentials
|
|
218
|
+
* are omitted. A complete basic-auth pair or token set in settings is still used,
|
|
219
|
+
* including one the project file set itself. An incomplete basic-auth pair is not
|
|
220
|
+
* filled from the environment: the search continues with a project token, or with
|
|
221
|
+
* no Authorization header, instead of failing closed on the missing half.
|
|
222
|
+
*/
|
|
223
|
+
function findAuth(activeSettings?: Settings, endpoint?: string | null): SearXNGAuth | null {
|
|
224
|
+
const resolvedEndpoint = endpoint === undefined ? findEndpoint(activeSettings) : endpoint;
|
|
225
|
+
const allowEnvironment = !endpointComesFromProjectLayer(activeSettings, resolvedEndpoint);
|
|
226
|
+
const username = findBasicUsername(activeSettings, allowEnvironment);
|
|
227
|
+
const password = findBasicPassword(activeSettings, allowEnvironment);
|
|
228
|
+
const token = findToken(activeSettings, allowEnvironment);
|
|
229
|
+
if (!allowEnvironment && (username === null) !== (password === null)) {
|
|
230
|
+
return token ? { type: "bearer", value: token } : null;
|
|
231
|
+
}
|
|
232
|
+
return resolveBasicOrBearerAuth(username, password, token);
|
|
183
233
|
}
|
|
184
234
|
|
|
185
235
|
function resolveBasicOrBearerAuth(
|
|
@@ -322,7 +372,9 @@ export async function searchSearXNG(params: {
|
|
|
322
372
|
);
|
|
323
373
|
}
|
|
324
374
|
|
|
325
|
-
const auth = params.authStorage
|
|
375
|
+
const auth = params.authStorage
|
|
376
|
+
? await findAuthFromStorage(params.authStorage)
|
|
377
|
+
: findAuth(params.settings, endpoint);
|
|
326
378
|
|
|
327
379
|
let categories: string | undefined;
|
|
328
380
|
let language: string | undefined;
|