@gajae-code/coding-agent 0.7.9 → 0.7.11

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 (123) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +2 -0
  3. package/dist/types/config/keybindings.d.ts +11 -5
  4. package/dist/types/config/settings-schema.d.ts +10 -1
  5. package/dist/types/defaults/gjc-defaults.d.ts +7 -0
  6. package/dist/types/extensibility/extensions/types.d.ts +11 -0
  7. package/dist/types/gjc-runtime/launch-tmux.d.ts +10 -0
  8. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +3 -2
  9. package/dist/types/gjc-runtime/restricted-role-agent-bash.d.ts +1 -0
  10. package/dist/types/gjc-runtime/state-writer.d.ts +1 -0
  11. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -0
  12. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -4
  13. package/dist/types/modes/components/custom-editor.d.ts +2 -1
  14. package/dist/types/modes/components/hook-selector.d.ts +5 -0
  15. package/dist/types/modes/components/skill-message.d.ts +1 -0
  16. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  17. package/dist/types/modes/controllers/input-controller.d.ts +4 -0
  18. package/dist/types/modes/interactive-mode.d.ts +1 -0
  19. package/dist/types/modes/prompt-action-autocomplete.d.ts +2 -0
  20. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +20 -6
  21. package/dist/types/modes/slash-command-visibility.d.ts +1 -0
  22. package/dist/types/modes/types.d.ts +1 -0
  23. package/dist/types/notifications/html-format.d.ts +2 -0
  24. package/dist/types/notifications/recent-activity.d.ts +5 -1
  25. package/dist/types/notifications/telegram-daemon-control.d.ts +8 -0
  26. package/dist/types/notifications/telegram-reference.d.ts +4 -0
  27. package/dist/types/notifications/threaded-render.d.ts +1 -0
  28. package/dist/types/sdk.d.ts +1 -1
  29. package/dist/types/session/agent-session.d.ts +8 -5
  30. package/dist/types/slash-commands/types.d.ts +2 -0
  31. package/dist/types/tools/ask.d.ts +2 -0
  32. package/dist/types/utils/cmux-workspace.d.ts +52 -0
  33. package/dist/types/web/search/index.d.ts +4 -2
  34. package/dist/types/web/search/provider.d.ts +14 -0
  35. package/dist/types/web/search/providers/utils.d.ts +42 -13
  36. package/dist/types/web/search/render.d.ts +1 -0
  37. package/package.json +7 -7
  38. package/src/cli/update-cli.ts +24 -0
  39. package/src/cli/web-search-cli.ts +5 -7
  40. package/src/cli.ts +1 -1
  41. package/src/commands/launch.ts +1 -1
  42. package/src/commands/ralplan.ts +1 -0
  43. package/src/config/keybindings.ts +14 -5
  44. package/src/config/model-profiles.ts +16 -0
  45. package/src/config/settings-schema.ts +11 -1
  46. package/src/coordinator-mcp/server.ts +9 -3
  47. package/src/defaults/gjc/skills/deep-interview/SKILL.md +26 -2
  48. package/src/defaults/gjc/skills/ralplan/SKILL.md +16 -14
  49. package/src/defaults/gjc/skills/ultragoal/SKILL.md +37 -112
  50. package/src/defaults/gjc-defaults.ts +16 -0
  51. package/src/extensibility/extensions/runner.ts +2 -1
  52. package/src/extensibility/extensions/types.ts +11 -0
  53. package/src/gjc-runtime/launch-tmux.ts +209 -26
  54. package/src/gjc-runtime/ralplan-runtime.ts +22 -6
  55. package/src/gjc-runtime/restricted-role-agent-bash.ts +1 -0
  56. package/src/gjc-runtime/state-runtime.ts +8 -5
  57. package/src/gjc-runtime/state-writer.ts +5 -5
  58. package/src/gjc-runtime/ultragoal-guard.ts +139 -19
  59. package/src/gjc-runtime/ultragoal-runtime.ts +16 -124
  60. package/src/gjc-runtime/workflow-manifest.generated.json +0 -8
  61. package/src/gjc-runtime/workflow-manifest.ts +0 -1
  62. package/src/hooks/skill-state.ts +33 -73
  63. package/src/internal-urls/docs-index.generated.ts +6 -4
  64. package/src/main.ts +2 -2
  65. package/src/migrate/executor.ts +23 -6
  66. package/src/modes/components/custom-editor.ts +8 -0
  67. package/src/modes/components/hook-selector.ts +21 -3
  68. package/src/modes/components/model-selector.ts +6 -2
  69. package/src/modes/components/settings-selector.ts +47 -5
  70. package/src/modes/components/skill-message.ts +18 -10
  71. package/src/modes/components/status-line/presets.ts +11 -0
  72. package/src/modes/controllers/command-controller.ts +23 -0
  73. package/src/modes/controllers/extension-ui-controller.ts +18 -2
  74. package/src/modes/controllers/input-controller.ts +41 -9
  75. package/src/modes/interactive-mode.ts +16 -7
  76. package/src/modes/prompt-action-autocomplete.ts +23 -1
  77. package/src/modes/shared/agent-wire/deep-interview-gate.ts +53 -9
  78. package/src/modes/slash-command-visibility.ts +9 -0
  79. package/src/modes/types.ts +1 -0
  80. package/src/modes/utils/hotkeys-markdown.ts +5 -2
  81. package/src/modes/utils/ui-helpers.ts +76 -3
  82. package/src/notifications/html-format.ts +75 -1
  83. package/src/notifications/index.ts +12 -1
  84. package/src/notifications/recent-activity.ts +32 -8
  85. package/src/notifications/telegram-daemon-control.ts +9 -0
  86. package/src/notifications/telegram-daemon.ts +68 -25
  87. package/src/notifications/telegram-reference.ts +36 -14
  88. package/src/notifications/threaded-render.ts +14 -6
  89. package/src/prompts/agents/architect.md +7 -4
  90. package/src/prompts/agents/critic.md +15 -6
  91. package/src/prompts/agents/planner.md +8 -3
  92. package/src/prompts/system/system-prompt.md +5 -1
  93. package/src/prompts/tools/bash.md +1 -1
  94. package/src/sdk.ts +6 -11
  95. package/src/session/agent-session.ts +60 -17
  96. package/src/slash-commands/builtin-registry.ts +30 -10
  97. package/src/slash-commands/types.ts +2 -0
  98. package/src/tools/ask.ts +136 -16
  99. package/src/tools/bash.ts +24 -8
  100. package/src/utils/cmux-workspace.ts +233 -0
  101. package/src/utils/title-generator.ts +2 -0
  102. package/src/web/kagi.ts +1 -1
  103. package/src/web/parallel.ts +2 -2
  104. package/src/web/search/index.ts +137 -52
  105. package/src/web/search/provider.ts +80 -0
  106. package/src/web/search/providers/anthropic.ts +1 -1
  107. package/src/web/search/providers/brave.ts +1 -1
  108. package/src/web/search/providers/codex.ts +1 -1
  109. package/src/web/search/providers/duckduckgo.ts +1 -1
  110. package/src/web/search/providers/exa.ts +1 -1
  111. package/src/web/search/providers/gemini.ts +7 -3
  112. package/src/web/search/providers/jina.ts +1 -1
  113. package/src/web/search/providers/kimi.ts +6 -1
  114. package/src/web/search/providers/openai-compatible.ts +1 -1
  115. package/src/web/search/providers/parallel.ts +1 -1
  116. package/src/web/search/providers/perplexity.ts +2 -2
  117. package/src/web/search/providers/searxng.ts +1 -1
  118. package/src/web/search/providers/synthetic.ts +1 -1
  119. package/src/web/search/providers/tavily.ts +1 -1
  120. package/src/web/search/providers/utils.ts +67 -17
  121. package/src/web/search/providers/xai.ts +1 -1
  122. package/src/web/search/providers/zai.ts +1 -1
  123. package/src/web/search/render.ts +3 -0
@@ -29,7 +29,11 @@ const ANTIGRAVITY_ENDPOINT_FALLBACKS = [ANTIGRAVITY_DAILY_ENDPOINT, ANTIGRAVITY_
29
29
  const DEFAULT_MODEL = "gemini-2.5-flash";
30
30
  const MAX_RETRIES = 3;
31
31
  const BASE_DELAY_MS = 1000;
32
- const RATE_LIMIT_BUDGET_MS = 5 * 60 * 1000;
32
+ // Retry-delay ceiling for 429/5xx. The web_search chain always terminates in
33
+ // the keyless DuckDuckGo fallback, so a rate-limited Gemini should fail
34
+ // through in seconds instead of parking the chain behind long server-provided
35
+ // retry delays (previously a 5-minute budget).
36
+ const RATE_LIMIT_BUDGET_MS = 30 * 1000;
33
37
 
34
38
  const GEMINI_PROVIDERS = ["google-gemini-cli", "google-antigravity"] as const;
35
39
  type GeminiProviderId = (typeof GEMINI_PROVIDERS)[number];
@@ -230,7 +234,7 @@ async function callGeminiSearch(
230
234
  ...headers,
231
235
  },
232
236
  body: JSON.stringify(requestBody),
233
- signal: withHardTimeout(signal),
237
+ signal: withHardTimeout(signal, "llm"),
234
238
  });
235
239
  const urlFor = (attempt: number) =>
236
240
  `${endpoints[Math.min(attempt, endpoints.length - 1)]}/v1internal:streamGenerateContent?alt=sse`;
@@ -466,7 +470,7 @@ async function searchGeminiViaGenerativeLanguage(params: SearchParams): Promise<
466
470
  method: "POST",
467
471
  headers: { ...(ctx.headers ?? {}), "x-goog-api-key": apiKey, "Content-Type": "application/json" },
468
472
  body: JSON.stringify(body),
469
- signal: withHardTimeout(params.signal),
473
+ signal: withHardTimeout(params.signal, "llm"),
470
474
  });
471
475
  const text = await response.text();
472
476
  if (!response.ok) {
@@ -41,7 +41,7 @@ async function callJinaSearch(apiKey: string, query: string, signal?: AbortSigna
41
41
  Accept: "application/json",
42
42
  Authorization: `Bearer ${apiKey}`,
43
43
  },
44
- signal: withHardTimeout(signal),
44
+ signal: withHardTimeout(signal, "api"),
45
45
  });
46
46
 
47
47
  if (!response.ok) {
@@ -19,6 +19,11 @@ const KIMI_SEARCH_URL = "https://api.kimi.com/coding/v1/search";
19
19
  const DEFAULT_NUM_RESULTS = 10;
20
20
  const MAX_NUM_RESULTS = 20;
21
21
  const DEFAULT_TIMEOUT_SECONDS = 30;
22
+ // Local hard ceiling aligned with the upstream `timeout_seconds` request budget
23
+ // (plus transport headroom) instead of the generic 15s "api" class: Kimi
24
+ // legitimately runs search + page crawling up to its advertised 30s window,
25
+ // and aborting earlier would discard valid in-budget responses.
26
+ const KIMI_HARD_TIMEOUT_MS = (DEFAULT_TIMEOUT_SECONDS + 5) * 1000;
22
27
 
23
28
  export interface KimiSearchParams {
24
29
  query: string;
@@ -87,7 +92,7 @@ async function callKimiSearch(
87
92
  enable_page_crawling: params.includeContent,
88
93
  timeout_seconds: DEFAULT_TIMEOUT_SECONDS,
89
94
  }),
90
- signal: withHardTimeout(params.signal),
95
+ signal: withHardTimeout(params.signal, KIMI_HARD_TIMEOUT_MS),
91
96
  });
92
97
 
93
98
  if (!response.ok) {
@@ -134,7 +134,7 @@ export class OpenAICompatibleSearchProvider extends SearchProvider {
134
134
  method: "POST",
135
135
  headers,
136
136
  body: JSON.stringify(payload),
137
- signal: withHardTimeout(params.signal),
137
+ signal: withHardTimeout(params.signal, "llm"),
138
138
  });
139
139
 
140
140
  // Web search is a Responses-API capability: many OpenAI-compatible
@@ -139,7 +139,7 @@ async function searchWithAuthStorage(
139
139
  max_chars_per_result: 10_000,
140
140
  },
141
141
  }),
142
- signal: withHardTimeout(params.signal),
142
+ signal: withHardTimeout(params.signal, "api"),
143
143
  });
144
144
  if (!response.ok) {
145
145
  throw parseParallelErrorResponse(response.status, await response.text());
@@ -251,7 +251,7 @@ async function callPerplexityApi(
251
251
  "Content-Type": "application/json",
252
252
  },
253
253
  body: JSON.stringify(request),
254
- signal: withHardTimeout(signal),
254
+ signal: withHardTimeout(signal, "llm"),
255
255
  });
256
256
 
257
257
  if (!response.ok) {
@@ -370,7 +370,7 @@ async function callPerplexityOAuth(
370
370
  skip_search_enabled: true,
371
371
  },
372
372
  }),
373
- signal: withHardTimeout(params.signal),
373
+ signal: withHardTimeout(params.signal, "llm"),
374
374
  });
375
375
 
376
376
  if (!response.ok) {
@@ -214,7 +214,7 @@ async function callSearXNGSearch(
214
214
 
215
215
  const response = await fetch(url, {
216
216
  headers,
217
- signal: withHardTimeout(params.signal),
217
+ signal: withHardTimeout(params.signal, "api"),
218
218
  });
219
219
 
220
220
  if (!response.ok) {
@@ -47,7 +47,7 @@ async function callSyntheticSearch(
47
47
  Authorization: `Bearer ${apiKey}`,
48
48
  },
49
49
  body: JSON.stringify({ query }),
50
- signal: withHardTimeout(signal),
50
+ signal: withHardTimeout(signal, "api"),
51
51
  });
52
52
 
53
53
  if (!response.ok) {
@@ -96,7 +96,7 @@ async function callTavilySearch(apiKey: string, params: TavilySearchParams): Pro
96
96
  Authorization: `Bearer ${apiKey}`,
97
97
  },
98
98
  body: JSON.stringify(buildRequestBody(params)),
99
- signal: withHardTimeout(params.signal),
99
+ signal: withHardTimeout(params.signal, "api"),
100
100
  });
101
101
 
102
102
  if (!response.ok) {
@@ -44,38 +44,81 @@ export function findCredential(
44
44
  }
45
45
 
46
46
  /**
47
- * Default hard ceiling for a single web-search round-trip. 300s tolerates
48
- * legitimate slow LLM-mediated responses (anthropic web_search_20250305,
49
- * perplexity, gemini, OpenAI code backend) while still guaranteeing the session unfreezes
50
- * if Bun's `AbortSignal` fails to propagate on Windows.
51
- *
52
- * Pure search APIs (brave, exa, jina, tavily, searxng, synthetic, zai)
53
- * settle far faster in practice; reusing the same ceiling keeps the wiring
54
- * uniform without compromising correctness.
47
+ * Legacy hard ceiling for a single web-search round-trip, retained as the
48
+ * fallback for call sites that do not declare a timeout class. 300s tolerates
49
+ * pathological cases while still guaranteeing the session unfreezes if Bun's
50
+ * `AbortSignal` fails to propagate on Windows.
55
51
  */
56
52
  export const SEARCH_HARD_TIMEOUT_MS = 300_000;
57
53
 
54
+ /**
55
+ * Hard ceiling for pure search APIs (brave, exa, jina, tavily, kagi,
56
+ * parallel, searxng, synthetic, zai, duckduckgo). These settle in ~1-3s in
57
+ * practice; a hung TCP/TLS connection should fall through to the next
58
+ * provider in seconds, not minutes. Kimi is the exception: it declares an
59
+ * explicit ceiling aligned with its upstream 30s request budget.
60
+ */
61
+ export const SEARCH_API_TIMEOUT_MS = 15_000;
62
+
63
+ /**
64
+ * Hard ceiling for LLM-mediated search providers (anthropic, codex, gemini,
65
+ * perplexity, xai, openai-compatible). These legitimately take tens of
66
+ * seconds because a model performs the search and synthesizes an answer.
67
+ */
68
+ export const SEARCH_LLM_TIMEOUT_MS = 120_000;
69
+
70
+ /** Timeout class a provider declares for its outbound round-trips. */
71
+ export type SearchTimeoutClass = "api" | "llm";
72
+
73
+ const TIMEOUT_CLASS_MS: Record<SearchTimeoutClass, number> = {
74
+ api: SEARCH_API_TIMEOUT_MS,
75
+ llm: SEARCH_LLM_TIMEOUT_MS,
76
+ };
77
+
58
78
  /**
59
79
  * Runtime-configurable hard timeout, seeded from the `web_search.timeout`
60
- * setting via {@link setSearchHardTimeoutMs}. Falls back to
61
- * {@link SEARCH_HARD_TIMEOUT_MS} when unset or invalid.
80
+ * setting via {@link setSearchHardTimeoutMs}. When set, it overrides every
81
+ * class default so users keep a single knob. Unset means class defaults apply.
62
82
  */
63
- let configuredHardTimeoutMs = SEARCH_HARD_TIMEOUT_MS;
83
+ let configuredHardTimeoutMs: number | undefined;
64
84
 
65
85
  /**
66
86
  * Override the hard timeout applied to every web-search round-trip.
67
87
  *
68
88
  * @param ms - Hard timeout in milliseconds. Non-finite or non-positive
69
- * values reset the timeout to {@link SEARCH_HARD_TIMEOUT_MS}.
89
+ * values clear the override so per-class defaults apply.
70
90
  */
71
91
  export function setSearchHardTimeoutMs(ms: number | undefined): void {
72
- configuredHardTimeoutMs = typeof ms === "number" && Number.isFinite(ms) && ms > 0 ? ms : SEARCH_HARD_TIMEOUT_MS;
92
+ configuredHardTimeoutMs = typeof ms === "number" && Number.isFinite(ms) && ms > 0 ? ms : undefined;
93
+ }
94
+
95
+ /** Structural settings source for {@link applyConfiguredSearchTimeout}. */
96
+ export interface SearchTimeoutSettingSource {
97
+ get(path: "web_search.timeout"): unknown;
98
+ has(path: "web_search.timeout"): boolean;
99
+ }
100
+
101
+ /**
102
+ * Install the `web_search.timeout` override from settings — but only when the
103
+ * user explicitly configured it. The settings schema ships a default value
104
+ * (300), and consuming that default via `get()` alone would reinstall a
105
+ * uniform 300s ceiling on every session, silently disabling the per-class
106
+ * timeout defaults. `has()` distinguishes loaded/overridden settings from
107
+ * schema defaults; unset clears any previous override so class defaults apply.
108
+ */
109
+ export function applyConfiguredSearchTimeout(settings: SearchTimeoutSettingSource): void {
110
+ if (!settings.has("web_search.timeout")) {
111
+ setSearchHardTimeoutMs(undefined);
112
+ return;
113
+ }
114
+ const value = settings.get("web_search.timeout");
115
+ setSearchHardTimeoutMs(typeof value === "number" && Number.isFinite(value) && value > 0 ? value * 1000 : undefined);
73
116
  }
74
117
 
75
118
  /**
76
119
  * Compose a caller-supplied {@link AbortSignal} with a hard timeout so an
77
- * outbound `fetch()` is guaranteed to settle within `ms` even when the
78
- * runtime fails to propagate cancellation to the underlying transport.
120
+ * outbound `fetch()` is guaranteed to settle even when the runtime fails to
121
+ * propagate cancellation to the underlying transport.
79
122
  *
80
123
  * Bun's WinHTTP backend on Windows is known to ignore `AbortSignal` once a
81
124
  * TCP/TLS connection stalls (oven-sh/bun#15275, oven-sh/bun#18536); without
@@ -83,9 +126,16 @@ export function setSearchHardTimeoutMs(ms: number | undefined): void {
83
126
  * because the user's Esc is never delivered to the native layer.
84
127
  *
85
128
  * @param signal - Caller cancellation signal, if any.
86
- * @param ms - Hard timeout in milliseconds. Defaults to the configured value.
129
+ * @param msOrClass - Explicit timeout in milliseconds, or a
130
+ * {@link SearchTimeoutClass} resolved to its class default. Omitted falls
131
+ * back to the legacy {@link SEARCH_HARD_TIMEOUT_MS} ceiling. A user-set
132
+ * `web_search.timeout` overrides class defaults (but not explicit ms).
87
133
  */
88
- export function withHardTimeout(signal: AbortSignal | undefined, ms: number = configuredHardTimeoutMs): AbortSignal {
134
+ export function withHardTimeout(signal: AbortSignal | undefined, msOrClass?: number | SearchTimeoutClass): AbortSignal {
135
+ const ms =
136
+ typeof msOrClass === "number"
137
+ ? msOrClass
138
+ : (configuredHardTimeoutMs ?? (msOrClass ? TIMEOUT_CLASS_MS[msOrClass] : SEARCH_HARD_TIMEOUT_MS));
89
139
  const timeout = AbortSignal.timeout(ms);
90
140
  return signal ? AbortSignal.any([signal, timeout]) : timeout;
91
141
  }
@@ -441,7 +441,7 @@ export async function searchXai(params: XaiSearchParams): Promise<SearchResponse
441
441
  noInlineCitations: params.no_inline_citations,
442
442
  }),
443
443
  ),
444
- signal: withHardTimeout(params.signal),
444
+ signal: withHardTimeout(params.signal, "llm"),
445
445
  });
446
446
 
447
447
  const text = await response.text();
@@ -79,7 +79,7 @@ async function callZaiTool(apiKey: string, args: Record<string, unknown>, signal
79
79
  arguments: args,
80
80
  },
81
81
  }),
82
- signal: withHardTimeout(signal),
82
+ signal: withHardTimeout(signal, "api"),
83
83
  });
84
84
 
85
85
  if (!response.ok) {
@@ -66,6 +66,7 @@ function renderFallbackText(contentText: string, expanded: boolean, theme: Theme
66
66
  export interface SearchRenderDetails {
67
67
  response: SearchResponse;
68
68
  error?: string;
69
+ warning?: string;
69
70
  }
70
71
 
71
72
  /** Render web search result with tree-based layout */
@@ -130,6 +131,8 @@ export function renderSearchResult(
130
131
 
131
132
  const metaLines: string[] = [];
132
133
  metaLines.push(`${theme.fg("muted", "Provider:")} ${theme.fg("text", providerLabel)}`);
134
+ if (details.warning)
135
+ metaLines.push(`${theme.fg("muted", "Fallback:")} ${theme.fg("warning", truncateToWidth(details.warning, 110))}`);
133
136
  if (response.authMode)
134
137
  metaLines.push(
135
138
  `${theme.fg("muted", "Auth:")} ${theme.fg("text", response.authMode === "oauth" ? "OAuth" : response.authMode === "api_key" ? "API key" : response.authMode)}`,