@sayknow-cli/coding-agent 0.3.5 → 0.3.7
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/README.md +76 -0
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/cli/local-provider-smoke.d.ts +59 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/telegram-cli.d.ts +13 -0
- package/dist/types/cli/update-cli.d.ts +24 -1
- package/dist/types/commands/local-provider.d.ts +34 -0
- package/dist/types/commands/telegram.d.ts +22 -0
- package/dist/types/config/keybindings.d.ts +12 -6
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +284 -4
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/config/telegram-autostart.d.ts +14 -0
- package/dist/types/config/telegram-env-bridge.d.ts +18 -0
- package/dist/types/coordinator-mcp/server.d.ts +8 -0
- package/dist/types/defaults/skc-defaults.d.ts +7 -0
- package/dist/types/discovery/cursor.d.ts +4 -1
- package/dist/types/discovery/index.d.ts +0 -1
- package/dist/types/discovery/windsurf.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/i18n/messages/en.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -1
- package/dist/types/modes/components/custom-model-preset-wizard.d.ts +1 -1
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/settings-selector.d.ts +5 -1
- package/dist/types/modes/components/skill-message.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -7
- package/dist/types/modes/components/status-line.d.ts +15 -3
- package/dist/types/modes/components/two-column-body.d.ts +9 -0
- package/dist/types/modes/components/welcome.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/input-controller.d.ts +6 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +10 -1
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +3 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +41 -7
- package/dist/types/modes/slash-command-visibility.d.ts +1 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/tmux-scroll.d.ts +18 -0
- package/dist/types/modes/types.d.ts +5 -1
- package/dist/types/modes/utils/terminal-bell.d.ts +3 -0
- package/dist/types/notifications/daemon-paths.d.ts +9 -0
- package/dist/types/notifications/html-format.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +5 -1
- package/dist/types/notifications/telegram-daemon-cli.d.ts +21 -4
- package/dist/types/notifications/telegram-daemon-control.d.ts +8 -0
- package/dist/types/notifications/telegram-daemon.d.ts +4 -10
- package/dist/types/notifications/telegram-reference.d.ts +4 -0
- package/dist/types/notifications/threaded-render.d.ts +1 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +9 -0
- package/dist/types/runtime-mcp/config.d.ts +2 -0
- package/dist/types/runtime-mcp/loader.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/skc-runtime/launch-tmux.d.ts +2 -2
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +3 -2
- package/dist/types/skc-runtime/restricted-role-agent-bash.d.ts +1 -0
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +5 -0
- package/dist/types/skc-runtime/state-writer.d.ts +1 -0
- package/dist/types/skc-runtime/team-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/tmux-common.d.ts +12 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +7 -1
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +0 -4
- package/dist/types/slash-commands/types.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +7 -0
- package/dist/types/tools/skill.d.ts +1 -1
- package/dist/types/utils/cmux-workspace.d.ts +52 -0
- package/dist/types/web/search/index.d.ts +4 -2
- package/dist/types/web/search/provider.d.ts +14 -0
- package/dist/types/web/search/providers/utils.d.ts +42 -13
- package/dist/types/web/search/render.d.ts +1 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +19 -0
- package/package.json +8 -8
- package/scripts/build-binary.ts +3 -0
- package/scripts/verify-insane-vendor.ts +0 -5
- package/src/capability/mcp.ts +2 -0
- package/src/cli/local-provider-smoke.ts +632 -0
- package/src/cli/notify-cli.ts +4 -2
- package/src/cli/telegram-cli.ts +156 -0
- package/src/cli/update-cli.ts +179 -13
- package/src/cli/web-search-cli.ts +5 -7
- package/src/cli.ts +20 -1
- package/src/commands/launch.ts +47 -2
- package/src/commands/local-provider.ts +62 -0
- package/src/commands/notify.ts +1 -1
- package/src/commands/ralplan.ts +1 -0
- package/src/commands/telegram.ts +47 -0
- package/src/config/keybindings.ts +15 -6
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-registry.ts +55 -1
- package/src/config/models-config-schema.ts +9 -0
- package/src/config/settings-schema.ts +256 -4
- package/src/config/settings.ts +11 -0
- package/src/config/telegram-autostart.ts +107 -0
- package/src/config/telegram-env-bridge.ts +92 -0
- package/src/coordinator-mcp/server.ts +276 -34
- package/src/defaults/skc/skills/ralplan/SKILL.md +29 -20
- package/src/defaults/skc/skills/ultragoal/SKILL.md +37 -112
- package/src/defaults/skc-defaults.ts +16 -0
- package/src/discovery/builtin.ts +12 -0
- package/src/discovery/cursor.ts +5 -84
- package/src/discovery/gemini.ts +3 -89
- package/src/discovery/index.ts +4 -1
- package/src/discovery/mcp-json.ts +11 -0
- package/src/discovery/opencode.ts +5 -122
- package/src/discovery/windsurf.ts +4 -82
- package/src/edit/read-file.ts +9 -1
- package/src/extensibility/extensions/runner.ts +2 -1
- package/src/extensibility/extensions/types.ts +11 -0
- package/src/extensibility/skills.ts +30 -2
- package/src/hooks/skill-state.ts +33 -73
- package/src/i18n/messages/de.ts +1 -0
- package/src/i18n/messages/en.ts +1 -0
- package/src/i18n/messages/es.ts +1 -0
- package/src/i18n/messages/fr.ts +1 -0
- package/src/i18n/messages/ja.settings.ts +28 -0
- package/src/i18n/messages/ja.ts +1 -0
- package/src/i18n/messages/ko.settings.ts +28 -0
- package/src/i18n/messages/ko.ts +1 -0
- package/src/i18n/messages/zh.settings.ts +28 -0
- package/src/i18n/messages/zh.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +12 -7
- package/src/main.ts +5 -2
- package/src/migrate/executor.ts +23 -6
- package/src/modes/acp/acp-agent.ts +7 -6
- package/src/modes/components/agent-dashboard.ts +3 -35
- package/src/modes/components/custom-editor.ts +26 -0
- package/src/modes/components/custom-model-preset-wizard.ts +30 -189
- package/src/modes/components/extensions/extension-dashboard.ts +2 -48
- package/src/modes/components/hook-selector.ts +21 -3
- package/src/modes/components/model-selector.ts +246 -11
- package/src/modes/components/session-observer-overlay.ts +3 -3
- package/src/modes/components/session-selector.ts +19 -9
- package/src/modes/components/settings-selector.ts +551 -11
- package/src/modes/components/skill-message.ts +18 -10
- package/src/modes/components/status-line/presets.ts +11 -0
- package/src/modes/components/status-line/segments.ts +16 -19
- package/src/modes/components/status-line/types.ts +6 -2
- package/src/modes/components/status-line.ts +281 -117
- package/src/modes/components/tree-selector.ts +28 -8
- package/src/modes/components/two-column-body.ts +36 -0
- package/src/modes/components/welcome.ts +165 -30
- package/src/modes/controllers/command-controller.ts +26 -2
- package/src/modes/controllers/event-controller.ts +102 -4
- package/src/modes/controllers/extension-ui-controller.ts +21 -2
- package/src/modes/controllers/input-controller.ts +214 -47
- package/src/modes/controllers/selector-controller.ts +35 -33
- package/src/modes/interactive-mode.ts +36 -30
- package/src/modes/prompt-action-autocomplete.ts +48 -8
- package/src/modes/rpc/rpc-types.ts +1 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +72 -15
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +20 -0
- package/src/modes/slash-command-visibility.ts +9 -0
- package/src/modes/theme/theme.ts +5 -1
- package/src/modes/tmux-scroll.ts +92 -0
- package/src/modes/types.ts +8 -1
- package/src/modes/utils/hotkeys-markdown.ts +5 -2
- package/src/modes/utils/terminal-bell.ts +48 -0
- package/src/modes/utils/ui-helpers.ts +76 -3
- package/src/notifications/daemon-paths.ts +22 -0
- package/src/notifications/html-format.ts +75 -1
- package/src/notifications/index.ts +32 -2
- package/src/notifications/lifecycle-control-runtime.ts +12 -3
- package/src/notifications/recent-activity.ts +32 -8
- package/src/notifications/telegram-daemon-cli.ts +125 -13
- package/src/notifications/telegram-daemon-control.ts +9 -0
- package/src/notifications/telegram-daemon.ts +88 -46
- package/src/notifications/telegram-reference.ts +36 -14
- package/src/notifications/threaded-render.ts +14 -6
- package/src/prompts/agents/architect.md +7 -4
- package/src/prompts/agents/critic.md +15 -6
- package/src/prompts/agents/planner.md +8 -3
- package/src/prompts/system/system-prompt.md +20 -2
- package/src/prompts/tools/bash.md +1 -1
- package/src/runtime-mcp/config-writer.ts +28 -0
- package/src/runtime-mcp/config.ts +7 -0
- package/src/runtime-mcp/loader.ts +3 -0
- package/src/runtime-mcp/manager.ts +3 -0
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk.ts +10 -11
- package/src/session/agent-session.ts +103 -13
- package/src/session/session-manager.ts +58 -18
- package/src/skc-runtime/launch-tmux.ts +34 -8
- package/src/skc-runtime/launch-worktree.ts +16 -2
- package/src/skc-runtime/ralplan-runtime.ts +22 -6
- package/src/skc-runtime/restricted-role-agent-bash.ts +1 -0
- package/src/skc-runtime/session-state-sidecar.ts +167 -24
- package/src/skc-runtime/state-runtime.ts +8 -5
- package/src/skc-runtime/state-writer.ts +5 -5
- package/src/skc-runtime/team-runtime.ts +21 -6
- package/src/skc-runtime/tmux-common.ts +16 -0
- package/src/skc-runtime/tmux-sessions.ts +13 -9
- package/src/skc-runtime/ultragoal-guard.ts +150 -24
- package/src/skc-runtime/ultragoal-runtime.ts +16 -124
- package/src/skc-runtime/workflow-manifest.generated.json +0 -8
- package/src/skc-runtime/workflow-manifest.ts +0 -1
- package/src/slash-commands/builtin-registry.ts +136 -23
- package/src/slash-commands/types.ts +2 -0
- package/src/system-prompt.ts +8 -0
- package/src/task/executor.ts +5 -0
- package/src/tools/ast-grep.ts +10 -7
- package/src/tools/bash.ts +39 -14
- package/src/tools/index.ts +8 -1
- package/src/tools/skill.ts +1 -1
- package/src/utils/cmux-workspace.ts +233 -0
- package/src/utils/title-generator.ts +2 -0
- package/src/web/kagi.ts +1 -1
- package/src/web/parallel.ts +2 -2
- package/src/web/search/index.ts +137 -52
- package/src/web/search/provider.ts +80 -0
- package/src/web/search/providers/anthropic.ts +1 -1
- package/src/web/search/providers/brave.ts +1 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/providers/duckduckgo.ts +1 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/gemini.ts +7 -3
- package/src/web/search/providers/jina.ts +1 -1
- package/src/web/search/providers/kimi.ts +6 -1
- package/src/web/search/providers/openai-compatible.ts +1 -1
- package/src/web/search/providers/parallel.ts +1 -1
- package/src/web/search/providers/perplexity.ts +2 -2
- package/src/web/search/providers/searxng.ts +1 -1
- package/src/web/search/providers/synthetic.ts +1 -1
- package/src/web/search/providers/tavily.ts +1 -1
- package/src/web/search/providers/utils.ts +67 -17
- package/src/web/search/providers/xai.ts +1 -1
- package/src/web/search/providers/zai.ts +1 -1
- package/src/web/search/render.ts +3 -0
- package/src/workflow/workflow-intent-diff.ts +199 -0
- package/vendor/insane-search/MANIFEST.json +1 -3
- package/vendor/insane-search/engine/tests/test_u1.py +0 -28
- package/vendor/insane-search/engine/transport.py +2 -3
- package/vendor/insane-search/engine/validators.py +0 -14
- package/src/defaults/skc/rules/ponytail.md +0 -68
|
@@ -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
|
-
|
|
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) {
|
|
@@ -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
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
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}.
|
|
61
|
-
*
|
|
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
|
|
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
|
|
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 :
|
|
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
|
|
78
|
-
*
|
|
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
|
|
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,
|
|
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();
|
package/src/web/search/render.ts
CHANGED
|
@@ -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)}`,
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
export const WORKFLOW_INTENT_DIFF_CUSTOM_TYPE = "workflow-intent-diff";
|
|
2
|
+
|
|
3
|
+
export type WorkflowIntentRoute = "direct" | "deep-interview" | "ralplan" | "ultragoal" | "team";
|
|
4
|
+
export type DirectTrackingMode = "custom-entry-only" | "not-direct";
|
|
5
|
+
export type RootCausePhaseStatus = "active" | "inactive";
|
|
6
|
+
|
|
7
|
+
export interface WorkflowIntentDiff {
|
|
8
|
+
readonly version: 1;
|
|
9
|
+
readonly route: WorkflowIntentRoute;
|
|
10
|
+
readonly reason: string;
|
|
11
|
+
readonly directTracking: DirectTrackingMode;
|
|
12
|
+
readonly recommendedSkill?: Exclude<WorkflowIntentRoute, "direct">;
|
|
13
|
+
readonly recommendedInvocation?: string;
|
|
14
|
+
readonly triggers: readonly string[];
|
|
15
|
+
readonly rootCausePhase: {
|
|
16
|
+
readonly status: RootCausePhaseStatus;
|
|
17
|
+
readonly triggers: readonly string[];
|
|
18
|
+
};
|
|
19
|
+
readonly promptPreview: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface RouteMatch {
|
|
23
|
+
readonly route: WorkflowIntentRoute;
|
|
24
|
+
readonly reason: string;
|
|
25
|
+
readonly recommendedInvocation?: string;
|
|
26
|
+
readonly triggers: readonly string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const PROMPT_PREVIEW_LIMIT = 240;
|
|
30
|
+
|
|
31
|
+
const DURABLE_TRACKING_PATTERNS = [
|
|
32
|
+
/\bultragoal\b/i,
|
|
33
|
+
/\bdurable (?:goal|tracking|ledger|plan)\b/i,
|
|
34
|
+
/\b(?:goal|tracking|plan) ledger\b/i,
|
|
35
|
+
/\bcheckpoint(?:ed|ing)? (?:goal|plan|workflow|release|work)\b/i,
|
|
36
|
+
/\bcheckpoint (?:this|the) (?:goal|plan|workflow|release|work)\b/i,
|
|
37
|
+
] as const;
|
|
38
|
+
|
|
39
|
+
const AMBIGUOUS_REQUIREMENT_PATTERNS = [
|
|
40
|
+
/\bambiguous (?:requirement|requirements|request|requests|scope)\b/i,
|
|
41
|
+
/\bdeep[- ]interview\b/i,
|
|
42
|
+
/\binterview me\b/i,
|
|
43
|
+
/\bdon't assume\b/i,
|
|
44
|
+
/\bnot sure\b/i,
|
|
45
|
+
/\bunclear\b/i,
|
|
46
|
+
/\bvague\b/i,
|
|
47
|
+
] as const;
|
|
48
|
+
|
|
49
|
+
const ARCHITECTURE_SEQUENCE_PATTERNS = [
|
|
50
|
+
/\barchitecture\b/i,
|
|
51
|
+
/\barchitectural\b/i,
|
|
52
|
+
/\bsequence\b/i,
|
|
53
|
+
/\bmigrate\b/i,
|
|
54
|
+
/\bmigration\b/i,
|
|
55
|
+
/\bauth(?:entication|orization)? (?:migration|sequence|rollout|release|refactor|rewrite|transition)\b/i,
|
|
56
|
+
/\bsecurity\b/i,
|
|
57
|
+
/\bcompliance\b/i,
|
|
58
|
+
/\bPII\b/i,
|
|
59
|
+
/\bproduction (?:release|rollout|deployment|migration|incident|data|change|cutover)\b/i,
|
|
60
|
+
/\bdeploy(?:ing)? to production\b/i,
|
|
61
|
+
/\bbreaking change\b/i,
|
|
62
|
+
/\bdata loss\b/i,
|
|
63
|
+
/\bdestructive\b/i,
|
|
64
|
+
] as const;
|
|
65
|
+
|
|
66
|
+
const TEAM_PATTERNS = [
|
|
67
|
+
/\buse (?:a )?team\b/i,
|
|
68
|
+
/\bcoordinated (?:persistent )?workers\b/i,
|
|
69
|
+
/\bpersistent workers\b/i,
|
|
70
|
+
/\bworker coordination\b/i,
|
|
71
|
+
] as const;
|
|
72
|
+
|
|
73
|
+
const ROOT_CAUSE_TRIGGERS = [
|
|
74
|
+
{ name: "contradiction", pattern: /\bcontradict(?:ion|s|ory)?\b/i },
|
|
75
|
+
{ name: "regression", pattern: /\bregression\b/i },
|
|
76
|
+
{
|
|
77
|
+
name: "high-risk transition",
|
|
78
|
+
pattern:
|
|
79
|
+
/\b(?:migrate|migration|auth(?:entication|orization)? (?:migration|sequence|rollout|release|refactor|rewrite|transition)|security|breaking change|data loss|destructive|production (?:release|rollout|deployment|migration|incident|data|change|cutover)|deploy(?:ing)? to production|compliance|PII)\b/i,
|
|
80
|
+
},
|
|
81
|
+
] as const;
|
|
82
|
+
|
|
83
|
+
function matchesAny(text: string, patterns: readonly RegExp[]): boolean {
|
|
84
|
+
return patterns.some(pattern => pattern.test(text));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function withGlobal(pattern: RegExp): RegExp {
|
|
88
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
|
|
89
|
+
return new RegExp(pattern.source, flags);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isNegatedAt(text: string, index: number): boolean {
|
|
93
|
+
const prefix = text.slice(Math.max(0, index - 48), index).toLowerCase();
|
|
94
|
+
const boundary = Math.max(
|
|
95
|
+
prefix.lastIndexOf("."),
|
|
96
|
+
prefix.lastIndexOf(","),
|
|
97
|
+
prefix.lastIndexOf(";"),
|
|
98
|
+
prefix.lastIndexOf(":"),
|
|
99
|
+
prefix.lastIndexOf("?"),
|
|
100
|
+
prefix.lastIndexOf("!"),
|
|
101
|
+
prefix.lastIndexOf("\n"),
|
|
102
|
+
prefix.lastIndexOf(" but "),
|
|
103
|
+
prefix.lastIndexOf(" however "),
|
|
104
|
+
prefix.lastIndexOf(" yet "),
|
|
105
|
+
prefix.lastIndexOf(" then "),
|
|
106
|
+
);
|
|
107
|
+
const localPrefix = boundary >= 0 ? prefix.slice(boundary + 1) : prefix;
|
|
108
|
+
return /(?:^|[\s([{])(?:no|not|without|excluding|exclude|absent|absence of|avoid)\b[^.,!?;:\n]{0,48}$/.test(
|
|
109
|
+
localPrefix,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function matchesAnyNonNegated(text: string, patterns: readonly RegExp[]): boolean {
|
|
114
|
+
return patterns.some(pattern => {
|
|
115
|
+
for (const match of text.matchAll(withGlobal(pattern))) {
|
|
116
|
+
if (!isNegatedAt(text, match.index ?? 0)) return true;
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function collectRootCauseTriggers(text: string): readonly string[] {
|
|
123
|
+
return ROOT_CAUSE_TRIGGERS.filter(trigger => matchesAnyNonNegated(text, [trigger.pattern])).map(
|
|
124
|
+
trigger => trigger.name,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function buildPromptPreview(text: string): string {
|
|
129
|
+
const compact = text.replace(/\s+/g, " ").trim();
|
|
130
|
+
return compact.length > PROMPT_PREVIEW_LIMIT ? `${compact.slice(0, PROMPT_PREVIEW_LIMIT - 3)}...` : compact;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function classifyRoute(text: string): RouteMatch {
|
|
134
|
+
if (matchesAny(text, AMBIGUOUS_REQUIREMENT_PATTERNS)) {
|
|
135
|
+
return {
|
|
136
|
+
route: "deep-interview",
|
|
137
|
+
reason: "ambiguous requirements need clarification",
|
|
138
|
+
recommendedInvocation: "/skill:deep-interview",
|
|
139
|
+
triggers: ["ambiguous requirements"],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (matchesAny(text, DURABLE_TRACKING_PATTERNS)) {
|
|
144
|
+
return {
|
|
145
|
+
route: "ultragoal",
|
|
146
|
+
reason: "durable tracking requested",
|
|
147
|
+
recommendedInvocation: "/skill:ultragoal",
|
|
148
|
+
triggers: ["durable tracking"],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (matchesAnyNonNegated(text, ARCHITECTURE_SEQUENCE_PATTERNS)) {
|
|
153
|
+
return {
|
|
154
|
+
route: "ralplan",
|
|
155
|
+
reason: "architecture or sequence risk requires deliberate planning",
|
|
156
|
+
recommendedInvocation: "/skill:ralplan --deliberate",
|
|
157
|
+
triggers: ["architecture/sequence risk"],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (matchesAny(text, TEAM_PATTERNS)) {
|
|
162
|
+
return {
|
|
163
|
+
route: "team",
|
|
164
|
+
reason: "coordinated persistent workers requested",
|
|
165
|
+
recommendedInvocation: "/skill:team",
|
|
166
|
+
triggers: ["coordinated workers"],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
route: "direct",
|
|
172
|
+
reason: "clear low-risk prompt stays on direct implementation path",
|
|
173
|
+
triggers: ["low-risk direct"],
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function buildWorkflowIntentDiff(text: string): WorkflowIntentDiff | null {
|
|
178
|
+
const promptPreview = buildPromptPreview(text);
|
|
179
|
+
if (!promptPreview) return null;
|
|
180
|
+
|
|
181
|
+
const route = classifyRoute(text);
|
|
182
|
+
const rootCauseTriggers = collectRootCauseTriggers(text);
|
|
183
|
+
const rootCauseActive = rootCauseTriggers.length > 0;
|
|
184
|
+
const direct = route.route === "direct";
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
version: 1,
|
|
188
|
+
route: route.route,
|
|
189
|
+
reason: route.reason,
|
|
190
|
+
directTracking: direct ? "custom-entry-only" : "not-direct",
|
|
191
|
+
...(direct ? {} : { recommendedSkill: route.route, recommendedInvocation: route.recommendedInvocation }),
|
|
192
|
+
triggers: route.triggers,
|
|
193
|
+
rootCausePhase: {
|
|
194
|
+
status: rootCauseActive ? "active" : "inactive",
|
|
195
|
+
triggers: rootCauseTriggers,
|
|
196
|
+
},
|
|
197
|
+
promptPreview,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
@@ -20,9 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"exclusionRationale": "Excludes upstream install hooks (SessionStart settings.json mutation), GitHub star-baiting (gh api user/starred), the update-notifier, and the past-session transcript-language scanner. Only the runtime Phase 0-3 engine and its Playwright/stealth templates are vendored.",
|
|
22
22
|
"localPatches": [
|
|
23
|
-
"engine/content_safety.py + FetchResult trust/risk metadata (content_trust, prompt_injection_risk, prompt_injection_signals, untrusted_content_boundary) and to_untrusted_text(): cherry-picked from insane-search a16f7c1 (upstream PR #5, v0.9.0). Adds prompt-injection labelling to the JSON surface (to_dict); the default CLI text output is intentionally left unchanged (plain content preserved, no envelope)."
|
|
24
|
-
"engine/transport.py SessionPool.warmup(): SKC-local fix. warmup() called _fetch_following(..., allow_private, DEFAULT_MAX_REDIRECTS, ...) with both names undefined in that scope -> NameError on every public-root warmup, silently breaking WAF sensor-cookie warmup (Akamai _abck etc.) that protected/adult sites rely on. Now binds allow_private = safety.allow_private_default() and uses safety.DEFAULT_MAX_REDIRECTS. Verified by engine/tests/test_u4.py warmup_once_guard. Re-apply on upstream re-sync.",
|
|
25
|
-
"engine/validators.py Layer 6 (+ engine/tests/test_u1.py): SKC-local fix. A SOFT challenge marker (captcha/datadome/access denied/checking your browser) hit with no success_selectors no longer forces CHALLENGE when the body is a COMPLETE content-bearing HTML page (_looks_complete_content_page). Such pages (e.g. adult/e-commerce sites that embed reCAPTCHA/age-gate/login scripts site-wide) were fully retrieved yet silently dropped (ok=false), blocking public-page QA. Complete page + soft marker -> WEAK_OK (reason soft_on_complete:*); script-only/incomplete stubs still -> CHALLENGE; HARD markers unchanged. Adds 2 regression tests. Re-apply on upstream re-sync."
|
|
23
|
+
"engine/content_safety.py + FetchResult trust/risk metadata (content_trust, prompt_injection_risk, prompt_injection_signals, untrusted_content_boundary) and to_untrusted_text(): cherry-picked from insane-search a16f7c1 (upstream PR #5, v0.9.0). Adds prompt-injection labelling to the JSON surface (to_dict); the default CLI text output is intentionally left unchanged (plain content preserved, no envelope)."
|
|
26
24
|
],
|
|
27
25
|
"notes": "Runtime engine is invoked via `python3 -m engine \"<url>\" --json` with cwd=this directory and PYTHONPATH pointed at this directory. Phase 0-2 require python3 + curl_cffi; Phase 3 requires node + playwright/playwright-extra/puppeteer-extra-plugin-stealth installed under engine/templates. SKC never auto-installs these dependencies."
|
|
28
26
|
}
|
|
@@ -163,32 +163,6 @@ def t_validator_small_fragment_still_challenge():
|
|
|
163
163
|
print(f" ✓ incomplete fragment → {v.verdict.value}")
|
|
164
164
|
|
|
165
165
|
|
|
166
|
-
def t_validator_soft_marker_on_complete_page_is_weak_ok():
|
|
167
|
-
# A COMPLETE content page that merely embeds a soft marker (e.g. a site-wide
|
|
168
|
-
# reCAPTCHA / login-modal / age-gate script, common on adult & e-commerce
|
|
169
|
-
# sites) is a real page we actually retrieved — not a challenge. Regression
|
|
170
|
-
# guard for the soft-marker false positive that silently dropped fully
|
|
171
|
-
# rendered pages whose body was already in hand.
|
|
172
|
-
body = ('<!doctype html><html lang="en"><head><title>Real Page</title>'
|
|
173
|
-
'<script src="https://www.google.com/recaptcha/api.js"></script></head>'
|
|
174
|
-
'<body><h1>Welcome</h1><p>' + ('real content ' * 400) +
|
|
175
|
-
'</p><div class="captcha">protected</div></body></html>')
|
|
176
|
-
v = validate(_Resp(200, body, headers={"Content-Type": "text/html"}))
|
|
177
|
-
assert v.body_size >= 3000, v.body_size
|
|
178
|
-
assert v.verdict == Verdict.WEAK_OK, (v.verdict, v.reasons)
|
|
179
|
-
assert any("soft_on_complete" in r for r in v.reasons), v.reasons
|
|
180
|
-
print(f" ✓ soft marker on complete page → {v.verdict.value} ({v.reasons})")
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
def t_validator_soft_marker_on_stub_still_challenge():
|
|
184
|
-
# A soft marker on a script-only / incomplete stub (no real visible text)
|
|
185
|
-
# stays a challenge — the fix must not swallow genuine interstitials.
|
|
186
|
-
body = '<html><head></head><body><script>showCaptcha();</script></body></html>'
|
|
187
|
-
v = validate(_Resp(200, body, headers={"Content-Type": "text/html"}))
|
|
188
|
-
assert v.verdict == Verdict.CHALLENGE, (v.verdict, v.reasons)
|
|
189
|
-
print(f" ✓ soft marker on script stub → {v.verdict.value}")
|
|
190
|
-
|
|
191
|
-
|
|
192
166
|
ALL = [
|
|
193
167
|
("scheduler_diversity_under_cap", t_scheduler_diversity_under_cap),
|
|
194
168
|
("scheduler_avoid_deprioritized_not_deleted", t_scheduler_avoid_deprioritized_not_deleted),
|
|
@@ -202,8 +176,6 @@ ALL = [
|
|
|
202
176
|
("validator_small_complete_page_is_weak_ok", t_validator_small_complete_page_is_weak_ok),
|
|
203
177
|
("validator_small_script_stub_still_challenge", t_validator_small_script_stub_still_challenge),
|
|
204
178
|
("validator_small_fragment_still_challenge", t_validator_small_fragment_still_challenge),
|
|
205
|
-
("validator_soft_marker_on_complete_page_is_weak_ok", t_validator_soft_marker_on_complete_page_is_weak_ok),
|
|
206
|
-
("validator_soft_marker_on_stub_still_challenge", t_validator_soft_marker_on_stub_still_challenge),
|
|
207
179
|
]
|
|
208
180
|
|
|
209
181
|
|
|
@@ -77,15 +77,14 @@ class SessionPool:
|
|
|
77
77
|
if ent is None or ent.warmed:
|
|
78
78
|
return False
|
|
79
79
|
from . import safety
|
|
80
|
-
|
|
81
|
-
ok, _reason = safety.classify_url(root_url, allow_private)
|
|
80
|
+
ok, _reason = safety.classify_url(root_url, safety.allow_private_default())
|
|
82
81
|
if not ok:
|
|
83
82
|
ent.warmed = True # don't retry a blocked root
|
|
84
83
|
return False
|
|
85
84
|
ent.warmed = True # mark first to avoid duplicate warmups under race
|
|
86
85
|
def _do_get(u):
|
|
87
86
|
return ent.session.get(u, timeout=timeout, allow_redirects=False)
|
|
88
|
-
resp, err = self._fetch_following(_do_get, root_url, allow_private,
|
|
87
|
+
resp, err = self._fetch_following(_do_get, root_url, allow_private, DEFAULT_MAX_REDIRECTS, ent)
|
|
89
88
|
return resp is not None and err is None
|
|
90
89
|
|
|
91
90
|
def inject_cookies(self, host: str, impersonate: str,
|
|
@@ -292,20 +292,6 @@ def validate(
|
|
|
292
292
|
# --- Layer 6: no positive proof — heuristics --------------------------
|
|
293
293
|
soft = _soft_marker_hits(lowered)
|
|
294
294
|
if soft:
|
|
295
|
-
# Soft markers ("captcha", "datadome", "access denied", "checking your
|
|
296
|
-
# browser") legitimately appear site-wide in scripts, login/age-gate
|
|
297
|
-
# modals, and consent banners on real content pages — notably adult and
|
|
298
|
-
# e-commerce sites that embed reCAPTCHA everywhere. Treating a lone soft
|
|
299
|
-
# hit as decisive silently drops fully-rendered pages whose body we
|
|
300
|
-
# actually retrieved. Real WAF interstitials are caught by the HARD
|
|
301
|
-
# markers above, or are script-only / incomplete stubs that fail the
|
|
302
|
-
# completeness check. So: a COMPLETE, content-bearing HTML document that
|
|
303
|
-
# merely mentions a soft marker is a real page (terminal WEAK_OK); only
|
|
304
|
-
# an incomplete / script-only / stub body stays a challenge.
|
|
305
|
-
if _looks_complete_content_page(text, lowered):
|
|
306
|
-
r.verdict = Verdict.WEAK_OK
|
|
307
|
-
r.reasons.extend(f"soft_on_complete:{m}" for m in soft[:3])
|
|
308
|
-
return r
|
|
309
295
|
r.verdict = Verdict.CHALLENGE
|
|
310
296
|
r.reasons.extend(f"soft:{m}" for m in soft[:3])
|
|
311
297
|
return r
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: ponytail
|
|
3
|
-
description: "Lazy senior dev mode — always pick the simplest, shortest solution that works (YAGNI, reuse, stdlib/native first), never cutting validation, error handling, security, or accessibility."
|
|
4
|
-
alwaysApply: true
|
|
5
|
-
license: MIT
|
|
6
|
-
source: https://github.com/DietrichGebert/ponytail
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# Ponytail — lazy senior dev mode
|
|
10
|
-
|
|
11
|
-
You write code like the laziest senior dev in the room. Lazy means efficient,
|
|
12
|
-
not careless. The best code is the code you never wrote. This rule governs
|
|
13
|
-
*what* you build, not how you talk.
|
|
14
|
-
|
|
15
|
-
## The ladder
|
|
16
|
-
|
|
17
|
-
After you understand the problem, stop at the first rung that holds:
|
|
18
|
-
|
|
19
|
-
1. **Does this need to exist at all?** Speculative need → skip it, say so in one line (YAGNI).
|
|
20
|
-
2. **Already in this codebase?** A helper, util, type, or pattern that lives here → reuse it. Look before you write; re-implementing what's a few files over is the most common slop.
|
|
21
|
-
3. **Stdlib does it?** Use it.
|
|
22
|
-
4. **Native platform feature covers it?** `<input type="date">` over a picker lib, CSS over JS, DB constraint over app code.
|
|
23
|
-
5. **Already-installed dependency solves it?** Use it. Never add a new dependency for what a few lines can do.
|
|
24
|
-
6. **Can it be one line?** One line.
|
|
25
|
-
7. **Only then:** the minimum code that works.
|
|
26
|
-
|
|
27
|
-
The ladder runs *after* comprehension, not instead of it. Read the task and the
|
|
28
|
-
code it touches, trace the real flow end to end, then climb. Two rungs work →
|
|
29
|
-
take the higher one and move on.
|
|
30
|
-
|
|
31
|
-
**Bug fix = root cause, not symptom.** Grep every caller of the function you are
|
|
32
|
-
about to touch. One guard in the shared function is a smaller diff than a guard
|
|
33
|
-
in every caller — and patching only the path the ticket names leaves every
|
|
34
|
-
sibling caller broken.
|
|
35
|
-
|
|
36
|
-
## Rules
|
|
37
|
-
|
|
38
|
-
- No unrequested abstractions: no interface with one implementation, no factory for one product, no config for a value that never changes.
|
|
39
|
-
- No boilerplate or scaffolding "for later" — later can scaffold for itself.
|
|
40
|
-
- Deletion over addition. Boring over clever; clever is what someone decodes at 3am.
|
|
41
|
-
- Fewest files possible. Shortest working diff wins — but only once you understand the problem. The smallest change in the wrong place is a second bug, not laziness.
|
|
42
|
-
- Two stdlib options the same size? Take the one that is correct on edge cases.
|
|
43
|
-
- Mark deliberate simplifications with a `ponytail:` comment that names the ceiling and the upgrade path (e.g. `// ponytail: global lock, per-account locks if throughput matters`).
|
|
44
|
-
|
|
45
|
-
## Output
|
|
46
|
-
|
|
47
|
-
Code first. Then at most three short lines: what was skipped, when to add it.
|
|
48
|
-
No essays defending a simplification — every paragraph of defense is complexity
|
|
49
|
-
smuggled back in as prose. Explanation the user explicitly asked for (a report,
|
|
50
|
-
a walkthrough) is not debt; give it in full.
|
|
51
|
-
|
|
52
|
-
Pattern: `[code] → skipped: [X], add when [Y].`
|
|
53
|
-
|
|
54
|
-
## When NOT to be lazy
|
|
55
|
-
|
|
56
|
-
Never simplify away: input validation at trust boundaries, error handling that
|
|
57
|
-
prevents data loss, security measures, accessibility basics, or anything
|
|
58
|
-
explicitly requested. Never be lazy about *understanding* the problem — the
|
|
59
|
-
ladder shortens the solution, never the reading. A confident small diff in the
|
|
60
|
-
wrong place is the dangerous kind of lazy.
|
|
61
|
-
|
|
62
|
-
Non-trivial logic (a branch, loop, parser, money/security path) leaves ONE
|
|
63
|
-
runnable check behind — the smallest thing that fails if the logic breaks. No
|
|
64
|
-
frameworks or fixtures unless asked. Trivial one-liners need no test.
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
Adapted from [ponytail](https://github.com/DietrichGebert/ponytail) (MIT) by DietrichGebert.
|