@hybridaione/hybridclaw 0.1.21 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,26 @@
8
8
 
9
9
  ### Fixed
10
10
 
11
+ ## [0.1.22](https://github.com/HybridAIOne/hybridclaw/tree/v0.1.22)
12
+
13
+ ### Added
14
+
15
+ - **Skills trust scanner**: Added `src/skills-guard.ts` with Hermes-derived regex threat detection (exfiltration, prompt injection, destructive ops, persistence, reverse shells, obfuscation, supply chain, credential exposure), structural checks (file count/size limits, binary blocking, symlink escape checks), and invisible-unicode detection.
16
+ - **Skill scan cache**: Added mtime-signature + content-hash scanner caching to skip re-scan on unchanged skills.
17
+ - **Extended SKILL frontmatter**: Added support for `always`, `requires.bins`, `requires.env`, and `metadata.hybridclaw.{tags,related_skills}` while preserving backward compatibility for existing fields.
18
+
19
+ ### Changed
20
+
21
+ - **Skill discovery tiers**: Expanded skill discovery precedence to `extra < bundled < codex < claude < agents-personal < agents-project < workspace`, including `config.skills.extraDirs[]` and `.agents/skills` interop paths.
22
+ - **Skill prompt embedding modes**: Implemented Always/Summary/Hidden behavior via frontmatter flags (`always`, `disable-model-invocation`) with `maxAlwaysChars=10000`, `maxSkillsPromptChars=30000`, and `maxSkillsInPrompt=150`.
23
+ - **Skill eligibility gating**: Skills with unmet `requires` are now silently excluded from both prompt availability and slash-command resolution.
24
+ - **Skill slash commands**: Added command-name sanitization (32-char max), reserved built-in command blocking, and deterministic collision deduplication (`-2`, `-3`, ...), while keeping `/skill name`, `/skill:name`, and `/<name>` invocation compatibility.
25
+ - **Web tool routing guidance**: Tool descriptions and runtime prompt guidance now include explicit `web_fetch` vs browser decision rules, concrete SPA/auth/app categories, and quantified cost asymmetry.
26
+ - **web_fetch escalation signaling**: `web_fetch` now emits structured escalation hints (`javascript_required`, `spa_shell_only`, `empty_extraction`, `boilerplate_only`, `bot_blocked`) and surfaces them in tool output for browser fallback routing.
27
+ - **Browser extraction steering**: `browser_navigate` responses now include text preview metadata and explicit next-step hints (`browser_snapshot` with `mode="full"`), and docs/prompts now clarify that `browser_pdf` is export-only (not text extraction).
28
+
29
+ ### Fixed
30
+
11
31
  ## [0.1.21](https://github.com/HybridAIOne/hybridclaw/tree/v0.1.21)
12
32
 
13
33
  ### Added
package/README.md CHANGED
@@ -87,6 +87,7 @@ HybridClaw uses typed runtime config in `config.json` (auto-created on first run
87
87
 
88
88
  - Start from `config.example.json` (reference)
89
89
  - Runtime watches `config.json` and hot-reloads most settings (model defaults, heartbeat, prompt hooks, limits, etc.)
90
+ - `skills.extraDirs` adds additional enterprise/shared skill roots (lowest precedence tier)
90
91
  - `proactive.*` controls autonomous behavior (`activeHours`, `delegation`, `autoRetry`)
91
92
  - `observability.*` controls push ingest into HybridAI (`events:batch` endpoint, batching, identity metadata)
92
93
  - Some settings require restart to fully apply (for example HTTP bind host/port)
@@ -178,13 +179,25 @@ HybridClaw supports `SKILL.md`-based skills (`<skill-name>/SKILL.md`).
178
179
 
179
180
  You can place skills in:
180
181
 
181
- - `./skills/<skill-name>/SKILL.md` (project-level)
182
- - `<agent workspace>/skills/<skill-name>/SKILL.md` (agent-specific)
183
- - `$CODEX_HOME/skills/<skill-name>/SKILL.md`, `~/.codex/skills/<skill-name>/SKILL.md`, or `~/.claude/skills/<skill-name>/SKILL.md` (managed/shared)
182
+ - any directory listed in `config.skills.extraDirs[]` (enterprise/shared)
183
+ - bundled package skills (`<hybridclaw install>/skills/<skill-name>/SKILL.md`)
184
+ - `$CODEX_HOME/skills/<skill-name>/SKILL.md` or `~/.codex/skills/<skill-name>/SKILL.md`
185
+ - `~/.claude/skills/<skill-name>/SKILL.md`
186
+ - `~/.agents/skills/<skill-name>/SKILL.md`
187
+ - `./.agents/skills/<skill-name>/SKILL.md` (project)
188
+ - `./skills/<skill-name>/SKILL.md` (workspace)
184
189
 
185
190
  Load precedence is:
186
191
 
187
- - managed/shared < project < agent workspace
192
+ - `extra < bundled < codex < claude < agents-personal < agents-project < workspace`
193
+ - skills are merged by `name`; higher-precedence sources override lower-precedence ones
194
+
195
+ Security scanning is trust-aware:
196
+
197
+ - `bundled` sources are treated as `builtin` and not scanned
198
+ - `workspace` sources (`./skills/`, `./.agents/skills/`) are scanned; `caution` is allowed, `dangerous` is blocked
199
+ - `personal` sources (`~/.codex/skills/`, `~/.claude/skills/`, `~/.agents/skills/`) are scanned and blocked on `caution`/`dangerous`
200
+ - scanner includes Hermes-derived regex checks, structural limits (50 files, 1MB total, 256KB/file, binary/symlink checks), invisible-unicode detection, and mtime+content-hash cache reuse
188
201
 
189
202
  ### Required format
190
203
 
@@ -196,6 +209,14 @@ name: repo-orientation
196
209
  description: Quickly map an unfamiliar repository and identify where a requested feature should be implemented.
197
210
  user-invocable: true
198
211
  disable-model-invocation: false
212
+ always: false
213
+ requires:
214
+ bins: [docker, git]
215
+ env: [GITHUB_TOKEN]
216
+ metadata:
217
+ hybridclaw:
218
+ tags: [devops, docker]
219
+ related_skills: [kubernetes]
199
220
  ---
200
221
 
201
222
  # Repo Orientation
@@ -208,16 +229,25 @@ Supported frontmatter keys:
208
229
  - `description` (required)
209
230
  - `user-invocable` (optional, default `true`)
210
231
  - `disable-model-invocation` (optional, default `false`)
232
+ - `always` (optional, default `false`; embeds full skill body in the system prompt up to `maxAlwaysChars=10000`, then demotes to summary)
233
+ - `requires.bins` / `requires.env` (optional; skill is excluded unless requirements are met)
234
+ - `metadata.hybridclaw.tags` / `metadata.hybridclaw.related_skills` (optional metadata namespace)
211
235
 
212
236
  ### Using skills
213
237
 
214
- Skills are listed to the model as metadata (`name`, `description`, `location`), and the model reads `SKILL.md` on demand with the `read` tool.
238
+ Skills are listed to the model as metadata (`name`, `description`, `location`), and the model reads `SKILL.md` on demand with the `read` tool. Skills with `always: true` are embedded directly in the system prompt.
239
+
240
+ Prompt embedding modes:
241
+
242
+ - `Always`: `always: true` embeds full body in `<skill_always ...>` (budgeted by `maxAlwaysChars=10000`)
243
+ - `Summary`: default mode, emits only XML metadata under `<available_skills>`
244
+ - `Hidden`: `disable-model-invocation: true` excludes the skill from model prompt metadata (still invocable by slash command when `user-invocable: true`)
215
245
 
216
246
  Explicit invocation is supported via:
217
247
 
218
248
  - `/skill <name> [input]`
219
249
  - `/skill:<name> [input]`
220
- - `/<name> [input]` (when `user-invocable: true`)
250
+ - `/<name> [input]` (when `user-invocable: true`; command names are sanitized to lowercase `a-z0-9-`, max 32 chars, with `-2`/`-3` dedup and built-in command-name blocking)
221
251
 
222
252
  Example skill in this repo:
223
253
 
@@ -233,8 +263,8 @@ The agent has access to these sandboxed tools inside the container:
233
263
  - `memory` — durable memory files (`MEMORY.md`, `USER.md`, `memory/YYYY-MM-DD.md`)
234
264
  - `session_search` — search/summarize historical sessions from transcript archives
235
265
  - `delegate` — push-based background subagent tasks (`single`, `parallel`, `chain`) with auto-announced completion (no polling)
236
- - `web_fetch` — fetch a URL and extract readable content (HTML markdown/text)
237
- - `browser_*` (optional) — interactive browser automation (`navigate`, `snapshot`, `click`, `type`, `press`, `scroll`, `back`, `screenshot`, `pdf`, `close`)
266
+ - `web_fetch` — plain HTTP fetch + extraction for static/read-only content (docs, articles, READMEs, JSON/text APIs, direct files)
267
+ - `browser_*` (optional) — full browser automation for JS-rendered or interactive pages (`navigate`, `snapshot`, `click`, `type`, `press`, `scroll`, `back`, `screenshot`, `pdf`, `close`)
238
268
 
239
269
  `delegate` mode examples:
240
270
 
@@ -244,6 +274,12 @@ The agent has access to these sandboxed tools inside the container:
244
274
 
245
275
  Browser tooling notes:
246
276
 
277
+ - Routing default: prefer `web_fetch` first for read-only retrieval.
278
+ - Use browser tools for SPAs/web apps/auth flows/interaction tasks, or when `web_fetch` returns escalation hints (`javascript_required`, `spa_shell_only`, `empty_extraction`, `boilerplate_only`, `bot_blocked`).
279
+ - Cost profile: browser calls are typically ~10-100x slower/more expensive than `web_fetch`.
280
+ - Browser read flow: after `browser_navigate`, use `browser_snapshot` with `mode="full"` to extract content, then `browser_scroll` + `browser_snapshot` for additional lazy-loaded sections.
281
+ - `browser_pdf` is for export artifacts, not text extraction.
282
+
247
283
  - The shipped container image preinstalls `agent-browser` and Chromium (Playwright).
248
284
  - You can override the binary via `AGENT_BROWSER_BIN` if needed.
249
285
  - User-directed authenticated browser-flow testing is supported (including filling/submitting login forms on the requested site).
@@ -6,6 +6,9 @@
6
6
  "trustModelVersion": "",
7
7
  "trustModelAcceptedBy": ""
8
8
  },
9
+ "skills": {
10
+ "extraDirs": []
11
+ },
9
12
  "discord": {
10
13
  "prefix": "!claw"
11
14
  },
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "hybridclaw-agent",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "hybridclaw-agent",
9
- "version": "0.1.21",
9
+ "version": "0.1.22",
10
10
  "dependencies": {
11
11
  "@mozilla/readability": "^0.6.0",
12
12
  "agent-browser": "^0.15.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hybridclaw-agent",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "tsc",
@@ -56,6 +56,24 @@ const EXTRACT_IFRAMES_SCRIPT = `(() => {
56
56
  }));
57
57
  })()`;
58
58
 
59
+ const EXTRACT_TEXT_PREVIEW_SCRIPT = `(() => {
60
+ const bodyText = document.body ? String(document.body.innerText || '') : '';
61
+ const normalized = bodyText
62
+ .replace(/\\r/g, '')
63
+ .replace(/[ \\t]+\\n/g, '\\n')
64
+ .replace(/\\n{3,}/g, '\\n\\n')
65
+ .trim();
66
+ const previewLimit = 6000;
67
+ return {
68
+ text_length: normalized.length,
69
+ preview: normalized.slice(0, previewLimit),
70
+ preview_truncated: normalized.length > previewLimit,
71
+ has_noscript: Boolean(document.querySelector('noscript')),
72
+ root_shell: Boolean(document.querySelector('div#root:empty, div#app:empty, div#__next:empty')),
73
+ ready_state: String(document.readyState || ''),
74
+ };
75
+ })()`;
76
+
59
77
  const NETWORK_TIMINGS_SCRIPT = `(() => {
60
78
  const entries = performance.getEntriesByType('resource');
61
79
  return entries
@@ -563,6 +581,19 @@ function buildBotDetectionWarning(titleValue: unknown): Record<string, unknown>
563
581
  };
564
582
  }
565
583
 
584
+ function buildReadExtractionHint(params: {
585
+ contentLength: number;
586
+ hasNoscript: boolean;
587
+ rootShell: boolean;
588
+ }): string {
589
+ const base =
590
+ 'For content extraction, call browser_snapshot with {"mode":"full"} next. For long or lazy-loaded pages, run browser_scroll then browser_snapshot again.';
591
+ if (params.hasNoscript || params.rootShell || params.contentLength < 200) {
592
+ return `${base} This page currently looks dynamic/app-shell-like; do not conclude "inaccessible" before snapshot attempts.`;
593
+ }
594
+ return `${base} Avoid browser_pdf for text extraction; PDF export is for artifact output.`;
595
+ }
596
+
566
597
  function extractVisionTextContent(content: unknown): string {
567
598
  if (typeof content === 'string') return content.trim();
568
599
  if (!Array.isArray(content)) return '';
@@ -754,12 +785,31 @@ export async function executeBrowserTool(name: string, args: Record<string, unkn
754
785
  const data = (result.data || {}) as Record<string, unknown>;
755
786
  const title = String(data.title || '');
756
787
  const botWarning = buildBotDetectionWarning(title);
788
+ const textEval = await runBrowserEval(effectiveSessionId, EXTRACT_TEXT_PREVIEW_SCRIPT, 20_000);
789
+ const textData = textEval.success ? asRecord(textEval.result) : null;
790
+ const contentPreview = typeof textData?.preview === 'string' ? textData.preview : '';
791
+ const contentLength =
792
+ typeof textData?.text_length === 'number' && Number.isFinite(textData.text_length)
793
+ ? Math.max(0, Math.floor(textData.text_length))
794
+ : 0;
795
+ const contentPreviewTruncated = textData?.preview_truncated === true;
796
+ const hasNoscript = textData?.has_noscript === true;
797
+ const rootShell = textData?.root_shell === true;
798
+ const readyState = typeof textData?.ready_state === 'string' ? textData.ready_state : '';
799
+ const extractionHint = buildReadExtractionHint({ contentLength, hasNoscript, rootShell });
757
800
  // Best-effort priming so browser_network has request listeners active quickly.
758
801
  await runAgentBrowser(effectiveSessionId, 'network', ['requests']).catch(() => undefined);
759
802
  return success({
760
803
  url: data.url || parsed.toString(),
761
804
  title,
762
805
  session_id: effectiveSessionId,
806
+ content_text_length: contentLength,
807
+ ...(contentPreview ? { content_preview: contentPreview } : {}),
808
+ ...(contentPreview ? { content_preview_truncated: contentPreviewTruncated } : {}),
809
+ ...(readyState ? { ready_state: readyState } : {}),
810
+ ...(hasNoscript ? { has_noscript: true } : {}),
811
+ ...(rootShell ? { root_shell: true } : {}),
812
+ read_extraction_hint: extractionHint,
763
813
  ...(botWarning ? { bot_detection_warning: botWarning } : {}),
764
814
  });
765
815
  }
@@ -1000,7 +1050,7 @@ export const BROWSER_TOOL_DEFINITIONS: ToolDefinition[] = [
1000
1050
  function: {
1001
1051
  name: 'browser_navigate',
1002
1052
  description:
1003
- 'Navigate to an HTTP/HTTPS URL in a browser session. Private/loopback hosts are blocked by default (SSRF guard).',
1053
+ 'Navigate to a URL in a full browser session with JavaScript execution and dynamic rendering. Use for SPAs (React/Vue/Angular/Svelte), auth/login flows, dashboards/web apps (Notion, Google Docs, Airtable, Jira, etc.), interaction tasks (click/type/submit/scroll), bot/captcha/consent flows, or when web_fetch returns escalation hints (javascript_required, spa_shell_only, empty_extraction, boilerplate_only, bot_blocked). Prefer web_fetch instead for static docs/articles/wikis, direct API JSON/XML/text endpoints, and simple read-only retrieval. Important: browser_navigate opens the page but does not replace content extraction; for read/summarize tasks call browser_snapshot with mode="full" next. Browser usage is typically ~10-100x slower/more expensive than web_fetch. Private/loopback hosts are blocked by default (SSRF guard).',
1004
1054
  parameters: {
1005
1055
  type: 'object',
1006
1056
  properties: {
@@ -1015,7 +1065,7 @@ export const BROWSER_TOOL_DEFINITIONS: ToolDefinition[] = [
1015
1065
  function: {
1016
1066
  name: 'browser_snapshot',
1017
1067
  description:
1018
- 'Return an accessibility-tree snapshot of the current page with element refs usable by browser_click/browser_type.',
1068
+ 'Return an accessibility-tree snapshot of the current page with element refs usable by browser_click/browser_type. Use this to actually read page content after browser_navigate; for extraction tasks prefer mode="full" and repeat after browser_scroll on long/lazy-loaded pages.',
1019
1069
  parameters: {
1020
1070
  type: 'object',
1021
1071
  properties: {
@@ -1134,7 +1184,7 @@ export const BROWSER_TOOL_DEFINITIONS: ToolDefinition[] = [
1134
1184
  function: {
1135
1185
  name: 'browser_pdf',
1136
1186
  description:
1137
- 'Save the current page as PDF. Output path is constrained under /workspace/.browser-artifacts for safety.',
1187
+ 'Save the current page as PDF artifact. Output path is constrained under /workspace/.browser-artifacts for safety. Use for export/sharing only, not for text extraction or summarization.',
1138
1188
  parameters: {
1139
1189
  type: 'object',
1140
1190
  properties: {
@@ -879,7 +879,14 @@ export async function executeTool(name: string, argsJson: string): Promise<strin
879
879
  });
880
880
  const header = result.title ? `# ${result.title}\n\n` : '';
881
881
  const meta = `[${result.extractor}] ${result.finalUrl} (${result.status}, ${result.tookMs}ms)`;
882
- return `${meta}\n\n${header}${result.text}`;
882
+ const lines = [meta];
883
+ if (result.escalationHint) {
884
+ lines.push(`Escalation hint: ${result.escalationHint} (retry with browser_navigate for this URL).`);
885
+ }
886
+ if (result.warning) {
887
+ lines.push(`Warning: ${result.warning}`);
888
+ }
889
+ return `${lines.join('\n')}\n\n${header}${result.text}`;
883
890
  }
884
891
 
885
892
  case 'browser_navigate':
@@ -1207,7 +1214,7 @@ export const TOOL_DEFINITIONS: ToolDefinition[] = [
1207
1214
  function: {
1208
1215
  name: 'web_fetch',
1209
1216
  description:
1210
- 'Fetch a URL and extract its readable content as markdown or plain text. Works with HTML pages, JSON APIs, and markdown URLs. Use for reading web pages, documentation, API responses, etc.',
1217
+ 'Fetch a URL via plain HTTP GET and extract readable content (HTML to markdown/text). No JavaScript execution, no clicks, no form interaction. Use for static read-only retrieval: articles, docs, wikis, READMEs, API JSON/text endpoints, and direct files/PDFs. Avoid for SPAs (React/Vue/Angular/Next client routes), auth/login-gated pages, dashboards/web apps, bot/challenge flows, or content loaded after render via XHR/fetch. Cost: typically ~10-100x cheaper/faster than browser tools. Default to web_fetch for read-only retrieval, then escalate to browser_navigate when output is empty/boilerplate, JavaScript-required, SPA shell-only, or bot-blocked.',
1211
1218
  parameters: {
1212
1219
  type: 'object',
1213
1220
  properties: {
@@ -19,6 +19,25 @@ const TIMEOUT_MS = 30_000;
19
19
  const CACHE_TTL_MS = 15 * 60_000; // 15 min
20
20
  const CACHE_MAX_ENTRIES = 100;
21
21
  const READABILITY_MAX_HTML_CHARS = 1_000_000;
22
+ const ESCALATION_MIN_TEXT_CHARS = 200;
23
+ const ESCALATION_MIN_HTML_CHARS = 5_000;
24
+ const BOT_BLOCKED_PATTERNS = [
25
+ 'access denied',
26
+ 'bot detected',
27
+ 'captcha',
28
+ 'cf-chl-',
29
+ 'checking your browser',
30
+ 'cloudflare',
31
+ 'just a moment',
32
+ 'attention required',
33
+ 'verification required',
34
+ ];
35
+ const JAVASCRIPT_REQUIRED_PATTERNS = [
36
+ 'enable javascript',
37
+ 'javascript required',
38
+ 'requires javascript',
39
+ 'turn on javascript',
40
+ ];
22
41
  const USER_AGENT =
23
42
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36';
24
43
 
@@ -271,10 +290,71 @@ async function fetchWithRedirects(
271
290
  throw new Error(`Too many redirects (max ${maxRedirects})`);
272
291
  }
273
292
 
293
+ function normalizeForDetection(value: string): string {
294
+ return String(value || '').toLowerCase().replace(/\s+/g, ' ').trim();
295
+ }
296
+
297
+ function includesAny(haystack: string, needles: readonly string[]): boolean {
298
+ for (const needle of needles) {
299
+ if (haystack.includes(needle)) return true;
300
+ }
301
+ return false;
302
+ }
303
+
304
+ function detectEscalationHint(params: {
305
+ status: number;
306
+ contentType: string;
307
+ body: string;
308
+ extractedText: string;
309
+ }): WebFetchEscalationHint | undefined {
310
+ const normalizedBody = normalizeForDetection(params.body);
311
+ if (params.status === 403 || params.status === 429 || includesAny(normalizedBody, BOT_BLOCKED_PATTERNS)) {
312
+ return 'bot_blocked';
313
+ }
314
+
315
+ const isHtml = params.contentType.toLowerCase().includes('text/html');
316
+ if (!isHtml) return undefined;
317
+
318
+ if (
319
+ /<noscript[\s\S]{0,2000}javascript[\s\S]{0,2000}<\/noscript>/i.test(params.body) ||
320
+ includesAny(normalizedBody, JAVASCRIPT_REQUIRED_PATTERNS)
321
+ ) {
322
+ return 'javascript_required';
323
+ }
324
+
325
+ if (
326
+ /<div[^>]+id=["'](?:root|app|__next)["'][^>]*>\s*<\/div>/i.test(params.body) &&
327
+ normalizeForDetection(params.extractedText).length < ESCALATION_MIN_TEXT_CHARS
328
+ ) {
329
+ return 'spa_shell_only';
330
+ }
331
+
332
+ const normalizedExtracted = normalizeForDetection(params.extractedText);
333
+ if (normalizedExtracted.length === 0) {
334
+ return 'empty_extraction';
335
+ }
336
+
337
+ if (
338
+ normalizedExtracted.length < ESCALATION_MIN_TEXT_CHARS &&
339
+ params.body.length > ESCALATION_MIN_HTML_CHARS
340
+ ) {
341
+ return 'boilerplate_only';
342
+ }
343
+
344
+ return undefined;
345
+ }
346
+
274
347
  // ---------------------------------------------------------------------------
275
348
  // Public API
276
349
  // ---------------------------------------------------------------------------
277
350
 
351
+ export type WebFetchEscalationHint =
352
+ | 'javascript_required'
353
+ | 'empty_extraction'
354
+ | 'spa_shell_only'
355
+ | 'bot_blocked'
356
+ | 'boilerplate_only';
357
+
278
358
  export interface WebFetchResult {
279
359
  url: string;
280
360
  finalUrl: string;
@@ -290,6 +370,7 @@ export interface WebFetchResult {
290
370
  text: string;
291
371
  cached?: boolean;
292
372
  warning?: string;
373
+ escalationHint?: WebFetchEscalationHint;
293
374
  }
294
375
 
295
376
  export async function webFetch(params: {
@@ -327,10 +408,6 @@ export async function webFetch(params: {
327
408
  controller.signal,
328
409
  );
329
410
 
330
- if (!res.ok) {
331
- throw new Error(`Web fetch failed (${res.status}): ${res.statusText}`);
332
- }
333
-
334
411
  const contentType = res.headers.get('content-type') ?? 'application/octet-stream';
335
412
  const normalizedContentType = contentType.split(';')[0]?.trim() || 'application/octet-stream';
336
413
  const bodyResult = await readResponseText(res, MAX_RESPONSE_BYTES);
@@ -357,13 +434,26 @@ export async function webFetch(params: {
357
434
  }
358
435
  }
359
436
 
437
+ const extractedText = extractMode === 'text' ? text : markdownToText(text);
438
+ const escalationHint = detectEscalationHint({
439
+ status: res.status,
440
+ contentType: normalizedContentType,
441
+ body,
442
+ extractedText,
443
+ });
444
+
445
+ if (!res.ok && !escalationHint) {
446
+ throw new Error(`Web fetch failed (${res.status}): ${res.statusText}`);
447
+ }
448
+
360
449
  // Truncate
361
450
  const truncated = text.length > maxChars;
362
451
  if (truncated) text = text.slice(0, maxChars);
363
452
 
364
- const warning = bodyResult.truncated
365
- ? `Response body truncated after ${MAX_RESPONSE_BYTES} bytes.`
366
- : undefined;
453
+ const warnings: string[] = [];
454
+ if (!res.ok) warnings.push(`HTTP ${res.status} ${res.statusText}.`);
455
+ if (bodyResult.truncated) warnings.push(`Response body truncated after ${MAX_RESPONSE_BYTES} bytes.`);
456
+ const warning = warnings.length > 0 ? warnings.join(' ') : undefined;
367
457
 
368
458
  const result: WebFetchResult = {
369
459
  url: params.url,
@@ -379,6 +469,7 @@ export async function webFetch(params: {
379
469
  tookMs: Date.now() - start,
380
470
  text,
381
471
  warning,
472
+ escalationHint,
382
473
  };
383
474
 
384
475
  writeCache(cacheKey, result);
@@ -1 +1 @@
1
- {"version":3,"file":"prompt-hooks.d.ts","sourceRoot":"","sources":["../src/prompt-hooks.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAK5D,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC/D,MAAM,MAAM,sBAAsB,GAAG,cAAc,GAAG,aAAa,CAAC;AAEpE,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,cAAc,GAAG,cAAc,CAAC;IAC1C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,sBAAsB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB;AAQD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CASpF;AA2KD,wBAAgB,cAAc,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,EAAE,CAY7E;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAI7E"}
1
+ {"version":3,"file":"prompt-hooks.d.ts","sourceRoot":"","sources":["../src/prompt-hooks.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAK5D,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC/D,MAAM,MAAM,sBAAsB,GAAG,cAAc,GAAG,aAAa,CAAC;AAEpE,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,cAAc,GAAG,cAAc,CAAC;IAC1C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,sBAAsB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB;AAQD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CASpF;AAsLD,wBAAgB,cAAc,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,EAAE,CAY7E;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAI7E"}
@@ -45,6 +45,17 @@ function buildSafetyHook(context) {
45
45
  'After file changes, run commands only when asked; otherwise explicitly offer to run them immediately.',
46
46
  'Only skip file creation when the user explicitly asks for snippet-only or explanation-only output.',
47
47
  '',
48
+ '## Web Retrieval Routing (web_fetch vs browser_*)',
49
+ 'Decision rule: default to `web_fetch` for read-only content retrieval.',
50
+ 'Use browser tools only when at least one of these is true: (1) known app-like/auth-gated URL, (2) interaction is required (click/type/login/scroll), (3) `web_fetch` returned escalation hints, (4) user explicitly requested browser use.',
51
+ 'Prefer browser for: SPAs/client-rendered apps (React/Vue/Angular/Next client routes), dashboards/web apps, social feeds, login/OAuth/cookie-consent/CAPTCHA flows, or API-driven pages that populate after initial render.',
52
+ 'Prefer web_fetch for: docs/wikis/READMEs/articles/reference pages, direct JSON/XML/text/CSV/PDF endpoints, and simple read-only extraction.',
53
+ 'Escalation signals from web_fetch: `escalationHint` present, JavaScript-required pages, empty extraction, SPA shell-only pages, boilerplate-only extraction, or bot-blocked responses (403/429/challenge pages).',
54
+ 'Cost note: browser calls are typically ~10-100x slower/more expensive than web_fetch.',
55
+ 'Browser extraction flow (for read/summarize requests): after `browser_navigate`, call `browser_snapshot` with `mode="full"` before deciding content is unavailable.',
56
+ 'If snapshot content is incomplete, run `browser_scroll` and then `browser_snapshot` again (repeat a few times for long/lazy-loaded pages).',
57
+ 'Do not use `browser_pdf` as a text-reading step; it is an export artifact, not a text extraction tool.',
58
+ '',
48
59
  '## Browser Auth Handling',
49
60
  'When the user explicitly asks for login/auth-flow testing, browser tools may be used on the requested site, including filling credentials and submitting forms.',
50
61
  'Do not invent blanket restrictions such as "browser tools are only for public/unauthenticated pages" unless an actual tool/policy error says so.',
@@ -1 +1 @@
1
- {"version":3,"file":"prompt-hooks.js","sourceRoot":"","sources":["../src/prompt-hooks.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prompt-hooks.js","sourceRoot":"","sources":["../src/prompt-hooks.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,9 @@ export interface RuntimeSecurityConfig {
11
11
  export interface RuntimeConfig {
12
12
  version: number;
13
13
  security: RuntimeSecurityConfig;
14
+ skills: {
15
+ extraDirs: string[];
16
+ };
14
17
  discord: {
15
18
  prefix: string;
16
19
  };
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-config.d.ts","sourceRoot":"","sources":["../src/runtime-config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runtime-config.d.ts","sourceRoot":"","sources":["../src/runtime-config.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,9 @@ const DEFAULT_RUNTIME_CONFIG = {
14
14
  trustModelVersion: '',
15
15
  trustModelAcceptedBy: '',
16
16
  },
17
+ skills: {
18
+ extraDirs: [],
19
+ },
17
20
  discord: {
18
21
  prefix: '!claw',
19
22
  },
@@ -201,6 +204,7 @@ function readLegacyEnvPatch() {
201
204
  const env = process.env;
202
205
  const patch = {
203
206
  discord: {},
207
+ skills: {},
204
208
  hybridai: {},
205
209
  container: {},
206
210
  heartbeat: {},
@@ -217,6 +221,7 @@ function readLegacyEnvPatch() {
217
221
  },
218
222
  };
219
223
  const discord = patch.discord;
224
+ const skills = patch.skills;
220
225
  const hybridai = patch.hybridai;
221
226
  const container = patch.container;
222
227
  const heartbeat = patch.heartbeat;
@@ -230,6 +235,8 @@ function readLegacyEnvPatch() {
230
235
  const proactiveAutoRetry = proactive.autoRetry;
231
236
  if (env.DISCORD_PREFIX != null)
232
237
  discord.prefix = env.DISCORD_PREFIX;
238
+ if (env.SKILLS_EXTRA_DIRS != null)
239
+ skills.extraDirs = env.SKILLS_EXTRA_DIRS;
233
240
  if (env.HYBRIDAI_BASE_URL != null)
234
241
  hybridai.baseUrl = env.HYBRIDAI_BASE_URL;
235
242
  if (env.HYBRIDAI_MODEL != null)
@@ -349,6 +356,7 @@ function readLegacyEnvPatch() {
349
356
  function normalizeRuntimeConfig(patch) {
350
357
  const raw = patch ?? {};
351
358
  const rawSecurity = isRecord(raw.security) ? raw.security : {};
359
+ const rawSkills = isRecord(raw.skills) ? raw.skills : {};
352
360
  const rawDiscord = isRecord(raw.discord) ? raw.discord : {};
353
361
  const rawHybridAi = isRecord(raw.hybridai) ? raw.hybridai : {};
354
362
  const rawContainer = isRecord(raw.container) ? raw.container : {};
@@ -381,6 +389,9 @@ function normalizeRuntimeConfig(patch) {
381
389
  trustModelVersion: normalizeString(rawSecurity.trustModelVersion, DEFAULT_RUNTIME_CONFIG.security.trustModelVersion, { allowEmpty: true }),
382
390
  trustModelAcceptedBy: normalizeString(rawSecurity.trustModelAcceptedBy, DEFAULT_RUNTIME_CONFIG.security.trustModelAcceptedBy, { allowEmpty: true }),
383
391
  },
392
+ skills: {
393
+ extraDirs: normalizeStringArray(rawSkills.extraDirs, DEFAULT_RUNTIME_CONFIG.skills.extraDirs),
394
+ },
384
395
  discord: {
385
396
  prefix: normalizeString(rawDiscord.prefix, DEFAULT_RUNTIME_CONFIG.discord.prefix, { allowEmpty: false }),
386
397
  },
@@ -656,7 +667,12 @@ function migrateConfigSchemaOnStartup() {
656
667
  try {
657
668
  writeConfigFile(migrated);
658
669
  const from = previousVersion == null ? 'unknown' : String(previousVersion);
659
- console.info(`[runtime-config] migrated config schema from v${from} to v${CONFIG_VERSION}`);
670
+ if (previousVersion !== CONFIG_VERSION) {
671
+ console.info(`[runtime-config] migrated config schema from v${from} to v${CONFIG_VERSION}`);
672
+ }
673
+ else {
674
+ console.info(`[runtime-config] normalized config schema v${CONFIG_VERSION} (filled defaults/canonicalized values)`);
675
+ }
660
676
  }
661
677
  catch (err) {
662
678
  console.warn(`[runtime-config] schema migration failed: ${err instanceof Error ? err.message : String(err)}`);