@gajae-code/coding-agent 0.2.3 → 0.2.5

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 (197) hide show
  1. package/CHANGELOG.md +34 -8600
  2. package/README.md +1 -1
  3. package/dist/types/async/job-manager.d.ts +61 -0
  4. package/dist/types/cli/update-cli.d.ts +3 -0
  5. package/dist/types/config/settings-schema.d.ts +27 -3
  6. package/dist/types/config/settings.d.ts +1 -1
  7. package/dist/types/defaults/gjc-defaults.d.ts +19 -6
  8. package/dist/types/discovery/helpers.d.ts +1 -0
  9. package/dist/types/exec/bash-executor.d.ts +8 -1
  10. package/dist/types/gjc-runtime/restricted-role-agent-bash.d.ts +2 -0
  11. package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -1
  12. package/dist/types/modes/components/settings-selector.d.ts +4 -0
  13. package/dist/types/modes/components/skill-hud/render.d.ts +1 -1
  14. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  15. package/dist/types/modes/interactive-mode.d.ts +2 -0
  16. package/dist/types/modes/theme/defaults/index.d.ts +45 -9351
  17. package/dist/types/modes/theme/theme.d.ts +6 -5
  18. package/dist/types/modes/types.d.ts +2 -0
  19. package/dist/types/sdk.d.ts +2 -0
  20. package/dist/types/session/streaming-output.d.ts +11 -0
  21. package/dist/types/skill-state/active-state.d.ts +1 -0
  22. package/dist/types/task/types.d.ts +1 -0
  23. package/dist/types/tools/bash-allowed-prefixes.d.ts +5 -0
  24. package/dist/types/tools/bash.d.ts +24 -0
  25. package/dist/types/tools/cron.d.ts +110 -0
  26. package/dist/types/tools/index.d.ts +4 -0
  27. package/dist/types/tools/monitor.d.ts +54 -0
  28. package/dist/types/web/search/index.d.ts +1 -0
  29. package/dist/types/web/search/provider.d.ts +11 -4
  30. package/dist/types/web/search/providers/duckduckgo.d.ts +57 -0
  31. package/dist/types/web/search/types.d.ts +1 -1
  32. package/package.json +7 -7
  33. package/src/async/job-manager.ts +224 -0
  34. package/src/cli/agents-cli.ts +3 -0
  35. package/src/cli/update-cli.ts +67 -16
  36. package/src/config/settings-schema.ts +30 -2
  37. package/src/config/settings.ts +44 -7
  38. package/src/defaults/gjc/skills/deep-interview/SKILL.md +48 -6
  39. package/src/defaults/gjc/skills/deep-interview/auto-answer-uncertain.md +37 -0
  40. package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +42 -0
  41. package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -4
  42. package/src/defaults/gjc/skills/ultragoal/SKILL.md +9 -6
  43. package/src/defaults/gjc-defaults.ts +68 -16
  44. package/src/discovery/helpers.ts +5 -0
  45. package/src/eval/js/shared/rewrite-imports.ts +1 -2
  46. package/src/exec/bash-executor.ts +20 -9
  47. package/src/gjc-runtime/deep-interview-runtime.ts +44 -0
  48. package/src/gjc-runtime/ralplan-runtime.ts +2 -0
  49. package/src/gjc-runtime/restricted-role-agent-bash.ts +5 -0
  50. package/src/gjc-runtime/state-runtime.ts +3 -2
  51. package/src/goals/tools/goal-tool.ts +5 -1
  52. package/src/hooks/skill-state.ts +1 -1
  53. package/src/internal-urls/docs-index.generated.ts +8 -4
  54. package/src/lsp/render.ts +1 -1
  55. package/src/memories/index.ts +5 -4
  56. package/src/modes/acp/acp-agent.ts +1 -1
  57. package/src/modes/acp/acp-client-bridge.ts +1 -1
  58. package/src/modes/components/agent-dashboard.ts +1 -1
  59. package/src/modes/components/diff.ts +2 -2
  60. package/src/modes/components/settings-selector.ts +25 -14
  61. package/src/modes/components/skill-hud/render.ts +7 -2
  62. package/src/modes/controllers/command-controller.ts +1 -1
  63. package/src/modes/controllers/input-controller.ts +10 -2
  64. package/src/modes/controllers/selector-controller.ts +67 -0
  65. package/src/modes/interactive-mode.ts +34 -3
  66. package/src/modes/theme/defaults/blue-crab.json +126 -0
  67. package/src/modes/theme/defaults/index.ts +2 -196
  68. package/src/modes/theme/theme.ts +75 -36
  69. package/src/modes/types.ts +2 -0
  70. package/src/prompts/agents/architect.md +5 -1
  71. package/src/prompts/agents/critic.md +5 -1
  72. package/src/prompts/agents/frontmatter.md +1 -0
  73. package/src/prompts/agents/planner.md +5 -1
  74. package/src/prompts/memories/unavailable.md +9 -0
  75. package/src/prompts/tools/bash.md +9 -0
  76. package/src/prompts/tools/cron.md +25 -0
  77. package/src/prompts/tools/monitor.md +30 -0
  78. package/src/runtime-mcp/oauth-flow.ts +4 -2
  79. package/src/sdk.ts +7 -0
  80. package/src/session/agent-session.ts +16 -5
  81. package/src/session/streaming-output.ts +21 -0
  82. package/src/skill-state/active-state.ts +163 -12
  83. package/src/slash-commands/builtin-registry.ts +11 -1
  84. package/src/task/agents.ts +1 -0
  85. package/src/task/executor.ts +1 -0
  86. package/src/task/types.ts +1 -0
  87. package/src/tools/bash-allowed-prefixes.ts +169 -0
  88. package/src/tools/bash.ts +190 -29
  89. package/src/tools/browser/tab-worker.ts +1 -1
  90. package/src/tools/cron.ts +665 -0
  91. package/src/tools/index.ts +20 -2
  92. package/src/tools/monitor.ts +136 -0
  93. package/src/vim/engine.ts +3 -3
  94. package/src/web/search/index.ts +31 -18
  95. package/src/web/search/provider.ts +57 -12
  96. package/src/web/search/providers/duckduckgo.ts +279 -0
  97. package/src/web/search/types.ts +2 -0
  98. package/src/modes/theme/dark.json +0 -95
  99. package/src/modes/theme/defaults/alabaster.json +0 -93
  100. package/src/modes/theme/defaults/amethyst.json +0 -96
  101. package/src/modes/theme/defaults/anthracite.json +0 -93
  102. package/src/modes/theme/defaults/basalt.json +0 -91
  103. package/src/modes/theme/defaults/birch.json +0 -95
  104. package/src/modes/theme/defaults/dark-abyss.json +0 -91
  105. package/src/modes/theme/defaults/dark-arctic.json +0 -104
  106. package/src/modes/theme/defaults/dark-aurora.json +0 -95
  107. package/src/modes/theme/defaults/dark-catppuccin.json +0 -107
  108. package/src/modes/theme/defaults/dark-cavern.json +0 -91
  109. package/src/modes/theme/defaults/dark-copper.json +0 -95
  110. package/src/modes/theme/defaults/dark-cosmos.json +0 -90
  111. package/src/modes/theme/defaults/dark-cyberpunk.json +0 -102
  112. package/src/modes/theme/defaults/dark-dracula.json +0 -98
  113. package/src/modes/theme/defaults/dark-eclipse.json +0 -91
  114. package/src/modes/theme/defaults/dark-ember.json +0 -95
  115. package/src/modes/theme/defaults/dark-equinox.json +0 -90
  116. package/src/modes/theme/defaults/dark-forest.json +0 -96
  117. package/src/modes/theme/defaults/dark-github.json +0 -105
  118. package/src/modes/theme/defaults/dark-gruvbox.json +0 -112
  119. package/src/modes/theme/defaults/dark-lavender.json +0 -95
  120. package/src/modes/theme/defaults/dark-lunar.json +0 -89
  121. package/src/modes/theme/defaults/dark-midnight.json +0 -95
  122. package/src/modes/theme/defaults/dark-monochrome.json +0 -94
  123. package/src/modes/theme/defaults/dark-monokai.json +0 -98
  124. package/src/modes/theme/defaults/dark-nebula.json +0 -90
  125. package/src/modes/theme/defaults/dark-nord.json +0 -97
  126. package/src/modes/theme/defaults/dark-ocean.json +0 -101
  127. package/src/modes/theme/defaults/dark-one.json +0 -100
  128. package/src/modes/theme/defaults/dark-poimandres.json +0 -141
  129. package/src/modes/theme/defaults/dark-rainforest.json +0 -91
  130. package/src/modes/theme/defaults/dark-reef.json +0 -91
  131. package/src/modes/theme/defaults/dark-retro.json +0 -92
  132. package/src/modes/theme/defaults/dark-rose-pine.json +0 -96
  133. package/src/modes/theme/defaults/dark-sakura.json +0 -95
  134. package/src/modes/theme/defaults/dark-slate.json +0 -95
  135. package/src/modes/theme/defaults/dark-solarized.json +0 -97
  136. package/src/modes/theme/defaults/dark-solstice.json +0 -90
  137. package/src/modes/theme/defaults/dark-starfall.json +0 -91
  138. package/src/modes/theme/defaults/dark-sunset.json +0 -99
  139. package/src/modes/theme/defaults/dark-swamp.json +0 -90
  140. package/src/modes/theme/defaults/dark-synthwave.json +0 -103
  141. package/src/modes/theme/defaults/dark-taiga.json +0 -91
  142. package/src/modes/theme/defaults/dark-terminal.json +0 -95
  143. package/src/modes/theme/defaults/dark-tokyo-night.json +0 -101
  144. package/src/modes/theme/defaults/dark-tundra.json +0 -91
  145. package/src/modes/theme/defaults/dark-twilight.json +0 -91
  146. package/src/modes/theme/defaults/dark-volcanic.json +0 -91
  147. package/src/modes/theme/defaults/graphite.json +0 -92
  148. package/src/modes/theme/defaults/light-arctic.json +0 -107
  149. package/src/modes/theme/defaults/light-aurora-day.json +0 -91
  150. package/src/modes/theme/defaults/light-canyon.json +0 -91
  151. package/src/modes/theme/defaults/light-catppuccin.json +0 -106
  152. package/src/modes/theme/defaults/light-cirrus.json +0 -90
  153. package/src/modes/theme/defaults/light-coral.json +0 -95
  154. package/src/modes/theme/defaults/light-cyberpunk.json +0 -96
  155. package/src/modes/theme/defaults/light-dawn.json +0 -90
  156. package/src/modes/theme/defaults/light-dunes.json +0 -91
  157. package/src/modes/theme/defaults/light-eucalyptus.json +0 -95
  158. package/src/modes/theme/defaults/light-forest.json +0 -100
  159. package/src/modes/theme/defaults/light-frost.json +0 -95
  160. package/src/modes/theme/defaults/light-github.json +0 -115
  161. package/src/modes/theme/defaults/light-glacier.json +0 -91
  162. package/src/modes/theme/defaults/light-gruvbox.json +0 -108
  163. package/src/modes/theme/defaults/light-haze.json +0 -90
  164. package/src/modes/theme/defaults/light-honeycomb.json +0 -95
  165. package/src/modes/theme/defaults/light-lagoon.json +0 -91
  166. package/src/modes/theme/defaults/light-lavender.json +0 -95
  167. package/src/modes/theme/defaults/light-meadow.json +0 -91
  168. package/src/modes/theme/defaults/light-mint.json +0 -95
  169. package/src/modes/theme/defaults/light-monochrome.json +0 -101
  170. package/src/modes/theme/defaults/light-ocean.json +0 -99
  171. package/src/modes/theme/defaults/light-one.json +0 -99
  172. package/src/modes/theme/defaults/light-opal.json +0 -91
  173. package/src/modes/theme/defaults/light-orchard.json +0 -91
  174. package/src/modes/theme/defaults/light-paper.json +0 -95
  175. package/src/modes/theme/defaults/light-poimandres.json +0 -141
  176. package/src/modes/theme/defaults/light-prism.json +0 -90
  177. package/src/modes/theme/defaults/light-retro.json +0 -98
  178. package/src/modes/theme/defaults/light-sand.json +0 -95
  179. package/src/modes/theme/defaults/light-savanna.json +0 -91
  180. package/src/modes/theme/defaults/light-solarized.json +0 -102
  181. package/src/modes/theme/defaults/light-soleil.json +0 -90
  182. package/src/modes/theme/defaults/light-sunset.json +0 -99
  183. package/src/modes/theme/defaults/light-synthwave.json +0 -98
  184. package/src/modes/theme/defaults/light-tokyo-night.json +0 -111
  185. package/src/modes/theme/defaults/light-wetland.json +0 -91
  186. package/src/modes/theme/defaults/light-zenith.json +0 -89
  187. package/src/modes/theme/defaults/limestone.json +0 -94
  188. package/src/modes/theme/defaults/mahogany.json +0 -97
  189. package/src/modes/theme/defaults/marble.json +0 -93
  190. package/src/modes/theme/defaults/obsidian.json +0 -91
  191. package/src/modes/theme/defaults/onyx.json +0 -91
  192. package/src/modes/theme/defaults/pearl.json +0 -93
  193. package/src/modes/theme/defaults/porcelain.json +0 -91
  194. package/src/modes/theme/defaults/quartz.json +0 -96
  195. package/src/modes/theme/defaults/sandstone.json +0 -95
  196. package/src/modes/theme/defaults/titanium.json +0 -90
  197. package/src/modes/theme/light.json +0 -93
@@ -0,0 +1,279 @@
1
+ /**
2
+ * DuckDuckGo Web Search Provider
3
+ *
4
+ * Keyless, permissionless web search. Scrapes DuckDuckGo's no-JavaScript HTML
5
+ * endpoints and maps anchors/snippets into the unified SearchResponse shape
6
+ * (sources only — DuckDuckGo does not synthesize an answer).
7
+ *
8
+ * This is the zero-config default/fallback backend: it requires no API key and
9
+ * no OAuth, so `isAvailable()` is always true. Because DuckDuckGo applies
10
+ * anti-bot rate limiting (HTTP 202 / 403 / empty responses) from datacenter and
11
+ * VPN IPs, the provider is best-effort: it retries with backoff, rotates the
12
+ * user-agent, and alternates between the `html` and `lite` endpoints. When every
13
+ * attempt fails it throws a {@link SearchProviderError} rather than returning an
14
+ * empty success — it never falls through to keyed providers.
15
+ *
16
+ * Endpoints:
17
+ * https://html.duckduckgo.com/html/ (primary)
18
+ * https://lite.duckduckgo.com/lite/ (fallback markup)
19
+ *
20
+ * The HTML markup is liable to drift; the parser is deliberately small and is
21
+ * pinned by fixture-driven tests (see test/tools/web-search-duckduckgo.test.ts).
22
+ */
23
+
24
+ import type { AuthStorage } from "@gajae-code/ai";
25
+
26
+ import type { SearchResponse, SearchSource } from "../../../web/search/types";
27
+ import { SearchProviderError } from "../../../web/search/types";
28
+ import { clampNumResults } from "../utils";
29
+ import type { SearchParams } from "./base";
30
+ import { SearchProvider } from "./base";
31
+ import { classifyProviderHttpError, withHardTimeout } from "./utils";
32
+
33
+ const HTML_ENDPOINT = "https://html.duckduckgo.com/html/";
34
+ const LITE_ENDPOINT = "https://lite.duckduckgo.com/lite/";
35
+
36
+ const DEFAULT_NUM_RESULTS = 10;
37
+ const MAX_NUM_RESULTS = 20;
38
+
39
+ /** Endpoint order across retry attempts; rotates markup and user-agent. */
40
+ const ATTEMPTS: Array<"html" | "lite"> = ["html", "lite", "html"];
41
+
42
+ /** Backoff (ms) applied between attempts. Index 0 is unused (first attempt). */
43
+ const BACKOFF_MS = [0, 400, 800];
44
+
45
+ /** Realistic desktop user-agents rotated per attempt to dodge naive blocks. */
46
+ const USER_AGENTS = [
47
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
48
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
49
+ "Mozilla/5.0 (X11; Linux x86_64; rv:125.0) Gecko/20100101 Firefox/125.0",
50
+ ];
51
+
52
+ /** Map our recency filter to DuckDuckGo's `df` time parameter. */
53
+ const RECENCY_MAP: Record<"day" | "week" | "month" | "year", string> = {
54
+ day: "d",
55
+ week: "w",
56
+ month: "m",
57
+ year: "y",
58
+ };
59
+
60
+ interface ParsedResult {
61
+ title: string;
62
+ url: string;
63
+ snippet?: string;
64
+ }
65
+
66
+ /** Decode a small set of HTML entities without pulling in a DOM library. */
67
+ function decodeEntities(input: string): string {
68
+ return input
69
+ .replace(/&amp;/g, "&")
70
+ .replace(/&lt;/g, "<")
71
+ .replace(/&gt;/g, ">")
72
+ .replace(/&quot;/g, '"')
73
+ .replace(/&#0*39;|&#x0*27;|&apos;/gi, "'")
74
+ .replace(/&#x0*2f;/gi, "/")
75
+ .replace(/&#(\d+);/g, (_, dec: string) => String.fromCodePoint(Number(dec)))
76
+ .replace(/&#x([0-9a-f]+);/gi, (_, hex: string) => String.fromCodePoint(Number.parseInt(hex, 16)))
77
+ .replace(/&nbsp;/g, " ");
78
+ }
79
+
80
+ /** Strip tags, decode entities, and collapse whitespace from an HTML fragment. */
81
+ function cleanText(fragment: string): string {
82
+ return decodeEntities(fragment.replace(/<[^>]+>/g, ""))
83
+ .replace(/\s+/g, " ")
84
+ .trim();
85
+ }
86
+
87
+ /**
88
+ * Resolve a DuckDuckGo result href to the real destination URL. DuckDuckGo wraps
89
+ * external links in a `/l/?uddg=<encoded>` redirect; `lite` sometimes links
90
+ * directly. Returns null for unusable or internal links (so ads/redirect shells
91
+ * are dropped).
92
+ */
93
+ export function decodeResultUrl(href: string): string | null {
94
+ let h = decodeEntities(href.trim());
95
+ if (!h || h.startsWith("#")) return null;
96
+ if (h.startsWith("//")) h = `https:${h}`;
97
+ let parsed: URL;
98
+ try {
99
+ parsed = new URL(h, "https://duckduckgo.com");
100
+ } catch {
101
+ return null;
102
+ }
103
+ const uddg = parsed.searchParams.get("uddg");
104
+ if (uddg) {
105
+ try {
106
+ const target = new URL(uddg);
107
+ if (target.protocol !== "http:" && target.protocol !== "https:") return null;
108
+ if (target.hostname.endsWith("duckduckgo.com")) return null;
109
+ return target.toString();
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+ // No redirect wrapper: accept only real external http(s) links.
115
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
116
+ if (parsed.hostname.endsWith("duckduckgo.com")) return null;
117
+ return parsed.toString();
118
+ }
119
+
120
+ /** Parse results from the `html.duckduckgo.com/html/` markup. */
121
+ export function parseHtmlResults(html: string): ParsedResult[] {
122
+ const titleRe = /<a\b[^>]*class="[^"]*\bresult__a\b[^"]*"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi;
123
+ const snippetRe = /<a\b[^>]*class="[^"]*\bresult__snippet\b[^"]*"[^>]*>([\s\S]*?)<\/a>/gi;
124
+ const snippets: string[] = [];
125
+ for (const m of html.matchAll(snippetRe)) snippets.push(cleanText(m[1]));
126
+
127
+ const results: ParsedResult[] = [];
128
+ let idx = 0;
129
+ for (const m of html.matchAll(titleRe)) {
130
+ const url = decodeResultUrl(m[1]);
131
+ const title = cleanText(m[2]);
132
+ const snippet = snippets[idx];
133
+ idx++;
134
+ if (!url || !title) continue;
135
+ results.push({ title, url, snippet: snippet || undefined });
136
+ }
137
+ return results;
138
+ }
139
+
140
+ /** Parse results from the `lite.duckduckgo.com/lite/` markup. */
141
+ export function parseLiteResults(html: string): ParsedResult[] {
142
+ const linkRe = /<a\b[^>]*class="[^"]*\bresult-link\b[^"]*"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi;
143
+ const snippetRe = /<td\b[^>]*class="[^"]*\bresult-snippet\b[^"]*"[^>]*>([\s\S]*?)<\/td>/gi;
144
+ const snippets: string[] = [];
145
+ for (const m of html.matchAll(snippetRe)) snippets.push(cleanText(m[1]));
146
+
147
+ const results: ParsedResult[] = [];
148
+ let idx = 0;
149
+ for (const m of html.matchAll(linkRe)) {
150
+ const url = decodeResultUrl(m[1]);
151
+ const title = cleanText(m[2]);
152
+ const snippet = snippets[idx];
153
+ idx++;
154
+ if (!url || !title) continue;
155
+ results.push({ title, url, snippet: snippet || undefined });
156
+ }
157
+ return results;
158
+ }
159
+
160
+ function delay(ms: number, signal?: AbortSignal): Promise<void> {
161
+ return new Promise((resolve, reject) => {
162
+ if (signal?.aborted) {
163
+ reject(new DOMException("Aborted", "AbortError"));
164
+ return;
165
+ }
166
+ const timer = setTimeout(resolve, ms);
167
+ signal?.addEventListener(
168
+ "abort",
169
+ () => {
170
+ clearTimeout(timer);
171
+ reject(new DOMException("Aborted", "AbortError"));
172
+ },
173
+ { once: true },
174
+ );
175
+ });
176
+ }
177
+
178
+ /** Fetch one endpoint and parse it. Throws on HTTP error, rate-limit, or empty parse. */
179
+ async function fetchAndParse(
180
+ endpoint: "html" | "lite",
181
+ query: string,
182
+ df: string | undefined,
183
+ userAgent: string,
184
+ signal: AbortSignal | undefined,
185
+ ): Promise<ParsedResult[]> {
186
+ const url = endpoint === "html" ? HTML_ENDPOINT : LITE_ENDPOINT;
187
+ const body = new URLSearchParams({ q: query });
188
+ if (df) body.set("df", df);
189
+
190
+ const response = await fetch(url, {
191
+ method: "POST",
192
+ headers: {
193
+ "User-Agent": userAgent,
194
+ Accept: "text/html,application/xhtml+xml",
195
+ "Content-Type": "application/x-www-form-urlencoded",
196
+ "Accept-Language": "en-US,en;q=0.9",
197
+ },
198
+ body,
199
+ signal: withHardTimeout(signal),
200
+ });
201
+
202
+ // DuckDuckGo signals soft blocks with 202 (which is still response.ok).
203
+ if (response.status === 202) {
204
+ throw new SearchProviderError("duckduckgo", "duckduckgo: rate-limited (202)", 202);
205
+ }
206
+ if (!response.ok) {
207
+ const errorText = await response.text();
208
+ const classified = classifyProviderHttpError("duckduckgo", response.status, errorText);
209
+ if (classified) throw classified;
210
+ throw new SearchProviderError("duckduckgo", `DuckDuckGo error (${response.status})`, response.status);
211
+ }
212
+
213
+ const text = await response.text();
214
+ const parsed = endpoint === "html" ? parseHtmlResults(text) : parseLiteResults(text);
215
+ if (parsed.length === 0) {
216
+ throw new SearchProviderError("duckduckgo", "duckduckgo: no parseable results (possible block)");
217
+ }
218
+ return parsed;
219
+ }
220
+
221
+ /** Execute a keyless DuckDuckGo web search with light resilience. */
222
+ export async function searchDuckDuckGo(params: {
223
+ query: string;
224
+ num_results?: number;
225
+ recency?: "day" | "week" | "month" | "year";
226
+ signal?: AbortSignal;
227
+ }): Promise<SearchResponse> {
228
+ const numResults = clampNumResults(params.num_results, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
229
+ const df = params.recency ? RECENCY_MAP[params.recency] : undefined;
230
+
231
+ let lastError: unknown;
232
+ for (let attempt = 0; attempt < ATTEMPTS.length; attempt++) {
233
+ if (params.signal?.aborted) throw new DOMException("Aborted", "AbortError");
234
+ if (BACKOFF_MS[attempt] > 0) await delay(BACKOFF_MS[attempt], params.signal);
235
+
236
+ const endpoint = ATTEMPTS[attempt];
237
+ const userAgent = USER_AGENTS[attempt % USER_AGENTS.length];
238
+ try {
239
+ const parsed = await fetchAndParse(endpoint, params.query, df, userAgent, params.signal);
240
+ const sources: SearchSource[] = parsed.slice(0, numResults).map(result => ({
241
+ title: result.title,
242
+ url: result.url,
243
+ snippet: result.snippet,
244
+ }));
245
+ return { provider: "duckduckgo", sources };
246
+ } catch (error) {
247
+ // A caller cancellation must abort immediately, never silently retry.
248
+ if (params.signal?.aborted) throw error;
249
+ lastError = error;
250
+ }
251
+ }
252
+
253
+ if (lastError instanceof SearchProviderError) throw lastError;
254
+ throw new SearchProviderError(
255
+ "duckduckgo",
256
+ `DuckDuckGo search failed after ${ATTEMPTS.length} attempts${
257
+ lastError instanceof Error ? `: ${lastError.message}` : ""
258
+ }`,
259
+ );
260
+ }
261
+
262
+ /** Keyless, permissionless web search provider backed by DuckDuckGo. */
263
+ export class DuckDuckGoProvider extends SearchProvider {
264
+ readonly id = "duckduckgo";
265
+ readonly label = "DuckDuckGo";
266
+
267
+ isAvailable(_authStorage: AuthStorage): boolean {
268
+ return true;
269
+ }
270
+
271
+ search(params: SearchParams): Promise<SearchResponse> {
272
+ return searchDuckDuckGo({
273
+ query: params.query,
274
+ num_results: params.numSearchResults ?? params.limit,
275
+ recency: params.recency,
276
+ signal: params.signal,
277
+ });
278
+ }
279
+ }
@@ -6,6 +6,7 @@
6
6
 
7
7
  /** Supported web search providers */
8
8
  export type SearchProviderId =
9
+ | "duckduckgo"
9
10
  | "exa"
10
11
  | "brave"
11
12
  | "jina"
@@ -23,6 +24,7 @@ export type SearchProviderId =
23
24
 
24
25
  export function isSearchProviderId(value: string): value is SearchProviderId {
25
26
  return [
27
+ "duckduckgo",
26
28
  "exa",
27
29
  "brave",
28
30
  "jina",
@@ -1,95 +0,0 @@
1
- {
2
- "$schema": "https://raw.githubusercontent.com/can1357/gajae-code/main/packages/coding-agent/theme-schema.json",
3
- "name": "dark",
4
- "vars": {
5
- "cyan": "#0088fa",
6
- "blue": "#178fb9",
7
- "green": "#89d281",
8
- "red": "#fc3a4b",
9
- "yellow": "#e4c00f",
10
- "gray": "#777d88",
11
- "dimGray": "#5f6673",
12
- "darkGray": "#3d424a",
13
- "accent": "#febc38",
14
- "selectedBg": "#31363f",
15
- "userMsgBg": "#221d1a",
16
- "toolPendingBg": "#1d2129",
17
- "toolSuccessBg": "#161a1f",
18
- "toolErrorBg": "#291d1d",
19
- "customMsgBg": "#2a2530"
20
- },
21
- "colors": {
22
- "accent": "accent",
23
- "border": "blue",
24
- "borderAccent": "cyan",
25
- "borderMuted": "darkGray",
26
- "success": "green",
27
- "error": "red",
28
- "warning": "yellow",
29
- "muted": "gray",
30
- "dim": "dimGray",
31
- "text": "",
32
- "thinkingText": "gray",
33
- "selectedBg": "selectedBg",
34
- "userMessageBg": "userMsgBg",
35
- "userMessageText": "",
36
- "customMessageBg": "customMsgBg",
37
- "customMessageText": "",
38
- "customMessageLabel": "#b281d6",
39
- "toolPendingBg": "toolPendingBg",
40
- "toolSuccessBg": "toolSuccessBg",
41
- "toolErrorBg": "toolErrorBg",
42
- "toolTitle": "",
43
- "toolOutput": "gray",
44
- "mdHeading": "#febc38",
45
- "mdLink": "#0088fa",
46
- "mdLinkUrl": "dimGray",
47
- "mdCode": "#e5c1ff",
48
- "mdCodeBlock": "#9CDCFE",
49
- "mdCodeBlockBorder": "darkGray",
50
- "mdQuote": "gray",
51
- "mdQuoteBorder": "darkGray",
52
- "mdHr": "darkGray",
53
- "mdListBullet": "accent",
54
- "toolDiffAdded": "green",
55
- "toolDiffRemoved": "red",
56
- "toolDiffContext": "gray",
57
- "link": "#0088fa",
58
- "syntaxComment": "#6A9955",
59
- "syntaxKeyword": "#569CD6",
60
- "syntaxFunction": "#DCDCAA",
61
- "syntaxVariable": "#9CDCFE",
62
- "syntaxString": "#CE9178",
63
- "syntaxNumber": "#B5CEA8",
64
- "syntaxType": "#4EC9B0",
65
- "syntaxOperator": "#D4D4D4",
66
- "syntaxPunctuation": "#D4D4D4",
67
- "thinkingOff": "darkGray",
68
- "thinkingMinimal": "dimGray",
69
- "thinkingLow": "#178fb9",
70
- "thinkingMedium": "#0088fa",
71
- "thinkingHigh": "#b281d6",
72
- "thinkingXhigh": "#e5c1ff",
73
- "bashMode": "cyan",
74
- "statusLineBg": "#121212",
75
- "statusLineSep": 244,
76
- "statusLineModel": "#d787af",
77
- "statusLinePath": "#00afaf",
78
- "statusLineGitClean": "#5faf5f",
79
- "statusLineGitDirty": "#d7af5f",
80
- "statusLineContext": "#8787af",
81
- "statusLineSpend": "#5fafaf",
82
- "statusLineStaged": 70,
83
- "statusLineDirty": 178,
84
- "statusLineUntracked": 39,
85
- "statusLineOutput": 205,
86
- "statusLineCost": 205,
87
- "statusLineSubagents": "accent",
88
- "pythonMode": "yellow"
89
- },
90
- "export": {
91
- "pageBg": "#18181e",
92
- "cardBg": "#1e1e24",
93
- "infoBg": "#3c3728"
94
- }
95
- }
@@ -1,93 +0,0 @@
1
- {
2
- "$schema": "https://raw.githubusercontent.com/can1357/gajae-code/main/packages/coding-agent/theme-schema.json",
3
- "name": "alabaster",
4
- "vars": {
5
- "alabaster": "#fdfcfb",
6
- "chiseledBlack": "#1a1c20",
7
- "shadowGray": "#505860",
8
- "carvedDepth": "#6a7080",
9
- "subtleShadow": "#e8e8ec",
10
- "lightShadow": "#f0f0f4",
11
- "accent": "#404850",
12
- "warmShadow": "#8a7060",
13
- "selectedBg": "#f0eff0",
14
- "userMsgBg": "#f5f4f5",
15
- "toolPendingBg": "#f3f3f8",
16
- "toolSuccessBg": "#f4f5f4",
17
- "toolErrorBg": "#f8f3f3",
18
- "customMsgBg": "#f6f4f7"
19
- },
20
- "colors": {
21
- "accent": "shadowGray",
22
- "border": "subtleShadow",
23
- "borderAccent": "shadowGray",
24
- "borderMuted": "lightShadow",
25
- "success": "#405840",
26
- "error": "#704040",
27
- "warning": "#806850",
28
- "muted": "carvedDepth",
29
- "dim": "#909098",
30
- "text": "",
31
- "thinkingText": "carvedDepth",
32
- "selectedBg": "selectedBg",
33
- "userMessageBg": "userMsgBg",
34
- "userMessageText": "",
35
- "customMessageBg": "customMsgBg",
36
- "customMessageText": "",
37
- "customMessageLabel": "shadowGray",
38
- "toolPendingBg": "toolPendingBg",
39
- "toolSuccessBg": "toolSuccessBg",
40
- "toolErrorBg": "toolErrorBg",
41
- "toolTitle": "",
42
- "toolOutput": "carvedDepth",
43
- "mdHeading": "chiseledBlack",
44
- "mdLink": "shadowGray",
45
- "mdLinkUrl": "carvedDepth",
46
- "mdCode": "accent",
47
- "mdCodeBlock": "shadowGray",
48
- "mdCodeBlockBorder": "subtleShadow",
49
- "mdQuote": "carvedDepth",
50
- "mdQuoteBorder": "subtleShadow",
51
- "mdHr": "subtleShadow",
52
- "mdListBullet": "shadowGray",
53
- "toolDiffAdded": "#405840",
54
- "toolDiffRemoved": "#704040",
55
- "toolDiffContext": "carvedDepth",
56
- "syntaxComment": "#707880",
57
- "syntaxKeyword": "#303840",
58
- "syntaxFunction": "chiseledBlack",
59
- "syntaxVariable": "chiseledBlack",
60
- "syntaxString": "warmShadow",
61
- "syntaxNumber": "shadowGray",
62
- "syntaxType": "#303840",
63
- "syntaxOperator": "#404850",
64
- "syntaxPunctuation": "#505860",
65
- "thinkingOff": "#c0c0c8",
66
- "thinkingMinimal": "#a0a0a8",
67
- "thinkingLow": "#808090",
68
- "thinkingMedium": "#606870",
69
- "thinkingHigh": "#505860",
70
- "thinkingXhigh": "#303840",
71
- "bashMode": "#405840",
72
- "statusLineBg": "#ececf0",
73
- "statusLineSep": "#a0a0a8",
74
- "statusLineModel": "#505860",
75
- "statusLinePath": "#404850",
76
- "statusLineGitClean": "#305030",
77
- "statusLineGitDirty": "#806030",
78
- "statusLineContext": "#404860",
79
- "statusLineSpend": "#405050",
80
- "statusLineStaged": 28,
81
- "statusLineDirty": 136,
82
- "statusLineUntracked": 31,
83
- "statusLineOutput": 133,
84
- "statusLineCost": 133,
85
- "statusLineSubagents": "shadowGray",
86
- "pythonMode": "#f0c040"
87
- },
88
- "export": {
89
- "pageBg": "#fdfcfb",
90
- "cardBg": "#ffffff",
91
- "infoBg": "#faf9f8"
92
- }
93
- }
@@ -1,96 +0,0 @@
1
- {
2
- "$schema": "https://raw.githubusercontent.com/can1357/gajae-code/main/packages/coding-agent/theme-schema.json",
3
- "name": "amethyst",
4
- "vars": {
5
- "amethyst": "#b48eff",
6
- "violet": "#9b6fd8",
7
- "deepPurple": "#6b4d9e",
8
- "quartz": "#f0e8f4",
9
- "roseQuartz": "#e5b4d4",
10
- "gold": "#d4af37",
11
- "lavender": "#c8a8e9",
12
- "crystalFacet": "#2a2435",
13
- "caveDark": "#140f18",
14
- "caveDeep": "#0f0b13",
15
- "selectedBg": "#221b28",
16
- "userMsgBg": "#1a1420",
17
- "toolPendingBg": "#1b1624",
18
- "toolSuccessBg": "#151220",
19
- "toolErrorBg": "#261418",
20
- "customMsgBg": "#1f1828"
21
- },
22
- "colors": {
23
- "accent": "amethyst",
24
- "border": "deepPurple",
25
- "borderAccent": "amethyst",
26
- "borderMuted": "crystalFacet",
27
- "success": "#8eb897",
28
- "error": "#d96c75",
29
- "warning": "gold",
30
- "muted": "#8b7a99",
31
- "dim": "#6a5a78",
32
- "text": "",
33
- "thinkingText": "#8b7a99",
34
- "selectedBg": "selectedBg",
35
- "userMessageBg": "userMsgBg",
36
- "userMessageText": "",
37
- "customMessageBg": "customMsgBg",
38
- "customMessageText": "",
39
- "customMessageLabel": "lavender",
40
- "toolPendingBg": "toolPendingBg",
41
- "toolSuccessBg": "toolSuccessBg",
42
- "toolErrorBg": "toolErrorBg",
43
- "toolTitle": "",
44
- "toolOutput": "#8b7a99",
45
- "mdHeading": "amethyst",
46
- "mdLink": "violet",
47
- "mdLinkUrl": "#6a5a78",
48
- "mdCode": "roseQuartz",
49
- "mdCodeBlock": "quartz",
50
- "mdCodeBlockBorder": "crystalFacet",
51
- "mdQuote": "#8b7a99",
52
- "mdQuoteBorder": "crystalFacet",
53
- "mdHr": "crystalFacet",
54
- "mdListBullet": "amethyst",
55
- "toolDiffAdded": "#8eb897",
56
- "toolDiffRemoved": "#d96c75",
57
- "toolDiffContext": "#8b7a99",
58
- "link": "violet",
59
- "syntaxComment": "#7a6b88",
60
- "syntaxKeyword": "amethyst",
61
- "syntaxFunction": "gold",
62
- "syntaxVariable": "lavender",
63
- "syntaxString": "roseQuartz",
64
- "syntaxNumber": "#a8c9b0",
65
- "syntaxType": "violet",
66
- "syntaxOperator": "quartz",
67
- "syntaxPunctuation": "#c0b0cf",
68
- "thinkingOff": "crystalFacet",
69
- "thinkingMinimal": "#6a5a78",
70
- "thinkingLow": "deepPurple",
71
- "thinkingMedium": "violet",
72
- "thinkingHigh": "amethyst",
73
- "thinkingXhigh": "lavender",
74
- "bashMode": "amethyst",
75
- "statusLineBg": "caveDeep",
76
- "statusLineSep": 240,
77
- "statusLineModel": "roseQuartz",
78
- "statusLinePath": "violet",
79
- "statusLineGitClean": "#8eb897",
80
- "statusLineGitDirty": "gold",
81
- "statusLineContext": "lavender",
82
- "statusLineSpend": "deepPurple",
83
- "statusLineStaged": 141,
84
- "statusLineDirty": 179,
85
- "statusLineUntracked": 183,
86
- "statusLineOutput": 213,
87
- "statusLineCost": 213,
88
- "statusLineSubagents": "amethyst",
89
- "pythonMode": "gold"
90
- },
91
- "export": {
92
- "pageBg": "caveDark",
93
- "cardBg": "#1b1520",
94
- "infoBg": "#251d2b"
95
- }
96
- }
@@ -1,93 +0,0 @@
1
- {
2
- "$schema": "https://raw.githubusercontent.com/can1357/gajae-code/main/packages/coding-agent/theme-schema.json",
3
- "name": "anthracite",
4
- "vars": {
5
- "coal": "#121419",
6
- "steel": "#3d4555",
7
- "steelLight": "#4f5566",
8
- "ember": "#e8734a",
9
- "emberDim": "#b5583a",
10
- "ash": "#d4d8e4",
11
- "ashGray": "#9ca3b5",
12
- "soot": "#6b7280",
13
- "sootDark": "#4d5561",
14
- "coalLighter": "#1a1e25",
15
- "success": "#7fb069",
16
- "error": "#d9534f",
17
- "warning": "#e89c4a"
18
- },
19
- "colors": {
20
- "accent": "ember",
21
- "border": "steel",
22
- "borderAccent": "steelLight",
23
- "borderMuted": "sootDark",
24
- "success": "success",
25
- "error": "error",
26
- "warning": "warning",
27
- "muted": "ashGray",
28
- "dim": "soot",
29
- "text": "",
30
- "thinkingText": "soot",
31
- "selectedBg": "#252932",
32
- "userMessageBg": "#1a1511",
33
- "userMessageText": "",
34
- "customMessageBg": "#1f1a24",
35
- "customMessageText": "",
36
- "customMessageLabel": "#c99a6e",
37
- "toolPendingBg": "#171a21",
38
- "toolSuccessBg": "#141a16",
39
- "toolErrorBg": "#221617",
40
- "toolTitle": "",
41
- "toolOutput": "ashGray",
42
- "mdHeading": "ember",
43
- "mdLink": "#6ba3d4",
44
- "mdLinkUrl": "soot",
45
- "mdCode": "#e8b089",
46
- "mdCodeBlock": "#c1cfe0",
47
- "mdCodeBlockBorder": "steel",
48
- "mdQuote": "ashGray",
49
- "mdQuoteBorder": "sootDark",
50
- "mdHr": "steel",
51
- "mdListBullet": "ember",
52
- "toolDiffAdded": "success",
53
- "toolDiffRemoved": "error",
54
- "toolDiffContext": "soot",
55
- "link": "#6ba3d4",
56
- "syntaxComment": "#6b8099",
57
- "syntaxKeyword": "#7ba8d4",
58
- "syntaxFunction": "#e8a069",
59
- "syntaxVariable": "#a9b8cf",
60
- "syntaxString": "#c98963",
61
- "syntaxNumber": "#89b084",
62
- "syntaxType": "#67b8a8",
63
- "syntaxOperator": "#b8bfcc",
64
- "syntaxPunctuation": "#9ca3b5",
65
- "thinkingOff": "sootDark",
66
- "thinkingMinimal": "soot",
67
- "thinkingLow": "#5f7fa3",
68
- "thinkingMedium": "#6ba3d4",
69
- "thinkingHigh": "#c99a6e",
70
- "thinkingXhigh": "#e8b089",
71
- "bashMode": "#6ba3d4",
72
- "statusLineBg": "#0f1114",
73
- "statusLineSep": 240,
74
- "statusLineModel": "#c99a6e",
75
- "statusLinePath": "#6ba3d4",
76
- "statusLineGitClean": "#7fb069",
77
- "statusLineGitDirty": "#e89c4a",
78
- "statusLineContext": "#8791a6",
79
- "statusLineSpend": "#6ba3d4",
80
- "statusLineStaged": 107,
81
- "statusLineDirty": 179,
82
- "statusLineUntracked": 74,
83
- "statusLineOutput": 173,
84
- "statusLineCost": 173,
85
- "statusLineSubagents": "ember",
86
- "pythonMode": "#f0c040"
87
- },
88
- "export": {
89
- "pageBg": "#121419",
90
- "cardBg": "#1a1e25",
91
- "infoBg": "#2a2317"
92
- }
93
- }