@humain/terminal 0.0.3-beta.2 → 0.0.4-beta.1

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 (136) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/bun/cli.d.ts.map +1 -1
  3. package/dist/bun/cli.js +0 -1
  4. package/dist/bun/cli.js.map +1 -1
  5. package/dist/core/agent-session.d.ts.map +1 -1
  6. package/dist/core/agent-session.js +7 -1
  7. package/dist/core/agent-session.js.map +1 -1
  8. package/dist/core/sdk.d.ts.map +1 -1
  9. package/dist/core/sdk.js +1 -1
  10. package/dist/core/sdk.js.map +1 -1
  11. package/dist/core/session-manager.d.ts +4 -0
  12. package/dist/core/session-manager.d.ts.map +1 -1
  13. package/dist/core/session-manager.js +46 -22
  14. package/dist/core/session-manager.js.map +1 -1
  15. package/dist/core/tools/index.d.ts +2 -1
  16. package/dist/core/tools/index.d.ts.map +1 -1
  17. package/dist/core/tools/index.js +9 -1
  18. package/dist/core/tools/index.js.map +1 -1
  19. package/dist/core/tools/subagent.d.ts +82 -0
  20. package/dist/core/tools/subagent.d.ts.map +1 -0
  21. package/dist/core/tools/subagent.js +980 -0
  22. package/dist/core/tools/subagent.js.map +1 -0
  23. package/dist/humain/bedrock-provider/vendor/index.bundle.d.mts +3 -0
  24. package/dist/humain/bedrock-provider/vendor/index.bundle.mjs +1509 -0
  25. package/dist/humain/index.d.ts.map +1 -1
  26. package/dist/humain/index.js +3 -0
  27. package/dist/humain/index.js.map +1 -1
  28. package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +36 -0
  29. package/dist/humain/mcp-adapter/vendor/README.md +47 -5
  30. package/dist/humain/mcp-adapter/vendor/cli.js +163 -1
  31. package/dist/humain/mcp-adapter/vendor/commands.ts +128 -22
  32. package/dist/humain/mcp-adapter/vendor/config.ts +25 -7
  33. package/dist/humain/mcp-adapter/vendor/direct-tools.ts +5 -4
  34. package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +1663 -458
  35. package/dist/humain/mcp-adapter/vendor/index.ts +219 -32
  36. package/dist/humain/mcp-adapter/vendor/init.ts +36 -4
  37. package/dist/humain/mcp-adapter/vendor/lifecycle.ts +315 -24
  38. package/dist/humain/mcp-adapter/vendor/mcp-auth-flow.ts +39 -6
  39. package/dist/humain/mcp-adapter/vendor/mcp-bearer-store.ts +387 -0
  40. package/dist/humain/mcp-adapter/vendor/mcp-callback-server.ts +27 -3
  41. package/dist/humain/mcp-adapter/vendor/mcp-code.ts +4 -0
  42. package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +42 -13
  43. package/dist/humain/mcp-adapter/vendor/metadata-cache.ts +32 -21
  44. package/dist/humain/mcp-adapter/vendor/package-mcp-loader.ts +168 -0
  45. package/dist/humain/mcp-adapter/vendor/package.json +179 -177
  46. package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +5 -4
  47. package/dist/humain/mcp-adapter/vendor/request-headers-command.ts +31 -37
  48. package/dist/humain/mcp-adapter/vendor/search-ranking.ts +92 -22
  49. package/dist/humain/mcp-adapter/vendor/server-manager.ts +140 -2
  50. package/dist/humain/mcp-adapter/vendor/session-recovery.ts +7 -0
  51. package/dist/humain/mcp-adapter/vendor/tool-approval.ts +15 -2
  52. package/dist/humain/mcp-adapter/vendor/tool-result-renderer.ts +44 -4
  53. package/dist/humain/mcp-adapter/vendor/types.ts +2 -0
  54. package/dist/humain/mcp-adapter/vendor/ui-server.ts +18 -16
  55. package/dist/humain/mcp-adapter/vendor/utils.ts +70 -0
  56. package/dist/humain/web-access/vendor/anysearch.d.ts.map +1 -1
  57. package/dist/humain/web-access/vendor/anysearch.js +3 -2
  58. package/dist/humain/web-access/vendor/anysearch.js.map +1 -1
  59. package/dist/humain/web-access/vendor/brave.d.ts.map +1 -1
  60. package/dist/humain/web-access/vendor/brave.js +14 -4
  61. package/dist/humain/web-access/vendor/brave.js.map +1 -1
  62. package/dist/humain/web-access/vendor/curator-page.d.ts +3 -0
  63. package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
  64. package/dist/humain/web-access/vendor/curator-page.js +4 -1
  65. package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
  66. package/dist/humain/web-access/vendor/curator-server.d.ts +3 -0
  67. package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
  68. package/dist/humain/web-access/vendor/curator-server.js +6 -0
  69. package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
  70. package/dist/humain/web-access/vendor/exa.d.ts.map +1 -1
  71. package/dist/humain/web-access/vendor/exa.js +16 -7
  72. package/dist/humain/web-access/vendor/exa.js.map +1 -1
  73. package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
  74. package/dist/humain/web-access/vendor/extract.js +31 -3
  75. package/dist/humain/web-access/vendor/extract.js.map +1 -1
  76. package/dist/humain/web-access/vendor/gemini-search.d.ts +2 -2
  77. package/dist/humain/web-access/vendor/gemini-search.d.ts.map +1 -1
  78. package/dist/humain/web-access/vendor/gemini-search.js +25 -4
  79. package/dist/humain/web-access/vendor/gemini-search.js.map +1 -1
  80. package/dist/humain/web-access/vendor/index.d.ts.map +1 -1
  81. package/dist/humain/web-access/vendor/index.js +36 -49
  82. package/dist/humain/web-access/vendor/index.js.map +1 -1
  83. package/dist/humain/web-access/vendor/page-query.d.ts +1 -2
  84. package/dist/humain/web-access/vendor/page-query.d.ts.map +1 -1
  85. package/dist/humain/web-access/vendor/page-query.js +5 -2
  86. package/dist/humain/web-access/vendor/page-query.js.map +1 -1
  87. package/dist/humain/web-access/vendor/parallel-mcp.d.ts +10 -0
  88. package/dist/humain/web-access/vendor/parallel-mcp.d.ts.map +1 -0
  89. package/dist/humain/web-access/vendor/parallel-mcp.js +224 -0
  90. package/dist/humain/web-access/vendor/parallel-mcp.js.map +1 -0
  91. package/dist/humain/web-access/vendor/parallel.d.ts +1 -0
  92. package/dist/humain/web-access/vendor/parallel.d.ts.map +1 -1
  93. package/dist/humain/web-access/vendor/parallel.js +2 -2
  94. package/dist/humain/web-access/vendor/parallel.js.map +1 -1
  95. package/dist/humain/web-access/vendor/query-rewrite.d.ts +3 -0
  96. package/dist/humain/web-access/vendor/query-rewrite.d.ts.map +1 -0
  97. package/dist/humain/web-access/vendor/query-rewrite.js +42 -0
  98. package/dist/humain/web-access/vendor/query-rewrite.js.map +1 -0
  99. package/dist/humain/web-access/vendor/serper.d.ts +4 -0
  100. package/dist/humain/web-access/vendor/serper.d.ts.map +1 -0
  101. package/dist/humain/web-access/vendor/serper.js +206 -0
  102. package/dist/humain/web-access/vendor/serper.js.map +1 -0
  103. package/dist/humain/web-access/vendor/summary-model-scope.d.ts +5 -0
  104. package/dist/humain/web-access/vendor/summary-model-scope.d.ts.map +1 -1
  105. package/dist/humain/web-access/vendor/summary-model-scope.js +10 -5
  106. package/dist/humain/web-access/vendor/summary-model-scope.js.map +1 -1
  107. package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
  108. package/dist/humain/web-access/vendor/summary-review.js +40 -14
  109. package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
  110. package/dist/humain/web-access/vendor/tavily.d.ts.map +1 -1
  111. package/dist/humain/web-access/vendor/tavily.js +14 -4
  112. package/dist/humain/web-access/vendor/tavily.js.map +1 -1
  113. package/dist/humain/web-access/vendor/utils.d.ts +10 -0
  114. package/dist/humain/web-access/vendor/utils.d.ts.map +1 -1
  115. package/dist/humain/web-access/vendor/utils.js +70 -0
  116. package/dist/humain/web-access/vendor/utils.js.map +1 -1
  117. package/dist/humain/web-access/vendor/valyu.d.ts +6 -0
  118. package/dist/humain/web-access/vendor/valyu.d.ts.map +1 -0
  119. package/dist/humain/web-access/vendor/valyu.js +194 -0
  120. package/dist/humain/web-access/vendor/valyu.js.map +1 -0
  121. package/docs/extensions.md +1 -1
  122. package/examples/extensions/README.md +1 -1
  123. package/examples/extensions/subagent/README.md +24 -147
  124. package/npm-shrinkwrap.json +7 -2
  125. package/package.json +7 -2
  126. package/src/humain/mcp-adapter/UPSTREAM.md +1 -1
  127. package/src/humain/mcp-adapter/upstream.json +3 -3
  128. package/src/humain/mcp-adapter/vendor/CHANGELOG.md +36 -0
  129. package/src/humain/web-access/UPSTREAM.md +1 -1
  130. package/src/humain/web-access/upstream.json +4 -4
  131. package/dist/bun/register-bedrock.d.ts +0 -2
  132. package/dist/bun/register-bedrock.d.ts.map +0 -1
  133. package/dist/bun/register-bedrock.js +0 -4
  134. package/dist/bun/register-bedrock.js.map +0 -1
  135. package/examples/extensions/subagent/agents.ts +0 -126
  136. package/examples/extensions/subagent/index.ts +0 -1015
@@ -8,6 +8,76 @@ export function getWebSearchConfigDir() {
8
8
  export function getWebSearchConfigPath() {
9
9
  return join(getWebSearchConfigDir(), "web-search.json");
10
10
  }
11
+ export function resolveApiBaseUrl(options) {
12
+ const fromEnvironment = options.environmentValue !== undefined;
13
+ const value = fromEnvironment ? options.environmentValue : options.configuredValue;
14
+ if (value === undefined)
15
+ return options.defaultValue;
16
+ const source = fromEnvironment
17
+ ? options.environmentKey
18
+ : `${options.configKey} in ${getWebSearchConfigPath()}`;
19
+ if (typeof value !== "string" || value.trim().length === 0) {
20
+ throw new Error(`${source} must be an absolute HTTP(S) URL`);
21
+ }
22
+ let url;
23
+ try {
24
+ url = new URL(value.trim());
25
+ }
26
+ catch {
27
+ throw new Error(`${source} must be an absolute HTTP(S) URL`);
28
+ }
29
+ if (url.protocol !== "https:") {
30
+ throw new Error(`${source} must be an absolute HTTPS URL`);
31
+ }
32
+ if (url.username || url.password) {
33
+ throw new Error(`${source} must not include credentials`);
34
+ }
35
+ if (url.search || url.hash) {
36
+ throw new Error(`${source} must not include query parameters or fragments`);
37
+ }
38
+ url.search = "";
39
+ url.hash = "";
40
+ url.pathname = url.pathname.replace(/\/+$/, "");
41
+ return url.toString().replace(/\/+$/, "");
42
+ }
43
+ const API_REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
44
+ const API_REQUEST_BODY_HEADERS = ["Content-Encoding", "Content-Language", "Content-Location", "Content-Type"];
45
+ const MAX_API_REDIRECTS = 5;
46
+ export async function fetchWithCredentialRedirects(url, init, credentialHeaders) {
47
+ let current = new URL(url);
48
+ let requestInit = init;
49
+ for (let redirects = 0;; redirects++) {
50
+ const response = await fetch(current, { ...requestInit, redirect: "manual" });
51
+ if (!API_REDIRECT_STATUSES.has(response.status))
52
+ return response;
53
+ const location = response.headers.get("location");
54
+ if (!location)
55
+ return response;
56
+ if (redirects === MAX_API_REDIRECTS) {
57
+ throw new Error(`Too many API redirects from ${url}`);
58
+ }
59
+ const next = new URL(location, current);
60
+ if (next.protocol !== "http:" && next.protocol !== "https:") {
61
+ throw new Error(`API redirect from ${current.origin} must use HTTP(S)`);
62
+ }
63
+ const method = requestInit.method?.toUpperCase() ?? "GET";
64
+ if (((response.status === 301 || response.status === 302) && method === "POST")
65
+ || (response.status === 303 && method !== "GET" && method !== "HEAD")) {
66
+ const headers = new Headers(requestInit.headers);
67
+ for (const name of API_REQUEST_BODY_HEADERS)
68
+ headers.delete(name);
69
+ const { body: _body, ...withoutBody } = requestInit;
70
+ requestInit = { ...withoutBody, method: "GET", headers };
71
+ }
72
+ if (next.origin !== current.origin) {
73
+ const headers = new Headers(requestInit.headers);
74
+ for (const name of credentialHeaders)
75
+ headers.delete(name);
76
+ requestInit = { ...requestInit, headers };
77
+ }
78
+ current = next;
79
+ }
80
+ }
11
81
  const LOCAL_CURATOR_NETWORK_DEFAULTS = { enabled: false, host: "localhost", bind: "127.0.0.1" };
12
82
  function trimmedString(value) {
13
83
  if (typeof value !== "string")
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { hostname } from \"node:os\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../host.ts\";\n\nexport function getWebSearchConfigDir(): string {\n\treturn getAgentDir();\n}\n\nexport function getWebSearchConfigPath(): string {\n\treturn join(getWebSearchConfigDir(), \"web-search.json\");\n}\n\nexport interface CuratorNetworkConfig {\n\t/** Whether remote access was opted into via curatorRemote. */\n\tenabled: boolean;\n\thost: string;\n\tbind: string;\n}\n\nconst LOCAL_CURATOR_NETWORK_DEFAULTS: CuratorNetworkConfig = { enabled: false, host: \"localhost\", bind: \"127.0.0.1\" };\n\nfunction trimmedString(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\treturn trimmed.length > 0 ? trimmed : undefined;\n}\n\n/** Resolves the curator server bind address and URL host from `curatorRemote`. */\nexport function resolveCuratorNetworkConfig(): CuratorNetworkConfig {\n\tconst configPath = getWebSearchConfigPath();\n\tif (!existsSync(configPath)) return LOCAL_CURATOR_NETWORK_DEFAULTS;\n\n\tlet raw: unknown;\n\ttry {\n\t\traw = JSON.parse(readFileSync(configPath, \"utf-8\"));\n\t} catch {\n\t\treturn LOCAL_CURATOR_NETWORK_DEFAULTS;\n\t}\n\tif (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return LOCAL_CURATOR_NETWORK_DEFAULTS;\n\n\tconst curatorRemote = (raw as Record<string, unknown>).curatorRemote;\n\tif (curatorRemote === true) return { enabled: true, host: hostname(), bind: \"0.0.0.0\" };\n\n\tif (curatorRemote && typeof curatorRemote === \"object\" && !Array.isArray(curatorRemote)) {\n\t\tconst obj = curatorRemote as Record<string, unknown>;\n\t\treturn {\n\t\t\tenabled: true,\n\t\t\thost: trimmedString(obj.host) ?? hostname(),\n\t\t\tbind: trimmedString(obj.bind) ?? \"0.0.0.0\",\n\t\t};\n\t}\n\n\treturn LOCAL_CURATOR_NETWORK_DEFAULTS;\n}\n\nexport function formatSeconds(s: number): string {\n\tconst h = Math.floor(s / 3600);\n\tconst m = Math.floor((s % 3600) / 60);\n\tconst sec = s % 60;\n\tif (h > 0) return `${h}:${String(m).padStart(2, \"0\")}:${String(sec).padStart(2, \"0\")}`;\n\treturn `${m}:${String(sec).padStart(2, \"0\")}`;\n}\n\nexport function readExecError(err: unknown): { code?: string; stderr: string; message: string } {\n\tif (!err || typeof err !== \"object\") {\n\t\treturn { stderr: \"\", message: String(err) };\n\t}\n\tconst code = (err as { code?: string }).code;\n\tconst message = (err as { message?: string }).message ?? \"\";\n\tconst stderrRaw = (err as { stderr?: Buffer | string }).stderr;\n\tconst stderr = Buffer.isBuffer(stderrRaw)\n\t\t? stderrRaw.toString(\"utf-8\")\n\t\t: typeof stderrRaw === \"string\"\n\t\t\t? stderrRaw\n\t\t\t: \"\";\n\treturn { code, stderr, message };\n}\n\nexport function isTimeoutError(err: unknown): boolean {\n\tif (!err || typeof err !== \"object\") return false;\n\tif ((err as { killed?: boolean }).killed) return true;\n\tconst name = (err as { name?: string }).name;\n\tconst code = (err as { code?: string }).code;\n\tconst message = (err as { message?: string }).message ?? \"\";\n\treturn name === \"AbortError\" || code === \"ETIMEDOUT\" || message.toLowerCase().includes(\"timed out\");\n}\n\nexport function trimErrorText(text: string): string {\n\treturn text.replace(/\\s+/g, \" \").trim().slice(0, 200);\n}\n\nexport function mapFfmpegError(err: unknown): string {\n\tconst { code, stderr, message } = readExecError(err);\n\tif (code === \"ENOENT\") return \"ffmpeg is not installed. Install with: brew install ffmpeg\";\n\tif (isTimeoutError(err)) return \"ffmpeg timed out extracting frame\";\n\tif (stderr.includes(\"403\")) return \"Stream URL returned 403 — may have expired, try again\";\n\tconst snippet = trimErrorText(stderr || message);\n\treturn snippet ? `ffmpeg failed: ${snippet}` : \"ffmpeg failed\";\n}\n"]}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { hostname } from \"node:os\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../host.ts\";\n\nexport function getWebSearchConfigDir(): string {\n\treturn getAgentDir();\n}\n\nexport function getWebSearchConfigPath(): string {\n\treturn join(getWebSearchConfigDir(), \"web-search.json\");\n}\n\ninterface ApiBaseUrlOptions {\n\tconfigKey: string;\n\tconfiguredValue: unknown;\n\tdefaultValue: string;\n\tenvironmentKey: string;\n\tenvironmentValue: string | undefined;\n}\n\nexport function resolveApiBaseUrl(options: ApiBaseUrlOptions): string {\n\tconst fromEnvironment = options.environmentValue !== undefined;\n\tconst value = fromEnvironment ? options.environmentValue : options.configuredValue;\n\tif (value === undefined) return options.defaultValue;\n\n\tconst source = fromEnvironment\n\t\t? options.environmentKey\n\t\t: `${options.configKey} in ${getWebSearchConfigPath()}`;\n\tif (typeof value !== \"string\" || value.trim().length === 0) {\n\t\tthrow new Error(`${source} must be an absolute HTTP(S) URL`);\n\t}\n\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(value.trim());\n\t} catch {\n\t\tthrow new Error(`${source} must be an absolute HTTP(S) URL`);\n\t}\n\tif (url.protocol !== \"https:\") {\n\t\tthrow new Error(`${source} must be an absolute HTTPS URL`);\n\t}\n\tif (url.username || url.password) {\n\t\tthrow new Error(`${source} must not include credentials`);\n\t}\n\tif (url.search || url.hash) {\n\t\tthrow new Error(`${source} must not include query parameters or fragments`);\n\t}\n\n\turl.search = \"\";\n\turl.hash = \"\";\n\turl.pathname = url.pathname.replace(/\\/+$/, \"\");\n\treturn url.toString().replace(/\\/+$/, \"\");\n}\n\nconst API_REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);\nconst API_REQUEST_BODY_HEADERS = [\"Content-Encoding\", \"Content-Language\", \"Content-Location\", \"Content-Type\"];\nconst MAX_API_REDIRECTS = 5;\n\nexport async function fetchWithCredentialRedirects(\n\turl: string,\n\tinit: RequestInit,\n\tcredentialHeaders: readonly string[],\n): Promise<Response> {\n\tlet current = new URL(url);\n\tlet requestInit = init;\n\n\tfor (let redirects = 0; ; redirects++) {\n\t\tconst response = await fetch(current, { ...requestInit, redirect: \"manual\" });\n\t\tif (!API_REDIRECT_STATUSES.has(response.status)) return response;\n\n\t\tconst location = response.headers.get(\"location\");\n\t\tif (!location) return response;\n\t\tif (redirects === MAX_API_REDIRECTS) {\n\t\t\tthrow new Error(`Too many API redirects from ${url}`);\n\t\t}\n\n\t\tconst next = new URL(location, current);\n\t\tif (next.protocol !== \"http:\" && next.protocol !== \"https:\") {\n\t\t\tthrow new Error(`API redirect from ${current.origin} must use HTTP(S)`);\n\t\t}\n\t\tconst method = requestInit.method?.toUpperCase() ?? \"GET\";\n\t\tif (\n\t\t\t((response.status === 301 || response.status === 302) && method === \"POST\")\n\t\t\t|| (response.status === 303 && method !== \"GET\" && method !== \"HEAD\")\n\t\t) {\n\t\t\tconst headers = new Headers(requestInit.headers);\n\t\t\tfor (const name of API_REQUEST_BODY_HEADERS) headers.delete(name);\n\t\t\tconst { body: _body, ...withoutBody } = requestInit;\n\t\t\trequestInit = { ...withoutBody, method: \"GET\", headers };\n\t\t}\n\t\tif (next.origin !== current.origin) {\n\t\t\tconst headers = new Headers(requestInit.headers);\n\t\t\tfor (const name of credentialHeaders) headers.delete(name);\n\t\t\trequestInit = { ...requestInit, headers };\n\t\t}\n\t\tcurrent = next;\n\t}\n}\n\nexport interface CuratorNetworkConfig {\n\t/** Whether remote access was opted into via curatorRemote. */\n\tenabled: boolean;\n\thost: string;\n\tbind: string;\n}\n\nconst LOCAL_CURATOR_NETWORK_DEFAULTS: CuratorNetworkConfig = { enabled: false, host: \"localhost\", bind: \"127.0.0.1\" };\n\nfunction trimmedString(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\treturn trimmed.length > 0 ? trimmed : undefined;\n}\n\n/** Resolves the curator server bind address and URL host from `curatorRemote`. */\nexport function resolveCuratorNetworkConfig(): CuratorNetworkConfig {\n\tconst configPath = getWebSearchConfigPath();\n\tif (!existsSync(configPath)) return LOCAL_CURATOR_NETWORK_DEFAULTS;\n\n\tlet raw: unknown;\n\ttry {\n\t\traw = JSON.parse(readFileSync(configPath, \"utf-8\"));\n\t} catch {\n\t\treturn LOCAL_CURATOR_NETWORK_DEFAULTS;\n\t}\n\tif (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return LOCAL_CURATOR_NETWORK_DEFAULTS;\n\n\tconst curatorRemote = (raw as Record<string, unknown>).curatorRemote;\n\tif (curatorRemote === true) return { enabled: true, host: hostname(), bind: \"0.0.0.0\" };\n\n\tif (curatorRemote && typeof curatorRemote === \"object\" && !Array.isArray(curatorRemote)) {\n\t\tconst obj = curatorRemote as Record<string, unknown>;\n\t\treturn {\n\t\t\tenabled: true,\n\t\t\thost: trimmedString(obj.host) ?? hostname(),\n\t\t\tbind: trimmedString(obj.bind) ?? \"0.0.0.0\",\n\t\t};\n\t}\n\n\treturn LOCAL_CURATOR_NETWORK_DEFAULTS;\n}\n\nexport function formatSeconds(s: number): string {\n\tconst h = Math.floor(s / 3600);\n\tconst m = Math.floor((s % 3600) / 60);\n\tconst sec = s % 60;\n\tif (h > 0) return `${h}:${String(m).padStart(2, \"0\")}:${String(sec).padStart(2, \"0\")}`;\n\treturn `${m}:${String(sec).padStart(2, \"0\")}`;\n}\n\nexport function readExecError(err: unknown): { code?: string; stderr: string; message: string } {\n\tif (!err || typeof err !== \"object\") {\n\t\treturn { stderr: \"\", message: String(err) };\n\t}\n\tconst code = (err as { code?: string }).code;\n\tconst message = (err as { message?: string }).message ?? \"\";\n\tconst stderrRaw = (err as { stderr?: Buffer | string }).stderr;\n\tconst stderr = Buffer.isBuffer(stderrRaw)\n\t\t? stderrRaw.toString(\"utf-8\")\n\t\t: typeof stderrRaw === \"string\"\n\t\t\t? stderrRaw\n\t\t\t: \"\";\n\treturn { code, stderr, message };\n}\n\nexport function isTimeoutError(err: unknown): boolean {\n\tif (!err || typeof err !== \"object\") return false;\n\tif ((err as { killed?: boolean }).killed) return true;\n\tconst name = (err as { name?: string }).name;\n\tconst code = (err as { code?: string }).code;\n\tconst message = (err as { message?: string }).message ?? \"\";\n\treturn name === \"AbortError\" || code === \"ETIMEDOUT\" || message.toLowerCase().includes(\"timed out\");\n}\n\nexport function trimErrorText(text: string): string {\n\treturn text.replace(/\\s+/g, \" \").trim().slice(0, 200);\n}\n\nexport function mapFfmpegError(err: unknown): string {\n\tconst { code, stderr, message } = readExecError(err);\n\tif (code === \"ENOENT\") return \"ffmpeg is not installed. Install with: brew install ffmpeg\";\n\tif (isTimeoutError(err)) return \"ffmpeg timed out extracting frame\";\n\tif (stderr.includes(\"403\")) return \"Stream URL returned 403 — may have expired, try again\";\n\tconst snippet = trimErrorText(stderr || message);\n\treturn snippet ? `ffmpeg failed: ${snippet}` : \"ffmpeg failed\";\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import type { SearchOptions, SearchResponse } from "./perplexity.ts";
2
+ export declare function isValyuAvailable(): boolean;
3
+ export declare function searchWithValyu(query: string, options?: SearchOptions & {
4
+ includeContent?: boolean;
5
+ }): Promise<SearchResponse>;
6
+ //# sourceMappingURL=valyu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"valyu.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/valyu.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAsIrE,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAa,GAAG;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAyDxI","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { ExtractedContent } from \"./extract.ts\";\nimport type { SearchOptions, SearchResponse } from \"./perplexity.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst VALYU_SEARCH_URL = \"https://api.valyu.ai/v1/search\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 60_000;\nconst MAX_SNIPPET_CHARS = 2_500;\nconst MAX_CONTENT_CHARS = 4_000;\n\ninterface WebSearchConfig {\n\tvalyuApiKey?: unknown;\n}\n\ninterface ValyuResult {\n\ttitle?: unknown;\n\turl?: unknown;\n\tdescription?: unknown;\n\tcontent?: unknown;\n}\n\nlet cachedConfig: WebSearchConfig | null = null;\n\nfunction loadConfig(): WebSearchConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = {};\n\t\treturn cachedConfig;\n\t}\n\tconst raw = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\tthrow new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);\n\t}\n\tcachedConfig = parsed as WebSearchConfig;\n\treturn cachedConfig;\n}\n\nasync function getApiKey(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"Valyu\",\n\t\tconfiguredValue: loadConfig().valyuApiKey,\n\t\tenvironmentValue: process.env.VALYU_API_KEY,\n\t\tsignal,\n\t});\n}\n\nasync function requireApiKey(signal?: AbortSignal): Promise<string> {\n\tconst apiKey = await getApiKey(signal);\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t\"Valyu API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"valyuApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set VALYU_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://platform.valyu.ai\",\n\t\t);\n\t}\n\treturn apiKey;\n}\n\nfunction normalizeCount(value: number | undefined): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) return 5;\n\treturn Math.max(1, Math.min(Math.floor(value), 20));\n}\n\nfunction normalizeDomain(value: string): string | null {\n\tlet input = value.trim().toLowerCase();\n\tif (!input) return null;\n\tif (input.startsWith(\"-\")) input = input.slice(1).trim();\n\tif (!input) return null;\n\ttry {\n\t\tconst parsed = input.includes(\"://\") ? new URL(input) : new URL(`https://${input}`);\n\t\tinput = parsed.hostname;\n\t} catch {\n\t\tinput = input.split(\"/\")[0]?.split(\":\")[0] ?? \"\";\n\t}\n\tinput = input.replace(/^\\.+|\\.+$/g, \"\");\n\treturn /^[a-z0-9][a-z0-9.-]*\\.[a-z]{2,}$/i.test(input) ? input : null;\n}\n\nfunction mapDomainFilter(domainFilter: string[] | undefined): { included_sources?: string[]; excluded_sources?: string[] } {\n\tif (!domainFilter?.length) return {};\n\tconst included_sources: string[] = [];\n\tconst excluded_sources: string[] = [];\n\tfor (const raw of domainFilter) {\n\t\tconst domain = normalizeDomain(raw);\n\t\tif (!domain) continue;\n\t\tconst target = raw.trim().startsWith(\"-\") ? excluded_sources : included_sources;\n\t\tif (!target.includes(domain)) target.push(domain);\n\t}\n\treturn {\n\t\t...(included_sources.length > 0 ? { included_sources } : {}),\n\t\t...(excluded_sources.length > 0 ? { excluded_sources } : {}),\n\t};\n}\n\nfunction recencyToStartDate(filter: SearchOptions[\"recencyFilter\"]): string | undefined {\n\tif (!filter) return undefined;\n\tconst days = { day: 1, week: 7, month: 30, year: 365 }[filter];\n\treturn days ? new Date(Date.now() - days * 86_400_000).toISOString().slice(0, 10) : undefined;\n}\n\nfunction text(value: unknown, limit: number): string {\n\treturn typeof value === \"string\" ? value.replace(/\\s+/g, \" \").trim().slice(0, limit) : \"\";\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction invalidResponse(message: string): Error {\n\treturn new Error(`Valyu API returned invalid response: ${message}`);\n}\n\nfunction parseResponse(value: unknown): ValyuResult[] {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) throw invalidResponse(\"expected an object envelope\");\n\tconst envelope = value as Record<string, unknown>;\n\tif (envelope.success !== true) throw invalidResponse(\"expected success true\");\n\tif (!Array.isArray(envelope.results)) throw invalidResponse(\"expected results array\");\n\treturn envelope.results as ValyuResult[];\n}\n\nfunction buildAnswer(results: SearchResponse[\"results\"]): string {\n\treturn results.map((result) => result.snippet\n\t\t? `${result.snippet}\\nSource: ${result.title} (${result.url})`\n\t\t: `Source: ${result.title} (${result.url})`).join(\"\\n\\n\");\n}\n\nexport function isValyuAvailable(): boolean {\n\treturn hasCredentialSource({ provider: \"Valyu\", configuredValue: loadConfig().valyuApiKey, environmentValue: process.env.VALYU_API_KEY });\n}\n\nexport async function searchWithValyu(query: string, options: SearchOptions & { includeContent?: boolean } = {}): Promise<SearchResponse> {\n\tconst apiKey = await requireApiKey(options.signal);\n\tconst numResults = normalizeCount(options.numResults);\n\tconst startDate = recencyToStartDate(options.recencyFilter);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(VALYU_SEARCH_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"x-api-key\": apiKey, \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n\t\t\tbody: JSON.stringify({ query, max_num_results: numResults, ...mapDomainFilter(options.domainFilter), ...(startDate ? { start_date: startDate } : {}) }),\n\t\t\tsignal: options.signal ? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal]) : AbortSignal.timeout(SEARCH_TIMEOUT_MS),\n\t\t});\n\t} catch (err) {\n\t\tconst message = errorMessage(err);\n\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\tif (redactedMessage.toLowerCase().includes(\"abort\")) activityMonitor.logComplete(activityId, 0);\n\t\telse activityMonitor.logError(activityId, redactedMessage);\n\t\tif (redactedMessage === message) throw err;\n\t\tconst redactedError = new Error(redactedMessage);\n\t\tif (err instanceof Error) redactedError.name = err.name;\n\t\tthrow redactedError;\n\t}\n\tif (!response.ok) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tconst errorText = redactCredential(await response.text(), apiKey);\n\t\tthrow new Error(`Valyu API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t}\n\tlet rawData: unknown;\n\ttry {\n\t\trawData = await response.json();\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow new Error(`Valyu API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\tlet entries: ValyuResult[];\n\ttry {\n\t\tentries = parseResponse(rawData);\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow err;\n\t}\n\tactivityMonitor.logComplete(activityId, response.status);\n\tconst results: SearchResponse[\"results\"] = [];\n\tconst inlineContent: ExtractedContent[] = [];\n\tfor (const entry of entries) {\n\t\tif (!entry || typeof entry !== \"object\") continue;\n\t\tconst url = text(entry.url, Number.MAX_SAFE_INTEGER);\n\t\tif (!url) continue;\n\t\tconst title = text(entry.title, Number.MAX_SAFE_INTEGER) || `Source ${results.length + 1}`;\n\t\tconst content = text(entry.content, MAX_CONTENT_CHARS);\n\t\tconst description = text(entry.description, MAX_SNIPPET_CHARS);\n\t\tresults.push({ title, url, snippet: (content || description).slice(0, MAX_SNIPPET_CHARS) });\n\t\tif (options.includeContent && content) inlineContent.push({ url, title, content, error: null });\n\t\tif (results.length >= numResults) break;\n\t}\n\treturn { answer: buildAnswer(results), results, ...(inlineContent.length > 0 ? { inlineContent } : {}) };\n}\n"]}
@@ -0,0 +1,194 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { activityMonitor } from "./activity.js";
3
+ import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.js";
4
+ import { getWebSearchConfigPath } from "./utils.js";
5
+ const VALYU_SEARCH_URL = "https://api.valyu.ai/v1/search";
6
+ const CONFIG_PATH = getWebSearchConfigPath();
7
+ const SEARCH_TIMEOUT_MS = 60_000;
8
+ const MAX_SNIPPET_CHARS = 2_500;
9
+ const MAX_CONTENT_CHARS = 4_000;
10
+ let cachedConfig = null;
11
+ function loadConfig() {
12
+ if (cachedConfig)
13
+ return cachedConfig;
14
+ if (!existsSync(CONFIG_PATH)) {
15
+ cachedConfig = {};
16
+ return cachedConfig;
17
+ }
18
+ const raw = readFileSync(CONFIG_PATH, "utf-8");
19
+ let parsed;
20
+ try {
21
+ parsed = JSON.parse(raw);
22
+ }
23
+ catch (err) {
24
+ const message = err instanceof Error ? err.message : String(err);
25
+ throw new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);
26
+ }
27
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
28
+ throw new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);
29
+ }
30
+ cachedConfig = parsed;
31
+ return cachedConfig;
32
+ }
33
+ async function getApiKey(signal) {
34
+ return resolveCredential({
35
+ provider: "Valyu",
36
+ configuredValue: loadConfig().valyuApiKey,
37
+ environmentValue: process.env.VALYU_API_KEY,
38
+ signal,
39
+ });
40
+ }
41
+ async function requireApiKey(signal) {
42
+ const apiKey = await getApiKey(signal);
43
+ if (!apiKey) {
44
+ throw new Error("Valyu API key not found. Either:\n" +
45
+ ` 1. Create ${CONFIG_PATH} with { "valyuApiKey": "your-key" }\n` +
46
+ " 2. Set VALYU_API_KEY environment variable\n" +
47
+ "Get a key at https://platform.valyu.ai");
48
+ }
49
+ return apiKey;
50
+ }
51
+ function normalizeCount(value) {
52
+ if (typeof value !== "number" || !Number.isFinite(value))
53
+ return 5;
54
+ return Math.max(1, Math.min(Math.floor(value), 20));
55
+ }
56
+ function normalizeDomain(value) {
57
+ let input = value.trim().toLowerCase();
58
+ if (!input)
59
+ return null;
60
+ if (input.startsWith("-"))
61
+ input = input.slice(1).trim();
62
+ if (!input)
63
+ return null;
64
+ try {
65
+ const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
66
+ input = parsed.hostname;
67
+ }
68
+ catch {
69
+ input = input.split("/")[0]?.split(":")[0] ?? "";
70
+ }
71
+ input = input.replace(/^\.+|\.+$/g, "");
72
+ return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
73
+ }
74
+ function mapDomainFilter(domainFilter) {
75
+ if (!domainFilter?.length)
76
+ return {};
77
+ const included_sources = [];
78
+ const excluded_sources = [];
79
+ for (const raw of domainFilter) {
80
+ const domain = normalizeDomain(raw);
81
+ if (!domain)
82
+ continue;
83
+ const target = raw.trim().startsWith("-") ? excluded_sources : included_sources;
84
+ if (!target.includes(domain))
85
+ target.push(domain);
86
+ }
87
+ return {
88
+ ...(included_sources.length > 0 ? { included_sources } : {}),
89
+ ...(excluded_sources.length > 0 ? { excluded_sources } : {}),
90
+ };
91
+ }
92
+ function recencyToStartDate(filter) {
93
+ if (!filter)
94
+ return undefined;
95
+ const days = { day: 1, week: 7, month: 30, year: 365 }[filter];
96
+ return days ? new Date(Date.now() - days * 86_400_000).toISOString().slice(0, 10) : undefined;
97
+ }
98
+ function text(value, limit) {
99
+ return typeof value === "string" ? value.replace(/\s+/g, " ").trim().slice(0, limit) : "";
100
+ }
101
+ function errorMessage(err) {
102
+ return err instanceof Error ? err.message : String(err);
103
+ }
104
+ function invalidResponse(message) {
105
+ return new Error(`Valyu API returned invalid response: ${message}`);
106
+ }
107
+ function parseResponse(value) {
108
+ if (!value || typeof value !== "object" || Array.isArray(value))
109
+ throw invalidResponse("expected an object envelope");
110
+ const envelope = value;
111
+ if (envelope.success !== true)
112
+ throw invalidResponse("expected success true");
113
+ if (!Array.isArray(envelope.results))
114
+ throw invalidResponse("expected results array");
115
+ return envelope.results;
116
+ }
117
+ function buildAnswer(results) {
118
+ return results.map((result) => result.snippet
119
+ ? `${result.snippet}\nSource: ${result.title} (${result.url})`
120
+ : `Source: ${result.title} (${result.url})`).join("\n\n");
121
+ }
122
+ export function isValyuAvailable() {
123
+ return hasCredentialSource({ provider: "Valyu", configuredValue: loadConfig().valyuApiKey, environmentValue: process.env.VALYU_API_KEY });
124
+ }
125
+ export async function searchWithValyu(query, options = {}) {
126
+ const apiKey = await requireApiKey(options.signal);
127
+ const numResults = normalizeCount(options.numResults);
128
+ const startDate = recencyToStartDate(options.recencyFilter);
129
+ const activityId = activityMonitor.logStart({ type: "api", query });
130
+ let response;
131
+ try {
132
+ response = await fetch(VALYU_SEARCH_URL, {
133
+ method: "POST",
134
+ headers: { "x-api-key": apiKey, "Content-Type": "application/json", Accept: "application/json" },
135
+ body: JSON.stringify({ query, max_num_results: numResults, ...mapDomainFilter(options.domainFilter), ...(startDate ? { start_date: startDate } : {}) }),
136
+ signal: options.signal ? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal]) : AbortSignal.timeout(SEARCH_TIMEOUT_MS),
137
+ });
138
+ }
139
+ catch (err) {
140
+ const message = errorMessage(err);
141
+ const redactedMessage = redactCredential(message, apiKey);
142
+ if (redactedMessage.toLowerCase().includes("abort"))
143
+ activityMonitor.logComplete(activityId, 0);
144
+ else
145
+ activityMonitor.logError(activityId, redactedMessage);
146
+ if (redactedMessage === message)
147
+ throw err;
148
+ const redactedError = new Error(redactedMessage);
149
+ if (err instanceof Error)
150
+ redactedError.name = err.name;
151
+ throw redactedError;
152
+ }
153
+ if (!response.ok) {
154
+ activityMonitor.logComplete(activityId, response.status);
155
+ const errorText = redactCredential(await response.text(), apiKey);
156
+ throw new Error(`Valyu API error ${response.status}: ${errorText.slice(0, 300)}`);
157
+ }
158
+ let rawData;
159
+ try {
160
+ rawData = await response.json();
161
+ }
162
+ catch (err) {
163
+ activityMonitor.logComplete(activityId, response.status);
164
+ throw new Error(`Valyu API returned invalid JSON: ${errorMessage(err)}`);
165
+ }
166
+ let entries;
167
+ try {
168
+ entries = parseResponse(rawData);
169
+ }
170
+ catch (err) {
171
+ activityMonitor.logComplete(activityId, response.status);
172
+ throw err;
173
+ }
174
+ activityMonitor.logComplete(activityId, response.status);
175
+ const results = [];
176
+ const inlineContent = [];
177
+ for (const entry of entries) {
178
+ if (!entry || typeof entry !== "object")
179
+ continue;
180
+ const url = text(entry.url, Number.MAX_SAFE_INTEGER);
181
+ if (!url)
182
+ continue;
183
+ const title = text(entry.title, Number.MAX_SAFE_INTEGER) || `Source ${results.length + 1}`;
184
+ const content = text(entry.content, MAX_CONTENT_CHARS);
185
+ const description = text(entry.description, MAX_SNIPPET_CHARS);
186
+ results.push({ title, url, snippet: (content || description).slice(0, MAX_SNIPPET_CHARS) });
187
+ if (options.includeContent && content)
188
+ inlineContent.push({ url, title, content, error: null });
189
+ if (results.length >= numResults)
190
+ break;
191
+ }
192
+ return { answer: buildAnswer(results), results, ...(inlineContent.length > 0 ? { inlineContent } : {}) };
193
+ }
194
+ //# sourceMappingURL=valyu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"valyu.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/valyu.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { ExtractedContent } from \"./extract.ts\";\nimport type { SearchOptions, SearchResponse } from \"./perplexity.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst VALYU_SEARCH_URL = \"https://api.valyu.ai/v1/search\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 60_000;\nconst MAX_SNIPPET_CHARS = 2_500;\nconst MAX_CONTENT_CHARS = 4_000;\n\ninterface WebSearchConfig {\n\tvalyuApiKey?: unknown;\n}\n\ninterface ValyuResult {\n\ttitle?: unknown;\n\turl?: unknown;\n\tdescription?: unknown;\n\tcontent?: unknown;\n}\n\nlet cachedConfig: WebSearchConfig | null = null;\n\nfunction loadConfig(): WebSearchConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = {};\n\t\treturn cachedConfig;\n\t}\n\tconst raw = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\tthrow new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);\n\t}\n\tcachedConfig = parsed as WebSearchConfig;\n\treturn cachedConfig;\n}\n\nasync function getApiKey(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"Valyu\",\n\t\tconfiguredValue: loadConfig().valyuApiKey,\n\t\tenvironmentValue: process.env.VALYU_API_KEY,\n\t\tsignal,\n\t});\n}\n\nasync function requireApiKey(signal?: AbortSignal): Promise<string> {\n\tconst apiKey = await getApiKey(signal);\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t\"Valyu API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"valyuApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set VALYU_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://platform.valyu.ai\",\n\t\t);\n\t}\n\treturn apiKey;\n}\n\nfunction normalizeCount(value: number | undefined): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) return 5;\n\treturn Math.max(1, Math.min(Math.floor(value), 20));\n}\n\nfunction normalizeDomain(value: string): string | null {\n\tlet input = value.trim().toLowerCase();\n\tif (!input) return null;\n\tif (input.startsWith(\"-\")) input = input.slice(1).trim();\n\tif (!input) return null;\n\ttry {\n\t\tconst parsed = input.includes(\"://\") ? new URL(input) : new URL(`https://${input}`);\n\t\tinput = parsed.hostname;\n\t} catch {\n\t\tinput = input.split(\"/\")[0]?.split(\":\")[0] ?? \"\";\n\t}\n\tinput = input.replace(/^\\.+|\\.+$/g, \"\");\n\treturn /^[a-z0-9][a-z0-9.-]*\\.[a-z]{2,}$/i.test(input) ? input : null;\n}\n\nfunction mapDomainFilter(domainFilter: string[] | undefined): { included_sources?: string[]; excluded_sources?: string[] } {\n\tif (!domainFilter?.length) return {};\n\tconst included_sources: string[] = [];\n\tconst excluded_sources: string[] = [];\n\tfor (const raw of domainFilter) {\n\t\tconst domain = normalizeDomain(raw);\n\t\tif (!domain) continue;\n\t\tconst target = raw.trim().startsWith(\"-\") ? excluded_sources : included_sources;\n\t\tif (!target.includes(domain)) target.push(domain);\n\t}\n\treturn {\n\t\t...(included_sources.length > 0 ? { included_sources } : {}),\n\t\t...(excluded_sources.length > 0 ? { excluded_sources } : {}),\n\t};\n}\n\nfunction recencyToStartDate(filter: SearchOptions[\"recencyFilter\"]): string | undefined {\n\tif (!filter) return undefined;\n\tconst days = { day: 1, week: 7, month: 30, year: 365 }[filter];\n\treturn days ? new Date(Date.now() - days * 86_400_000).toISOString().slice(0, 10) : undefined;\n}\n\nfunction text(value: unknown, limit: number): string {\n\treturn typeof value === \"string\" ? value.replace(/\\s+/g, \" \").trim().slice(0, limit) : \"\";\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction invalidResponse(message: string): Error {\n\treturn new Error(`Valyu API returned invalid response: ${message}`);\n}\n\nfunction parseResponse(value: unknown): ValyuResult[] {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) throw invalidResponse(\"expected an object envelope\");\n\tconst envelope = value as Record<string, unknown>;\n\tif (envelope.success !== true) throw invalidResponse(\"expected success true\");\n\tif (!Array.isArray(envelope.results)) throw invalidResponse(\"expected results array\");\n\treturn envelope.results as ValyuResult[];\n}\n\nfunction buildAnswer(results: SearchResponse[\"results\"]): string {\n\treturn results.map((result) => result.snippet\n\t\t? `${result.snippet}\\nSource: ${result.title} (${result.url})`\n\t\t: `Source: ${result.title} (${result.url})`).join(\"\\n\\n\");\n}\n\nexport function isValyuAvailable(): boolean {\n\treturn hasCredentialSource({ provider: \"Valyu\", configuredValue: loadConfig().valyuApiKey, environmentValue: process.env.VALYU_API_KEY });\n}\n\nexport async function searchWithValyu(query: string, options: SearchOptions & { includeContent?: boolean } = {}): Promise<SearchResponse> {\n\tconst apiKey = await requireApiKey(options.signal);\n\tconst numResults = normalizeCount(options.numResults);\n\tconst startDate = recencyToStartDate(options.recencyFilter);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(VALYU_SEARCH_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"x-api-key\": apiKey, \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n\t\t\tbody: JSON.stringify({ query, max_num_results: numResults, ...mapDomainFilter(options.domainFilter), ...(startDate ? { start_date: startDate } : {}) }),\n\t\t\tsignal: options.signal ? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal]) : AbortSignal.timeout(SEARCH_TIMEOUT_MS),\n\t\t});\n\t} catch (err) {\n\t\tconst message = errorMessage(err);\n\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\tif (redactedMessage.toLowerCase().includes(\"abort\")) activityMonitor.logComplete(activityId, 0);\n\t\telse activityMonitor.logError(activityId, redactedMessage);\n\t\tif (redactedMessage === message) throw err;\n\t\tconst redactedError = new Error(redactedMessage);\n\t\tif (err instanceof Error) redactedError.name = err.name;\n\t\tthrow redactedError;\n\t}\n\tif (!response.ok) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tconst errorText = redactCredential(await response.text(), apiKey);\n\t\tthrow new Error(`Valyu API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t}\n\tlet rawData: unknown;\n\ttry {\n\t\trawData = await response.json();\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow new Error(`Valyu API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\tlet entries: ValyuResult[];\n\ttry {\n\t\tentries = parseResponse(rawData);\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow err;\n\t}\n\tactivityMonitor.logComplete(activityId, response.status);\n\tconst results: SearchResponse[\"results\"] = [];\n\tconst inlineContent: ExtractedContent[] = [];\n\tfor (const entry of entries) {\n\t\tif (!entry || typeof entry !== \"object\") continue;\n\t\tconst url = text(entry.url, Number.MAX_SAFE_INTEGER);\n\t\tif (!url) continue;\n\t\tconst title = text(entry.title, Number.MAX_SAFE_INTEGER) || `Source ${results.length + 1}`;\n\t\tconst content = text(entry.content, MAX_CONTENT_CHARS);\n\t\tconst description = text(entry.description, MAX_SNIPPET_CHARS);\n\t\tresults.push({ title, url, snippet: (content || description).slice(0, MAX_SNIPPET_CHARS) });\n\t\tif (options.includeContent && content) inlineContent.push({ url, title, content, error: null });\n\t\tif (results.length >= numResults) break;\n\t}\n\treturn { answer: buildAnswer(results), results, ...(inlineContent.length > 0 ? { inlineContent } : {}) };\n}\n"]}
@@ -2968,7 +2968,7 @@ All examples in [examples/extensions/](../examples/extensions/).
2968
2968
  | `interactive-shell.ts` | Persistent shell session | `on("user_bash")` |
2969
2969
  | `sandbox/` | Sandboxed tool execution | Tool operations |
2970
2970
  | `gondolin/` | Route built-in tools and `!` commands into a Gondolin micro-VM | Tool operations, built-in tool overrides, `on("user_bash")` |
2971
- | `subagent/` | Spawn sub-agents | `registerTool`, `exec` |
2971
+ | `subagent/` | Sample agent definitions for the built-in `subagent` tool | agent/prompt markdown samples |
2972
2972
  | **Games** |||
2973
2973
  | `snake.ts` | Snake game | `registerCommand`, `ui.custom`, keyboard handling |
2974
2974
  | `space-invaders.ts` | Space Invaders game | `registerCommand`, `ui.custom` |
@@ -42,7 +42,7 @@ cp permission-gate.ts ~/.humain-terminal/agent/extensions/
42
42
  | `minimal-mode.ts` | Override built-in tool rendering for minimal display (only tool calls, no output in collapsed mode) |
43
43
  | `truncated-tool.ts` | Wraps ripgrep with proper output truncation (50KB/2000 lines) |
44
44
  | `ssh.ts` | Delegate all tools to a remote machine via SSH using pluggable operations |
45
- | `subagent/` | Delegate tasks to specialized subagents with isolated context windows |
45
+ | `subagent/` | Sample agent definitions and workflow presets for the built-in `subagent` tool |
46
46
 
47
47
  ### Commands & UI
48
48
 
@@ -1,23 +1,26 @@
1
- # Subagent Example
1
+ # Subagent Agent Definitions
2
2
 
3
- Delegate tasks to specialized subagents with isolated context windows.
3
+ The `subagent` tool is now a built-in core tool (see
4
+ `packages/coding-agent/src/core/tools/subagent.ts`) — no extension install is
5
+ required. It delegates tasks to specialized subagents with isolated context
6
+ windows, in three modes:
4
7
 
5
- ## Features
8
+ - **Single**: `{ agent: "name", task: "..." }`
9
+ - **Parallel**: `{ tasks: [{ agent, task }, ...] }` (max 8 tasks, up to 4 concurrent)
10
+ - **Chain**: `{ chain: [{ agent, task }, ...] }` with a `{previous}` placeholder
11
+ that carries the prior step's output
6
12
 
7
- - **Isolated context**: Each subagent runs in a separate `pi` process
8
- - **Streaming output**: See tool calls and progress as they happen
9
- - **Parallel streaming**: All parallel tasks stream updates simultaneously
10
- - **Markdown rendering**: Final output rendered with proper formatting (expanded view)
11
- - **Usage tracking**: Shows turns, tokens, cost, and context usage per agent
12
- - **Abort support**: Ctrl+C propagates to kill subagent processes
13
+ Subagents cannot delegate further (nesting is blocked via
14
+ `HUMAIN_TERMINAL_SUBAGENT_DEPTH`).
15
+
16
+ This directory keeps sample agent definitions and workflow prompt presets you
17
+ can copy into your own setup.
13
18
 
14
19
  ## Structure
15
20
 
16
21
  ```
17
22
  subagent/
18
23
  ├── README.md # This file
19
- ├── index.ts # The extension (entry point)
20
- ├── agents.ts # Agent discovery logic
21
24
  ├── agents/ # Sample agent definitions
22
25
  │ ├── scout.md # Fast recon, returns compressed context
23
26
  │ ├── planner.md # Creates implementation plans
@@ -29,147 +32,21 @@ subagent/
29
32
  └── implement-and-review.md # worker -> reviewer -> worker
30
33
  ```
31
34
 
32
- ## Installation
35
+ ## Installing the samples
33
36
 
34
- From the repository root, symlink the files:
37
+ Agent definitions are markdown files with `name`, `description`, optional
38
+ `tools` (comma-separated) and `model` frontmatter; the body becomes the
39
+ subagent's appended system prompt.
35
40
 
36
41
  ```bash
37
- # Symlink the extension (must be in a subdirectory with index.ts)
38
- mkdir -p ~/.humain-terminal/agent/extensions/subagent
39
- ln -sf "$(pwd)/packages/coding-agent/examples/extensions/subagent/index.ts" ~/.humain-terminal/agent/extensions/subagent/index.ts
40
- ln -sf "$(pwd)/packages/coding-agent/examples/extensions/subagent/agents.ts" ~/.humain-terminal/agent/extensions/subagent/agents.ts
41
-
42
- # Symlink agents
42
+ # User-scoped agents (default discovery scope)
43
43
  mkdir -p ~/.humain-terminal/agent/agents
44
- for f in packages/coding-agent/examples/extensions/subagent/agents/*.md; do
45
- ln -sf "$(pwd)/$f" ~/.humain-terminal/agent/agents/$(basename "$f")
46
- done
44
+ cp packages/coding-agent/examples/extensions/subagent/agents/*.md ~/.humain-terminal/agent/agents/
47
45
 
48
- # Symlink workflow prompts
46
+ # Optional workflow prompt presets
49
47
  mkdir -p ~/.humain-terminal/agent/prompts
50
- for f in packages/coding-agent/examples/extensions/subagent/prompts/*.md; do
51
- ln -sf "$(pwd)/$f" ~/.humain-terminal/agent/prompts/$(basename "$f")
52
- done
53
- ```
54
-
55
- ## Security Model
56
-
57
- This tool executes a separate `pi` subprocess with a delegated system prompt and tool/model configuration.
58
-
59
- **Project-local agents** (`.humain-terminal/agents/*.md`) are repo-controlled prompts that can instruct the model to read files, run bash commands, etc.
60
-
61
- **Default behavior:** Only loads **user-level agents** from `~/.humain-terminal/agent/agents`.
62
-
63
- To enable project-local agents, pass `agentScope: "both"` (or `"project"`). Only do this for repositories you trust.
64
-
65
- When running interactively, the tool prompts for confirmation before running project-local agents. Set `confirmProjectAgents: false` to disable.
66
-
67
- ## Usage
68
-
69
- ### Single agent
70
- ```
71
- Use scout to find all authentication code
72
- ```
73
-
74
- ### Parallel execution
75
- ```
76
- Run 2 scouts in parallel: one to find models, one to find providers
77
- ```
78
-
79
- ### Chained workflow
80
- ```
81
- Use a chain: first have scout find the read tool, then have planner suggest improvements
82
- ```
83
-
84
- ### Workflow prompts
85
- ```
86
- /implement add Redis caching to the session store
87
- /scout-and-plan refactor auth to support OAuth
88
- /implement-and-review add input validation to API endpoints
48
+ cp packages/coding-agent/examples/extensions/subagent/prompts/*.md ~/.humain-terminal/agent/prompts/
89
49
  ```
90
50
 
91
- ## Tool Modes
92
-
93
- | Mode | Parameter | Description |
94
- |------|-----------|-------------|
95
- | Single | `{ agent, task }` | One agent, one task |
96
- | Parallel | `{ tasks: [...] }` | Multiple agents run concurrently (max 8, 4 concurrent) |
97
- | Chain | `{ chain: [...] }` | Sequential with `{previous}` placeholder |
98
-
99
- ## Output Display
100
-
101
- **Collapsed view** (default):
102
- - Status icon (✓/✗/⏳) and agent name
103
- - Last 5-10 items (tool calls and text)
104
- - Usage stats: `3 turns ↑input ↓output RcacheRead WcacheWrite $cost ctx:contextTokens model`
105
-
106
- **Expanded view** (Ctrl+O):
107
- - Full task text
108
- - All tool calls with formatted arguments
109
- - Final output rendered as Markdown
110
- - Per-task usage (for chain/parallel)
111
-
112
- **Parallel mode streaming**:
113
- - Shows all tasks with live status (⏳ running, ✓ done, ✗ failed)
114
- - Updates as each task makes progress
115
- - Shows "2/3 done, 1 running" status
116
- - Returns each completed task's final output to the parent model, capped at 50 KB per task
117
- - Returns failure diagnostics from stderr/error messages when a child exits before producing output
118
-
119
- **Tool call formatting** (mimics built-in tools):
120
- - `$ command` for bash
121
- - `read ~/path:1-10` for read
122
- - `grep /pattern/ in ~/path` for grep
123
- - etc.
124
-
125
- ## Agent Definitions
126
-
127
- Agents are markdown files with YAML frontmatter:
128
-
129
- ```markdown
130
- ---
131
- name: my-agent
132
- description: What this agent does
133
- tools: read, grep, find, ls
134
- model: claude-haiku-4-5
135
- ---
136
-
137
- System prompt for the agent goes here.
138
- ```
139
-
140
- **Locations:**
141
- - `~/.humain-terminal/agent/agents/*.md` - User-level (always loaded)
142
- - `.humain-terminal/agents/*.md` - Project-level (only with `agentScope: "project"` or `"both"`)
143
-
144
- Project agents override user agents with the same name when `agentScope: "both"`.
145
-
146
- ## Sample Agents
147
-
148
- | Agent | Purpose | Model | Tools |
149
- |-------|---------|-------|-------|
150
- | `scout` | Fast codebase recon | Haiku | read, grep, find, ls, bash |
151
- | `planner` | Implementation plans | Sonnet | read, grep, find, ls |
152
- | `reviewer` | Code review | Sonnet | read, grep, find, ls, bash |
153
- | `worker` | General-purpose | Sonnet | (all default) |
154
-
155
- ## Workflow Prompts
156
-
157
- | Prompt | Flow |
158
- |--------|------|
159
- | `/implement <query>` | scout → planner → worker |
160
- | `/scout-and-plan <query>` | scout → planner |
161
- | `/implement-and-review <query>` | worker → reviewer → worker |
162
-
163
- ## Error Handling
164
-
165
- - **Exit code != 0**: Tool returns error with stderr/output
166
- - **stopReason "error"**: LLM error propagated with error message
167
- - **stopReason "aborted"**: User abort (Ctrl+C) kills subprocess, throws error
168
- - **Chain mode**: Stops at first failing step, reports which step failed
169
-
170
- ## Limitations
171
-
172
- - Output truncated to last 10 items in collapsed view (expand to see all)
173
- - Parallel model-visible output is capped at 50 KB per task; full results remain in tool details
174
- - Agents discovered fresh on each invocation (allows editing mid-session)
175
- - Parallel mode limited to 8 tasks, 4 concurrent
51
+ Project-local agents live in `.humain-terminal/agents/` inside a repository and
52
+ require `agentScope: "both"` (or `"project"`) plus an interactive confirmation.
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@humain/terminal",
3
- "version": "0.0.3-beta.2",
3
+ "version": "0.0.4-beta.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@humain/terminal",
9
- "version": "0.0.3-beta.2",
9
+ "version": "0.0.4-beta.1",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
+ "@aws-sdk/client-bedrock-runtime": "3.1048.0",
12
13
  "@earendil-works/pi-agent-core": "0.84.1",
13
14
  "@earendil-works/pi-ai": "0.84.1",
14
15
  "@earendil-works/pi-client": "0.84.1",
@@ -20,6 +21,7 @@
20
21
  "@mozilla/readability": "0.6.0",
21
22
  "@napi-rs/keyring": "1.3.0",
22
23
  "@silvia-odwyer/photon-node": "0.3.4",
24
+ "@smithy/node-http-handler": "4.7.3",
23
25
  "chalk": "5.6.2",
24
26
  "ajv": "8.20.0",
25
27
  "ajv-formats": "3.0.1",
@@ -30,10 +32,13 @@
30
32
  "highlight.js": "10.7.3",
31
33
  "hosted-git-info": "9.0.3",
32
34
  "ignore": "7.0.5",
35
+ "http-proxy-agent": "7.0.2",
36
+ "https-proxy-agent": "7.0.6",
33
37
  "jiti": "2.7.0",
34
38
  "linkedom": "0.16.11",
35
39
  "minimatch": "10.2.5",
36
40
  "p-limit": "6.1.0",
41
+ "partial-json": "0.1.7",
37
42
  "open": "10.2.0",
38
43
  "promise.try": "2.0.1",
39
44
  "proper-lockfile": "4.1.2",