@x-otto/tools 0.0.1-alpha.0
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 +72 -0
- package/dist/index.d.ts +1329 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +152 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["logger","os","delay","resolveUrl","CHECKSUMS","logger","childProcessSpawn","childProcessSpawn","spawn","logger","spawn","logger","nodeSpawn","DEFAULT_USER_AGENT"],"sources":["../src/hitl/grill-me.ts","../src/limits.ts","../src/tool-error.ts","../src/binary/binary-executor.ts","../src/binary/find.ts","../src/binary/ripgrep.ts","../src/binary/binary-executor-registry.ts","../src/shell/execution-backend.ts","../src/shell/local-execution-backend.ts","../src/shell/process.ts","../src/shell/service-detector.ts","../src/shell/bash.ts","../src/shell/bash-output.ts","../src/shell/kill-shell.ts","../src/search/find.ts","../src/search/grep.ts","../src/fs/read.ts","../src/fs/write.ts","../src/fs/diff.ts","../src/fs/fuzzy-match-impl.ts","../src/fs/edit.ts","../src/web/url-validator.ts","../src/web/html-to-text.ts","../src/web/fetch.ts","../src/web/search.ts","../src/orchestration/missing-capability.ts","../src/orchestration/agent-call-description.ts","../src/orchestration/dispatched-task.ts","../src/orchestration/delegate-job.ts","../src/orchestration/capability-gap.ts","../src/orchestration/task-inspect.ts","../src/orchestration/task-control.ts","../src/orchestration/list-models.ts","../src/orchestration/write-todos.ts","../src/orchestration/capture-file-state.ts","../src/orchestration/learn.ts","../src/orchestration/tool-search.ts","../src/lsp/constants.ts","../src/lsp/workspace-edit.ts","../src/lsp/lsp-formatters.ts","../src/lsp/readiness.ts","../src/lsp/lsp-server-manager.ts","../src/lsp/lsp-client.ts","../src/lsp/server-config.ts","../src/lsp/lsp-wrapper.ts","../src/lsp/definition.ts","../src/lsp/references.ts","../src/lsp/symbols.ts","../src/lsp/diagnostics.ts","../src/lsp/rename.ts","../src/session/session-manager.ts","../src/skill/skill.ts","../src/memory/memory-record.ts","../src/memory/memory-read.ts","../src/memory/memory-archive.ts","../src/scheduling/create.ts","../src/scheduling/list.ts","../src/scheduling/delete.ts","../src/scheduling/run.ts","../src/scheduling/cancel.ts","../src/tool-nodes.ts","../src/register-builtin.ts","../src/tool-registry.ts","../src/orchestration/constants.ts","../src/shell/sandbox/noop.ts","../src/shell/sandbox/macos-seatbelt.ts","../src/shell/sandbox/bubblewrap.ts","../src/shell/sandbox/platform.ts"],"sourcesContent":["import { z } from 'zod'\n\nimport type { AgentTool, GrillAnswer, GrillQuestion, ToolResult } from '@x-otto/interchange'\n\nexport type SessionGrill = (\n sessionId: string,\n question: Omit<GrillQuestion, 'id'>,\n) => Promise<GrillAnswer>\n\nconst GrillOptionSchema = z.object({\n label: z.string(),\n rationale: z.string().describe('选这项的含义/利弊(plain language)'),\n recommended: z.boolean().optional(),\n preview: z.string().optional().describe('两段具体产物并排对比'),\n})\n\nconst GrillMeArgsSchema = z.object({\n header: z.string().max(24).describe('短主题标签(≤24 字),便于 UI/日志'),\n question: z.string().describe('一个清晰具体的关键问题'),\n background: z.string().describe('详细背景与前因后果(grill 纪律:必填)'),\n recommendation: z.string().describe('推荐答案 + 理由(必填);无真人应答时被自动采纳'),\n options: z.array(GrillOptionSchema).optional(),\n allowFreeform: z.boolean().optional().default(true),\n multiSelect: z.boolean().optional().default(false),\n evidence: z.array(z.string()).optional().describe('引用的本地证据/社区方案'),\n})\n\ntype GrillMeArgs = z.infer<typeof GrillMeArgsSchema>\n\nexport function createGrillMe(_projectRoot: string, grill?: SessionGrill): AgentTool<GrillMeArgs> {\n return {\n name: 'grill_me',\n description:\n 'Ask the human ONE key decision with detailed background and a required recommendation. ' +\n 'Advisory only — never authorizes side effects (those still pass permission/approval). ' +\n 'Use when a single fork genuinely needs a human steer; otherwise decide yourself.',\n parameters: GrillMeArgsSchema,\n readonly: false,\n\n async execute({ params, agentName, sessionId }): Promise<ToolResult> {\n if (!grill || !sessionId) {\n return {\n content: [\n {\n type: 'text',\n text: 'grill_me is not available in this context. If you need clarification, return your open question in your result so the lead/main agent can ask the human.',\n },\n ],\n isError: true,\n errorKind: 'runtime',\n }\n }\n\n const answer = await grill(sessionId, {\n header: params.header,\n question: params.question,\n background: params.background,\n recommendation: params.recommendation,\n options: params.options,\n allowFreeform: params.allowFreeform,\n multiSelect: params.multiSelect,\n evidence: params.evidence,\n askedBy: agentName ?? 'agent',\n })\n\n const prefix = answer.resolvedBy === 'auto_recommendation' ? '(无真人应答,已采纳推荐)' : ''\n return {\n content: [{ type: 'text', text: `${prefix}${answer.answer}` }],\n details: {\n resolvedBy: answer.resolvedBy,\n acceptedRecommendation: answer.acceptedRecommendation,\n selectedOptionLabels: answer.selectedOptionLabels,\n freeformText: answer.freeformText,\n },\n }\n },\n }\n}\n","import { normalizeEnv } from '@x-otto/env'\n\n/**\n * 工具域输出/超时调优常量(RFC-074 M8-06a / R-ENVCONST)。\n *\n * 从 @x-otto/env 的 god-constants 桶迁回 tools——域调优常量各归其位,env 只保留跨切面的环境/\n * 路径/全局配置。这些值的唯一消费方就是 tools 包自身(fs/search/shell/web/binary)。\n * 仍用 env 的 `normalizeEnv` 解析(env 的通用 helper 是真跨切面,留在 env)。\n */\nexport const TOOLS_MAX_OUTPUT_LINES = normalizeEnv(process.env['TOOLS_MAX_OUTPUT_LINES'], 2000)\nexport const TOOLS_MAX_OUTPUT_BYTES = normalizeEnv(process.env['TOOLS_MAX_OUTPUT_BYTES'], 60 * 1024)\nexport const TOOLS_EXECUTE_TIMEOUT_MS = normalizeEnv(process.env['TOOLS_EXECUTE_TIMEOUT_MS'], 30_000)\nexport const TOOLS_BINARY_DOWNLOAD_TIMEOUT_MS = normalizeEnv(\n process.env['TOOLS_BINARY_DOWNLOAD_TIMEOUT_MS'],\n 1_200_000,\n)\n\n/** write 工具单次写入内容字节上限(默认 10MB)。硬编码审计 P1:与 TOOLS_MAX_OUTPUT_BYTES 同域未迁移,补 env 覆盖。 */\nexport const TOOLS_MAX_WRITE_BYTES = normalizeEnv(\n process.env['TOOLS_MAX_WRITE_BYTES'],\n 10 * 1024 * 1024,\n)\n\n/** web_fetch 响应体字节上限(默认 5MB,Content-Length 预检 + 流式读取双守卫)。硬编码审计 P1。 */\nexport const TOOLS_MAX_FETCH_BYTES = normalizeEnv(\n process.env['TOOLS_MAX_FETCH_BYTES'],\n 5 * 1024 * 1024,\n)\n\n/**\n * 第三方二进制工具的 `--version` 探测超时(默认 5s)。硬编码审计 P1:同文件的下载超时\n * (TOOLS_BINARY_DOWNLOAD_TIMEOUT_MS)已 env 化,此探测超时是漏网的裸字面量——慢/高负载\n * 机器上 5s 探测可能误判工具不可用,补 env 覆盖。\n */\nexport const TOOLS_BINARY_PROBE_TIMEOUT_MS = normalizeEnv(\n process.env['TOOLS_BINARY_PROBE_TIMEOUT_MS'],\n 5_000,\n)\n","import type { ToolResult } from '@x-otto/interchange'\n\n/** 工具错误类别(产生处定型)——派生自 ToolResult.errorKind 单一真源,不另抄 union。 */\nexport type ToolErrorKind = NonNullable<ToolResult['errorKind']>\n\n/**\n * 产生处定型的工具错误(RFC-074 R-ERRKIND):在 `throw` 处附带 `errorKind`,\n * agent tool-executor 的 catch 据此(鸭子类型读取)写入 `ToolResult.errorKind`,\n * 取代把一切 throw 盲目归类为 `'runtime'`。fs/search 的 not_found/validation/io/aborted\n * 等语义不再被抹平成 runtime。未用本 helper 的 throw 仍回退 `'runtime'`(向后兼容)。\n */\nexport function toolError(message: string, errorKind: ToolErrorKind): Error {\n return Object.assign(new Error(message), { errorKind })\n}\n","import os from 'node:os'\nimport { invariant } from '@x-otto/shared'\nimport {\n createWriteStream,\n existsSync,\n mkdirSync,\n readdirSync,\n statSync,\n copyFileSync,\n rmSync,\n renameSync,\n chmodSync,\n openSync,\n closeSync,\n} from 'node:fs'\nimport { readFile } from 'node:fs/promises'\nimport { createHash, randomUUID } from 'node:crypto'\nimport { join, resolve } from 'node:path'\nimport { Readable } from 'node:stream'\nimport { pipeline } from 'node:stream/promises'\nimport { setTimeout as delay } from 'node:timers/promises'\nimport { spawn, type SpawnOptionsWithoutStdio } from 'node:child_process'\nimport type { ProcessTracker } from '@x-otto/interchange'\nimport { getThirdPartyToolDir, getThirdPartyToolBinaryDir, createLogger } from '@x-otto/shared'\nimport { SETTING_OFFLINE_MODE_ENABLED, OTTO_USER_AGENT } from '@x-otto/env'\nimport { TOOLS_BINARY_DOWNLOAD_TIMEOUT_MS, TOOLS_BINARY_PROBE_TIMEOUT_MS } from '../limits'\nimport { toolError } from '../tool-error'\n\nconst logger = createLogger('@x-otto/tools:binary')\n\nconst BINARY_KILL_GRACE_MS = 2_000\nconst BINARY_STDERR_MAX_BYTES = 256 * 1024\n\nexport interface BinaryToolExecutionOptions extends SpawnOptionsWithoutStdio {\n /**\n * stdout 累计字节上限(M6-06,防无界缓冲/OOM)。超过即停止收集 + 终止子进程,\n * 结果标记 `truncated`。缺省不限(向后兼容)。\n */\n maxBuffer?: number\n}\n\nexport interface BinaryToolExecutionResult {\n stdout: string\n stderr: string\n exitCode: number | null\n /** stdout 触达 maxBuffer 被截断(M6-06)。 */\n truncated?: boolean\n}\n\nexport interface BinaryTool {\n name: string\n version: string\n execute(args: string[], options?: BinaryToolExecutionOptions): Promise<BinaryToolExecutionResult>\n}\n\nasync function tryExecute(toolPath: string, args: string[] = ['--version']): Promise<boolean> {\n try {\n const result = await asyncSpawn(toolPath, args, { timeout: TOOLS_BINARY_PROBE_TIMEOUT_MS })\n return result.status === 0\n } catch {\n return false\n }\n}\n\n/** spawn 的 async 包装——不阻塞事件循环。 */\nfunction asyncSpawn(\n command: string,\n args: string[],\n opts?: { timeout?: number },\n): Promise<{ status: number | null; stderr: string }> {\n return new Promise((resolve, reject) => {\n const child = spawn(command, args, { stdio: ['ignore', 'ignore', 'pipe'] })\n let stderr = ''\n child.stderr?.on('data', (d: Buffer) => { stderr += d.toString() })\n if (opts?.timeout) setTimeout(() => { child.kill(); reject(new Error('timeout')) }, opts.timeout)\n child.on('close', (status) => resolve({ status, stderr }))\n child.on('error', reject)\n })\n}\n\nasync function tryResolveExecutablePath(\n toolName: string,\n target: string = toolName,\n): Promise<string | null> {\n const ext = os.platform() === 'win32' ? '.exe' : ''\n const toolPath = getThirdPartyToolBinaryDir(target, toolName) + ext\n\n try {\n if (existsSync(toolPath)) {\n return toolPath\n }\n } catch {\n logger.debug(`Error checking existence of ${toolPath}, will try PATH lookup: %s`, toolPath)\n }\n\n if (await tryExecute(toolName)) {\n return toolName\n }\n\n return null\n}\n\nconst DOWNLOAD_LOCK_STALE_MS = 5 * 60_000\nconst DOWNLOAD_LOCK_POLL_MS = 200\n\ninterface DownloadSpec {\n name: string\n cacheDir: string\n url: string\n checksum?: string\n}\n\nexport async function verifyChecksum(\n archivePath: string,\n expected: string | undefined,\n url: string,\n) {\n if (!expected) {\n logger.warn('No checksum configured for %s — integrity not verified', url)\n return\n }\n\n const hash = createHash('sha256')\n hash.update(await readFile(archivePath))\n const actual = hash.digest('hex')\n\n if (actual.toLowerCase() !== expected.toLowerCase()) {\n throw toolError(`Checksum mismatch for ${url}: expected ${expected}, got ${actual}`, 'validation')\n }\n}\n\nexport async function extractArchive(url: string, archivePath: string, extractDir: string) {\n const run = async (command: string, args: string[]) => {\n try {\n const { status, stderr } = await asyncSpawn(command, args)\n if (status !== 0) {\n throw new Error(stderr?.trim() || `exit code ${status}`)\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n throw toolError(`Failed to extract ${url}: ${msg}`, 'io')\n }\n }\n\n if (url.endsWith('.tar.gz') || url.endsWith('.tgz')) {\n await run('tar', ['xzf', archivePath, '-C', extractDir])\n return\n }\n\n if (url.endsWith('.zip')) {\n if (os.platform() === 'win32') {\n await run('powershell', [\n '-NoProfile',\n '-Command',\n `Expand-Archive -LiteralPath '${archivePath}' -DestinationPath '${extractDir}' -Force`,\n ])\n } else {\n await run('unzip', ['-o', '-q', archivePath, '-d', extractDir])\n }\n return\n }\n\n throw toolError(`Unsupported archive format: ${url}`, 'validation')\n}\n\nexport async function acquireDownloadLock(lockPath: string, binaryPath: string): Promise<boolean> {\n const deadline = Date.now() + TOOLS_BINARY_DOWNLOAD_TIMEOUT_MS\n\n for (;;) {\n try {\n closeSync(openSync(lockPath, 'wx'))\n return true\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw error\n }\n }\n\n try {\n if (Date.now() - statSync(lockPath).mtimeMs > DOWNLOAD_LOCK_STALE_MS) {\n const claimPath = `${lockPath}.stale-${randomUUID()}`\n try {\n renameSync(lockPath, claimPath)\n rmSync(claimPath, { force: true })\n } catch {}\n continue\n }\n } catch {\n continue\n }\n\n if (existsSync(binaryPath)) {\n return false\n }\n\n if (Date.now() > deadline) {\n return false\n }\n\n await delay(DOWNLOAD_LOCK_POLL_MS)\n }\n}\n\nasync function tryDownloadAndExtract(spec: DownloadSpec): Promise<Error | undefined> {\n const { name, cacheDir, url, checksum } = spec\n const platform = os.platform()\n const toolsDir = getThirdPartyToolDir()\n\n const filename = name + (platform === 'win32' ? '.exe' : '')\n const binaryDir = resolve(toolsDir, cacheDir)\n const binaryPath = resolve(binaryDir, filename)\n const lockPath = resolve(toolsDir, `${cacheDir}.lock`)\n\n const token = randomUUID()\n const archivePath = resolve(toolsDir, `${cacheDir}-${token}.download`)\n const extractDir = resolve(toolsDir, `${cacheDir}-extract-${token}`)\n\n mkdirSync(toolsDir, { recursive: true })\n\n const owned = await acquireDownloadLock(lockPath, binaryPath)\n if (!owned) {\n return\n }\n\n try {\n if (existsSync(binaryPath)) {\n return\n }\n\n const response = await fetch(url, {\n headers: { 'User-Agent': OTTO_USER_AGENT },\n signal: AbortSignal.timeout(TOOLS_BINARY_DOWNLOAD_TIMEOUT_MS),\n })\n\n if (!response.ok || !response.body) {\n throw toolError(`Failed to download: ${response.status}`, 'network')\n }\n\n const fileStream = createWriteStream(archivePath)\n await pipeline(Readable.fromWeb(response.body), fileStream)\n\n await verifyChecksum(archivePath, checksum, url)\n\n mkdirSync(binaryDir, { recursive: true })\n mkdirSync(extractDir, { recursive: true })\n\n await extractArchive(url, archivePath, extractDir)\n\n const found = findBinaryRecursively(extractDir, filename)\n if (!found) {\n throw toolError(`Binary ${filename} not found in archive`, 'not_found')\n }\n\n copyFileSync(found, binaryPath)\n\n if (platform !== 'win32') {\n chmodSync(binaryPath, 0o755)\n }\n } catch (error) {\n logger.warn(error)\n return error instanceof Error ? error : new Error(String(error))\n } finally {\n rmSync(archivePath, { force: true })\n rmSync(extractDir, { recursive: true, force: true })\n rmSync(lockPath, { force: true })\n }\n return undefined\n}\n\nfunction findBinaryRecursively(rootDir: string, binaryFileName: string) {\n const stack: string[] = [rootDir]\n\n while (stack.length > 0) {\n const currentDir = stack.pop() as string\n\n let entries: string[]\n try {\n entries = readdirSync(currentDir, { encoding: 'utf-8' })\n } catch {\n continue\n }\n\n for (const name of entries) {\n const fullPath = join(currentDir, name)\n\n try {\n const st = statSync(fullPath)\n if (st.isDirectory()) {\n stack.push(fullPath)\n } else if (st.isFile() && name === binaryFileName) {\n return fullPath\n }\n } catch {\n continue\n }\n }\n }\n\n return undefined\n}\n\nexport abstract class BinaryToolExecutor implements BinaryTool {\n public abstract name: string\n public abstract version: string\n\n protected get cacheDir(): string {\n return `${this.name}-${this.version}`\n }\n\n protected projectRoot: string\n private downloadTask: Promise<void> | null = null\n private lastDownloadError: Error | undefined\n\n /** RFC-095:进程追踪器(由组合根注入,替代 globalProcessRuntime 全局单例)。 */\n protected processTracker?: ProcessTracker\n\n constructor(projectRoot: string, processTracker?: ProcessTracker) {\n this.projectRoot = projectRoot\n this.processTracker = processTracker\n }\n\n protected abstract resolveUrl(): string | undefined\n\n protected resolveChecksum(): string | undefined {\n return undefined\n }\n\n async ensure(): Promise<string> {\n const existing = await tryResolveExecutablePath(this.name, this.cacheDir)\n if (existing) {\n return existing\n }\n\n if (SETTING_OFFLINE_MODE_ENABLED) {\n throw toolError(`${this.name} not found and offline mode is enabled`, 'not_found')\n }\n\n if (!this.downloadTask) {\n this.downloadTask = this.download().finally(() => {\n this.downloadTask = null\n })\n }\n\n await this.downloadTask\n\n const resolved = await tryResolveExecutablePath(this.name, this.cacheDir)\n if (!resolved) {\n const cause = this.lastDownloadError\n const err = toolError(\n `${this.name}: download failed${cause ? ` — ${cause.message}` : ' (binary not found)'}`,\n 'not_found',\n )\n if (cause) {\n err.cause = cause\n }\n throw err\n }\n\n return resolved\n }\n\n protected async download(): Promise<void> {\n const url = this.resolveUrl()\n invariant(url, `No download URL for ${this.name}`)\n this.lastDownloadError = await tryDownloadAndExtract({\n name: this.name,\n cacheDir: this.cacheDir,\n url,\n checksum: this.resolveChecksum(),\n })\n }\n\n async execute(\n args: string[],\n options?: BinaryToolExecutionOptions,\n ): Promise<BinaryToolExecutionResult> {\n const executablePath = await this.ensure()\n const maxBuffer = options?.maxBuffer\n\n return new Promise((resolve, reject) => {\n const ps = spawn(executablePath, args, {\n cwd: options?.cwd,\n env: options?.env,\n timeout: options?.timeout,\n signal: options?.signal,\n })\n // RFC-095: 纳入 ProcessTracker 统一追踪(短命 tool spawn,退出即自动移除)。\n // 优先用注入的 processTracker;fallback no-op(非 CLI/非工具装配路径)。\n this.processTracker?.registerChild(\n {\n command: executablePath,\n args,\n owner: { type: 'agent-tool', id: 'binary' },\n category: 'tool',\n lifecycle: 'evictable',\n cwd: typeof options?.cwd === 'string' ? options.cwd : undefined,\n },\n ps,\n )\n\n const stdout: Buffer[] = []\n const stderr: Buffer[] = []\n let stdoutBytes = 0\n let stderrBytes = 0\n let truncated = false\n let killTimer: ReturnType<typeof setTimeout> | undefined\n\n const terminate = (): void => {\n try {\n ps.kill('SIGTERM')\n } catch {}\n killTimer = setTimeout(() => {\n try {\n ps.kill('SIGKILL')\n } catch {}\n }, BINARY_KILL_GRACE_MS)\n killTimer.unref?.()\n }\n\n ps.stdout.on('data', (data: Buffer) => {\n if (truncated) {\n return\n }\n if (maxBuffer !== undefined && stdoutBytes + data.byteLength > maxBuffer) {\n const remaining = maxBuffer - stdoutBytes\n if (remaining > 0) {\n stdout.push(data.subarray(0, remaining))\n stdoutBytes = maxBuffer\n }\n truncated = true\n terminate()\n return\n }\n stdout.push(data)\n stdoutBytes += data.byteLength\n })\n ps.stderr.on('data', (data: Buffer) => {\n if (stderrBytes >= BINARY_STDERR_MAX_BYTES) {\n return\n }\n const remaining = BINARY_STDERR_MAX_BYTES - stderrBytes\n if (data.byteLength > remaining) {\n stderr.push(data.subarray(0, remaining))\n stderrBytes = BINARY_STDERR_MAX_BYTES\n } else {\n stderr.push(data)\n stderrBytes += data.byteLength\n }\n })\n\n ps.on('error', (err) => {\n if (killTimer) {\n clearTimeout(killTimer)\n }\n reject(err)\n })\n ps.on('close', (code) => {\n if (killTimer) {\n clearTimeout(killTimer)\n }\n if (!truncated && code !== 0) {\n return reject(\n new Error(`Process exited with code ${code}: ${Buffer.concat(stderr).toString()}`),\n )\n }\n\n resolve({\n stdout: Buffer.concat(stdout).toString(),\n stderr: Buffer.concat(stderr).toString(),\n exitCode: truncated ? 0 : code,\n truncated,\n })\n })\n })\n }\n}\n\ntype ConfiguredBinaryExecute = (\n args: string[],\n options?: BinaryToolExecutionOptions,\n) => Promise<BinaryToolExecutionResult>\n\nexport class ConfiguredBinaryExecutor implements BinaryTool {\n public name: string\n public version: string\n\n protected executeHandler: ConfiguredBinaryExecute\n\n constructor(name: string, version: string, execute: ConfiguredBinaryExecute) {\n this.name = name\n this.version = version\n this.executeHandler = execute\n }\n\n async execute(\n args: string[],\n options?: BinaryToolExecutionOptions,\n ): Promise<BinaryToolExecutionResult> {\n return this.executeHandler(args, options)\n }\n}\n","import os from 'os'\nimport { BinaryToolExecutor, type BinaryTool } from './binary-executor'\nimport type { ProcessTracker } from '@x-otto/interchange'\n\nfunction resolveUrl(repository: string, version: string, asset: string): string {\n return `https://github.com/${repository}/releases/download/v${version}/${asset}`\n}\n\nconst CHECKSUMS: Record<string, string> = {\n 'fd-v10.4.2-aarch64-apple-darwin.tar.gz':\n '623dc0afc81b92e4d4606b380d7bc91916ba7b97814263e554d50923a39e480a',\n 'fd-v10.4.2-aarch64-unknown-linux-gnu.tar.gz':\n '6c51f7c5446b3338b1e401ff15dc194c590bb2fa64fd43ff3278300f073adec5',\n 'fd-v10.4.2-x86_64-unknown-linux-gnu.tar.gz':\n 'def59805cd14b5651b68990855f426ad087f3b96881296d963910431ba3143c8',\n 'fd-v10.4.2-aarch64-pc-windows-msvc.zip':\n '4f9110c2d5b33a7f760bfa5510f4c113d828109f7277d421b1053a9943c0fc92',\n 'fd-v10.4.2-x86_64-pc-windows-msvc.zip':\n 'b2816e506390a89941c63c9187d58a3cc10e9a55f2ef0685f9ea0eccaf7c98c8',\n}\n\nexport class FindExecutor extends BinaryToolExecutor {\n public readonly name = 'fd'\n public readonly version = '10.4.2'\n public readonly repository = 'sharkdp/fd'\n\n constructor(projectRoot: string, processTracker?: ProcessTracker) {\n super(projectRoot, processTracker)\n }\n\n private resolveAsset(): string {\n const platform = os.platform()\n const arch = os.arch()\n\n switch (platform) {\n case 'darwin':\n return arch === 'arm64'\n ? `fd-v${this.version}-aarch64-apple-darwin.tar.gz`\n : `fd-v${this.version}-x86_64-apple-darwin.tar.gz`\n case 'linux':\n return arch === 'arm64'\n ? `fd-v${this.version}-aarch64-unknown-linux-gnu.tar.gz`\n : `fd-v${this.version}-x86_64-unknown-linux-gnu.tar.gz`\n case 'win32':\n return arch === 'arm64'\n ? `fd-v${this.version}-aarch64-pc-windows-msvc.zip`\n : `fd-v${this.version}-x86_64-pc-windows-msvc.zip`\n }\n\n throw new Error(`Unsupported platform/architecture: ${platform}/${arch}`)\n }\n\n resolveUrl(): string | undefined {\n return resolveUrl(this.repository, this.version, this.resolveAsset())\n }\n\n protected override resolveChecksum(): string | undefined {\n return CHECKSUMS[this.resolveAsset()]\n }\n}\n\nexport const createFindExecutor = (projectRoot: string, processTracker?: ProcessTracker): BinaryTool => {\n return new FindExecutor(projectRoot, processTracker)\n}\n","import os from 'os'\nimport { BinaryToolExecutor, type BinaryTool } from './binary-executor'\nimport type { ProcessTracker } from '@x-otto/interchange'\n\nfunction resolveUrl(repository: string, version: string, asset: string): string {\n return `https://github.com/${repository}/releases/download/${version}/${asset}`\n}\n\nconst CHECKSUMS: Record<string, string> = {\n 'ripgrep-15.1.0-aarch64-apple-darwin.tar.gz':\n '378e973289176ca0c6054054ee7f631a065874a352bf43f0fa60ef079b6ba715',\n 'ripgrep-15.1.0-x86_64-apple-darwin.tar.gz':\n '64811cb24e77cac3057d6c40b63ac9becf9082eedd54ca411b475b755d334882',\n 'ripgrep-15.1.0-aarch64-unknown-linux-gnu.tar.gz':\n '2b661c6ef508e902f388e9098d9c4c5aca72c87b55922d94abdba830b4dc885e',\n 'ripgrep-15.1.0-x86_64-unknown-linux-musl.tar.gz':\n '1c9297be4a084eea7ecaedf93eb03d058d6faae29bbc57ecdaf5063921491599',\n 'ripgrep-15.1.0-aarch64-pc-windows-msvc.zip':\n '00d931fb5237c9696ca49308818edb76d8eb6fc132761cb2a1bd616b2df02f8e',\n 'ripgrep-15.1.0-x86_64-pc-windows-msvc.zip':\n '124510b94b6baa3380d051fdf4650eaa80a302c876d611e9dba0b2e18d87493a',\n}\n\nexport class RipgrepExecutor extends BinaryToolExecutor {\n public readonly name = 'rg'\n public readonly repository = 'BurntSushi/ripgrep'\n public readonly version = '15.1.0'\n\n constructor(projectRoot: string, processTracker?: ProcessTracker) {\n super(projectRoot, processTracker)\n }\n\n private resolveAsset(): string {\n const platform = os.platform()\n const arch = os.arch()\n\n switch (platform) {\n case 'darwin':\n return arch === 'arm64'\n ? `ripgrep-${this.version}-aarch64-apple-darwin.tar.gz`\n : `ripgrep-${this.version}-x86_64-apple-darwin.tar.gz`\n case 'linux':\n return arch === 'arm64'\n ? `ripgrep-${this.version}-aarch64-unknown-linux-gnu.tar.gz`\n : `ripgrep-${this.version}-x86_64-unknown-linux-musl.tar.gz`\n case 'win32':\n return arch === 'arm64'\n ? `ripgrep-${this.version}-aarch64-pc-windows-msvc.zip`\n : `ripgrep-${this.version}-x86_64-pc-windows-msvc.zip`\n }\n\n throw new Error(`Unsupported platform/architecture: ${platform}/${arch}`)\n }\n\n resolveUrl(): string | undefined {\n return resolveUrl(this.repository, this.version, this.resolveAsset())\n }\n\n protected override resolveChecksum(): string | undefined {\n return CHECKSUMS[this.resolveAsset()]\n }\n}\n\nexport const createRipgrepExecutor = (projectRoot: string, processTracker?: ProcessTracker): BinaryTool => {\n return new RipgrepExecutor(projectRoot, processTracker)\n}\n","import { createLogger } from '@x-otto/shared'\nimport { createFindExecutor } from './find'\nimport { createRipgrepExecutor } from './ripgrep'\nimport { BinaryToolExecutor, type BinaryTool } from './binary-executor'\nimport type { ProcessTracker } from '@x-otto/interchange'\n\nconst logger = createLogger('@x-otto/tools:binary-executor-registry')\n\nexport class BinaryToolExecutorRegistry {\n private binaries: Map<string, BinaryTool> = new Map()\n\n has(tool: string): boolean {\n return this.binaries.has(tool)\n }\n\n get(tool: string): BinaryTool | undefined {\n return this.binaries.get(tool)\n }\n\n register(tool: BinaryTool): void {\n if (!this.binaries.has(tool.name)) {\n this.binaries.set(tool.name, tool)\n } else {\n logger.warn(`Tool ${tool.name} is already registered, skipping`)\n }\n }\n\n unregister(tool: string): void {\n if (this.binaries.has(tool)) {\n this.binaries.delete(tool)\n } else {\n logger.warn(`Tool ${tool} is not registered, skipping`)\n }\n }\n\n async ensureAll(): Promise<void> {\n for (const tool of this.binaries.values()) {\n if (tool instanceof BinaryToolExecutor) {\n await tool.ensure()\n }\n }\n }\n\n async ensure(tool: string): Promise<BinaryTool> {\n const binary = this.binaries.get(tool)\n if (!binary) {\n throw new Error(`Tool ${tool} not found in registry`)\n }\n\n if (binary instanceof BinaryToolExecutor) {\n await binary.ensure()\n }\n\n return binary\n }\n\n dispose(): void {\n this.binaries.clear()\n }\n}\n\nexport const createBinaryToolExecutorRegistry = (\n projectRoot: string,\n processTracker?: ProcessTracker,\n): BinaryToolExecutorRegistry => {\n const registry = new BinaryToolExecutorRegistry()\n\n registry.register(createFindExecutor(projectRoot, processTracker))\n registry.register(createRipgrepExecutor(projectRoot, processTracker))\n\n return registry\n}\n","/**\n * execution-backend.ts —— RFC-297:bash 工具可插拔命令执行后端。\n *\n * `ExecutionBackend` 抽象\"在哪跑一条命令\",与 `SandboxStrategy`(\"如何隔离一条命令\",\n * 见 `./sandbox/types.ts`)是不同关注点、不同层:`spawn()` 先过沙箱包装,再交后端执行\n * (见 RFC-297 D3)。\n *\n * 接口刻意不暴露 Node 特有语义(真实 OS pid、进程组信号、`taskkill`)——\"如何终止一条\n * 正在执行的命令\"完全下沉到后端实现内部:调用方只负责传入 `AbortSignal`,后端自行决定\n * 怎么真正杀掉(本地后端走 SIGTERM→SIGKILL 分级 + Windows taskkill;未来的容器后端走\n * 容器 kill API)。上层不感知 pid/信号细节(见 RFC-297 D1)。\n */\n\nimport type { StreamSource } from './process'\n\nexport interface ExecutionBackendOptions {\n cwd?: string\n env?: Record<string, string>\n /** 毫秒。超时后后端应自行终止命令并在结果里标 `timedOut: true`。 */\n timeout: number\n /** 触发时后端应尽快终止命令(不保证立即,语义与本地 SIGTERM→SIGKILL 分级一致)。 */\n signal?: AbortSignal\n}\n\nexport interface ExecutionResult {\n /** stdout+stderr 按到达顺序交错合并的全量字节流(与现有 `SpawnExecuteResult.chunks` 语义一致)。 */\n chunks: Buffer\n stdout: Buffer\n stderr: Buffer\n exitCode: number\n timedOut: boolean\n}\n\nexport type ExecutionProgressCallback = (chunk: Buffer, source: StreamSource) => void\n\nexport interface ExecutionBackend {\n /** 后端标识名(日志/调试/配置文件选择用)。 */\n readonly name: string\n\n /**\n * 执行一条命令,返回聚合后的 stdout/stderr/exitCode。\n * 超时应 reject(与现有 `process.spawn()` 行为一致:`Process timed out after ${timeout}ms`);\n * 非超时失败(如 spawn 本身失败)应 reject 底层错误;正常退出(含非零 exitCode)应 resolve。\n */\n execute(\n command: string,\n args: string[],\n options: ExecutionBackendOptions,\n onProgress?: ExecutionProgressCallback,\n ): Promise<ExecutionResult>\n}\n\n/**\n * 晚绑定 provider(M16a `SandboxProvider` 同款范式,见 `bash.ts` 对 `sandbox` 的解析方式)。\n * 无参数——是否用某个执行后端通常是会话/全局级配置,不存在\"按命令名选后端\"的真实场景\n * (对比 `SandboxProvider` 接受 `{command}` 上下文用于命令级 bypass 决策,见 RFC-297 D1a\n * 自查补充说明)。未来若出现命令级选择后端的真实需求,再按需给 provider 加参数。\n */\nexport type ExecutionBackendProvider = () => ExecutionBackend\n\n/** 从 `ExecutionBackend | ExecutionBackendProvider | undefined` 解析出当前生效后端。 */\nexport function resolveExecutionBackend(\n backend: ExecutionBackend | ExecutionBackendProvider | undefined,\n): ExecutionBackend | undefined {\n return typeof backend === 'function' ? backend() : backend\n}\n","/**\n * local-execution-backend.ts —— RFC-297 T2:默认 `ExecutionBackend` 实现。\n *\n * 本文件是 `process.ts` 迁移前 `spawn()` 函数体的**原样搬移**(纯重构,零逻辑改动,\n * 见 RFC-297 D2/重要事项规则1)——白名单 env、超时/信号处理、`signalGroup` SIGTERM→SIGKILL\n * 分级终止、Windows `taskkill` 分支全部逐行保留。`process.ts` 的 `spawn()` 改为委派本类。\n */\n\nimport { spawn as childProcessSpawn } from 'node:child_process'\n\nimport { buildAllowedEnv } from '@x-otto/shared'\n\nimport type {\n ExecutionBackend,\n ExecutionBackendOptions,\n ExecutionProgressCallback,\n ExecutionResult,\n} from './execution-backend'\n\nconst KILL_GRACE_MS = 2_000\n\nconst createExecutionResult = (): ExecutionResult => ({\n chunks: Buffer.alloc(0),\n stdout: Buffer.alloc(0),\n stderr: Buffer.alloc(0),\n exitCode: -1,\n timedOut: false,\n})\n\nconst signalGroup = (pid: number, signal: NodeJS.Signals): void => {\n try {\n process.kill(-pid, signal)\n } catch {\n try {\n process.kill(pid, signal)\n } catch {}\n }\n}\n\nexport class LocalExecutionBackend implements ExecutionBackend {\n readonly name = 'local'\n\n execute(\n command: string,\n args: string[],\n options: ExecutionBackendOptions,\n onProgress?: ExecutionProgressCallback,\n ): Promise<ExecutionResult> {\n const { cwd, env, timeout, signal } = options\n\n return new Promise<ExecutionResult>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new Error('Process execution aborted before start'))\n return\n }\n\n const result: ExecutionResult = createExecutionResult()\n const stdoutChunks: Buffer[] = []\n const stderrChunks: Buffer[] = []\n const allChunks: Buffer[] = []\n\n let graceTimer: ReturnType<typeof setTimeout> | undefined\n\n const terminate = (): void => {\n const pid = child.pid\n if (pid === undefined) {\n return\n }\n\n if (process.platform === 'win32') {\n childProcessSpawn('taskkill', ['/F', '/T', '/PID', `${pid}`], {\n stdio: 'ignore',\n detached: true,\n })\n return\n }\n\n signalGroup(pid, 'SIGTERM')\n graceTimer = setTimeout(() => signalGroup(pid, 'SIGKILL'), KILL_GRACE_MS)\n }\n\n const timer = setTimeout(() => {\n result.timedOut = true\n terminate()\n }, timeout)\n\n const onAbort = () => terminate()\n signal?.addEventListener('abort', onAbort, { once: true })\n\n const cleanup = () => {\n clearTimeout(timer)\n if (graceTimer) {\n clearTimeout(graceTimer)\n }\n signal?.removeEventListener('abort', onAbort)\n }\n\n const child = childProcessSpawn(command, args, {\n cwd,\n // 白名单 env,不透传父进程 API key;调用方 env override 仍透传\n env: buildAllowedEnv(env),\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: process.platform !== 'win32',\n })\n\n child.stdout?.on('data', (chunk: Buffer) => {\n stdoutChunks.push(chunk)\n allChunks.push(chunk)\n onProgress?.(chunk, 'stdout')\n })\n\n child.stderr?.on('data', (chunk: Buffer) => {\n stderrChunks.push(chunk)\n allChunks.push(chunk)\n onProgress?.(chunk, 'stderr')\n })\n\n child.on('error', (error) => {\n cleanup()\n reject(error)\n })\n\n child.on('close', (code) => {\n result.exitCode = code ?? -1\n result.stdout = Buffer.concat(stdoutChunks)\n result.stderr = Buffer.concat(stderrChunks)\n result.chunks = Buffer.concat(allChunks)\n cleanup()\n\n if (result.timedOut) {\n reject(new Error(`Process timed out after ${timeout}ms`))\n } else {\n resolve(result)\n }\n })\n })\n }\n}\n\n/** 模块级默认单例(无自定义后端时的回退,见 `process.ts spawn()`)。 */\nexport const defaultLocalExecutionBackend = new LocalExecutionBackend()\n","import { spawn as childProcessSpawn } from 'node:child_process'\nimport type { ChildProcess } from 'node:child_process'\nimport { appendFileSync, closeSync, openSync } from 'node:fs'\n\nimport { buildAllowedEnv } from '@x-otto/shared'\n\nimport { resolveExecutionBackend } from './execution-backend'\nimport { defaultLocalExecutionBackend } from './local-execution-backend'\n\nimport type { ExecutionBackend, ExecutionBackendProvider } from './execution-backend'\nimport type { SandboxStrategy } from './sandbox/types'\n\nexport type StreamSource = 'stdout' | 'stderr'\n\ntype ProgressCallback = (chunk: Buffer, source: StreamSource) => void\n\nexport interface SpawnExecuteOptions {\n timeout: number\n cwd?: string\n signal?: AbortSignal\n env?: Record<string, string>\n onProgress?: ProgressCallback\n /** OS 级沙箱策略。未注入时直接 spawn(保持兼容行为)。 */\n sandbox?: SandboxStrategy\n /**\n * 项目根目录(沙箱可写域锚点)。\n * 与 cwd 是不同概念:cwd 是命令工作目录(bash targetDir,可为项目子目录),\n * projectRoot 是沙箱允许写入的根。未传时回退 cwd(兼容直接调 spawn 的用法)。\n */\n projectRoot?: string\n /**\n * RFC-297:可插拔命令执行后端。未提供时回退 `LocalExecutionBackend`(即本函数迁移前的\n * 原生 `node:child_process.spawn` 行为,零回归)。可传实例或晚绑定 provider。\n */\n backend?: ExecutionBackend | ExecutionBackendProvider\n}\n\nexport interface SpawnExecuteResult {\n chunks: Buffer\n stdout: Buffer\n stderr: Buffer\n exitCode: number\n timedOut: boolean\n}\n\nexport interface SpawnBackgroundOptions {\n cwd: string\n env?: Record<string, string>\n sandbox?: SandboxStrategy\n projectRoot?: string\n /** 子进程 stdout+stderr 直接重定向到此文件(追加)。 */\n logPath: string\n /**\n * RFC-223:可选的输出 chunk 回调(原始字节,未做行切分——行缓冲/识别逻辑由调用方\n * 负责,如 `bash.ts` 的 `runBackground`,职责分离:本函数只管转发字节)。\n * 提供该回调时,实现从 fd 直接重定向切换为 pipe 模式(父进程读取子进程输出并手动\n * 写回 `logPath`,保持落盘内容与此前完全一致);不提供时维持原有 fd 直写路径\n * (零行为变化,零性能开销)。\n */\n onOutputChunk?: ProgressCallback\n}\n\nexport interface SpawnBackgroundResult {\n child: ChildProcess\n pid: number\n}\n\n/**\n * 启动后台进程并**立即返回**(不等退出)。\n * - 沙箱包装与前台 spawn 一致(detached 下包装器即组长,kill(-pgid) 仍级联内层)。\n * - 默认路径:输出经 fd 直写 logPath(无父进程流背压),detached + unref 脱离事件循环。\n * - RFC-223:提供 `onOutputChunk` 时改走 pipe 模式(父进程读取 stdout/stderr 并手动\n * append 回 logPath,同时把原始 chunk 转发给回调)——落盘行为不变,只是新增了一条\n * \"顺便读一下字节\"的旁路,不影响现有零回调调用方的性能特征。\n * - 进程组终止由 ProcessRegistry 负责(本函数不挂 timeout/abort)。\n */\nexport function spawnBackground(\n command: string,\n args: string[],\n options: SpawnBackgroundOptions,\n): SpawnBackgroundResult {\n let cmd = command\n let cmdArgs = args\n let cwd = options.cwd\n let env = options.env\n\n if (options.sandbox) {\n const projectRoot = options.projectRoot ?? cwd\n const wrapped = options.sandbox.wrap({\n command: cmd,\n args: cmdArgs,\n options: { timeout: 0, cwd, env, projectRoot },\n projectRoot,\n })\n cmd = wrapped.command\n cmdArgs = wrapped.args\n cwd = wrapped.options.cwd ?? cwd\n env = wrapped.options.env\n }\n\n const usesPipe = options.onOutputChunk !== undefined\n let child: ChildProcess\n let logFd: number | undefined\n\n if (usesPipe) {\n // pipe 模式:父进程持有 fd 手动 append(保持与直写模式相同的落盘语义),\n // 同时把 chunk 转发给识别回调。不 `.pause()` —— 保持流恒处于 flowing 模式,\n // 避免因识别回调耗时(纯正则,微秒级)导致子进程侧背压阻塞。\n logFd = openSync(options.logPath, 'a')\n child = childProcessSpawn(cmd, cmdArgs, {\n cwd,\n env: buildAllowedEnv(env),\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: process.platform !== 'win32',\n })\n const writeChunk = (chunk: Buffer, source: StreamSource): void => {\n if (logFd !== undefined) {\n try {\n appendFileSync(logFd, chunk)\n } catch {\n /* best-effort:写失败不影响进程本身运行,识别回调仍照常触发 */\n }\n }\n options.onOutputChunk?.(chunk, source)\n }\n child.stdout?.on('data', (chunk: Buffer) => writeChunk(chunk, 'stdout'))\n child.stderr?.on('data', (chunk: Buffer) => writeChunk(chunk, 'stderr'))\n child.once('exit', () => {\n if (logFd !== undefined) {\n try {\n closeSync(logFd)\n } catch {}\n logFd = undefined\n }\n })\n // 关键(本地 spike 实测确认):`stdio:'pipe'` 创建的流默认持有事件循环引用——\n // 即使 `child.unref()`,父进程也会因 stdout/stderr 流本身而挂起等待,破坏既有\n // \"detached 后台进程不阻塞宿主退出\"不变式(fd 直写模式无此问题,因为没有流对象)。\n // 用公开的 `stream.unref()` API 显式解除引用;解除后父进程可以正常退出/继续,\n // 子进程仍在后台运行且流仍正常触发 'data' 事件(父进程尚存活时,如 otto 交互式\n // 会话未退出期间)——已用脚本验证:父进程因其他 ref'd handle(如 stdin)存活时,\n // unref 的流照常完整接收数据;父进程退出后子进程独立存活写 logPath 不受影响。\n // Node 运行时给 child_process 的 pipe 流附加了 `.unref()`(同底层 socket handle),\n // 但 `@types/node` 的 `Readable` 公共类型未声明该方法——按本仓惯例用具名接口做\n // 双重断言而非 `as any`(见 AGENTS.md 类型转换约定)。\n ;(child.stdout as unknown as { unref?: () => void } | null)?.unref?.()\n ;(child.stderr as unknown as { unref?: () => void } | null)?.unref?.()\n } else {\n // 默认路径:零回调时维持原有 fd 直写(零变化,零额外开销)。\n const fd = openSync(options.logPath, 'a')\n try {\n child = childProcessSpawn(cmd, cmdArgs, {\n cwd,\n // 白名单 env,不透传父进程 API key;调用方 env override 仍透传\n env: buildAllowedEnv(env),\n stdio: ['ignore', fd, fd],\n detached: process.platform !== 'win32',\n })\n } finally {\n closeSync(fd)\n }\n }\n\n // detached/unref 后无人监听 'error':异步 spawn 失败(如 ENOENT)会成未捕获异常崩宿主。落到 logPath 留痕。\n child.once('error', (err) => {\n try {\n appendFileSync(options.logPath, `\\n[otto] background spawn error: ${err.message}\\n`)\n } catch {}\n })\n child.unref()\n const pid = child.pid\n if (pid === undefined) {\n throw new Error('Failed to spawn background process (no pid)')\n }\n return { child, pid }\n}\n\n/**\n * RFC-297:foreground spawn,经可插拔 `ExecutionBackend` 执行命令(默认 `LocalExecutionBackend`,\n * 即迁移前的原生 `node:child_process.spawn` 行为,零回归)。沙箱包装(若有)仍在本函数内、\n * 交给后端之前生效——沙箱管\"如何隔离一条命令\",后端管\"在哪跑一条命令\"(RFC-297 D3)。\n */\nexport function spawn(\n command: string,\n args: string[],\n options: SpawnExecuteOptions,\n): Promise<SpawnExecuteResult> {\n const sandbox = options.sandbox\n if (sandbox) {\n const projectRoot = options.projectRoot ?? options.cwd ?? process.cwd()\n const wrapped = sandbox.wrap({ command, args, options, projectRoot })\n command = wrapped.command\n args = wrapped.args\n options = wrapped.options\n }\n\n const { cwd, env, timeout, signal, onProgress } = options\n const backend = resolveExecutionBackend(options.backend) ?? defaultLocalExecutionBackend\n\n return backend.execute(command, args, { cwd, env, timeout, signal }, onProgress)\n}\n","import type { DetectedService, ServiceProtocol } from './background-types'\n\n/**\n * service-detector.ts — RFC-223:从委托后台进程的单行运行时输出识别服务信息。\n *\n * 纯函数(无 I/O),供 `process.ts`/`bash.ts` 在行到达时调用。识别失败(未命中任何\n * 内置模式)返回 `null`,调用方静默丢弃该行,不报错、不产生占位符号(RFC-223 重要\n * 事项规则 1)。\n *\n * 规则集(D3,覆盖高频场景,非穷举):\n * ① 完整 URL(`http(s)://`/`ws(s)://`)——最高优先级,直接给 protocol+port+url。\n * ② dev-server 惯用横幅前缀(Local:/Network:/Listening on/Server running at)+ URL——\n * 与①共用同一条 URL 正则,前缀词本身不影响识别结果,只是常见语境证据。\n * ③ 独立端口号(无 URL,如 \"listening on port 3000\")——退化为 protocol:'tcp'\n * (无法判断 http/ws 时的保守默认)。\n * ④ 协议由 URL scheme 决定:ws(s):// → 'ws',其余 URL → 'http'。\n */\n\n/** 完整 URL:scheme + host[:port] + 可选 path。scheme 决定 protocol(④)。\n * path 段排除常见尾随标点(`)`/`,`/`.`/`\"` 等),避免吞掉包裹 URL 的括号/引号\n * (如 \"... (http://0.0.0.0:8080/) ...\" 这类 python http.server 输出)。 */\nconst URL_RE = /\\b(https?|wss?):\\/\\/([-\\w.]+)(?::(\\d{2,5}))?(\\/[^\\s)\\],.\"']*)?/i\n\n/** 独立端口号退路(③):常见 \"listening on port N\" / \"running at :N\" / 裸 \"port N\" 等表达。 */\nconst PORT_ONLY_RE = /\\b(?:listening on\\s+(?:port\\s+)?|(?:running\\s+)?at\\s+:|on\\s+:|port[:\\s]+)(\\d{2,5})\\b/i\n\nfunction protocolFromScheme(scheme: string): ServiceProtocol {\n return /^wss?$/i.test(scheme) ? 'ws' : 'http'\n}\n\n/**\n * 从单行文本识别服务信息。命中①(完整 URL)优先于③(独立端口)——同一行两者都命中时\n * (如 \"Serving HTTP on :: port 8080 (http://[::]:8080/) ...\"),① 更精确故优先。\n */\nexport function detectServiceFromLine(line: string): DetectedService | null {\n const urlMatch = line.match(URL_RE)\n if (urlMatch) {\n const [full, scheme, , portStr] = urlMatch\n const port = portStr ? Number(portStr) : undefined\n return {\n url: full,\n port: port !== undefined && port > 0 && port <= 65535 ? port : undefined,\n protocol: protocolFromScheme(scheme!),\n }\n }\n\n const portMatch = line.match(PORT_ONLY_RE)\n if (portMatch) {\n const port = Number(portMatch[1])\n if (port > 0 && port <= 65535) {\n return { port, protocol: 'tcp' }\n }\n }\n\n return null\n}\n","import {\n createLogger,\n createProjectOverflowLogPath,\n createTempLoggerPath,\n formatBytes,\n sweepStaleProjectOverflowLogs,\n sweepStaleTempLogs,\n truncateTail,\n toTruncationMeta,\n} from '@x-otto/shared'\nimport { TOOLS_EXECUTE_TIMEOUT_MS, TOOLS_MAX_OUTPUT_BYTES, TOOLS_MAX_OUTPUT_LINES } from '../limits'\nimport { createWriteStream, mkdirSync, WriteStream } from 'node:fs'\nimport { dirname } from 'node:path'\nimport { spawn, spawnBackground } from './process'\nimport { z } from 'zod'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport type { ExecutionBackend, ExecutionBackendProvider } from './execution-backend'\nimport type { SandboxProvider, SandboxStrategy } from './sandbox/types'\nimport type { BackgroundProcessPort } from './background-types'\nimport { resolveExecutionBackend } from './execution-backend'\nimport { detectServiceFromLine } from './service-detector'\nimport { resolve } from 'node:path'\n\nconst BashArgsSchema = z.object({\n command: z.string().describe('Shell command to execute'),\n targetDir: z.string().optional().describe('Working directory (relative to project root)'),\n timeout: z.number().int().min(1000).optional().describe('Timeout (ms), default 30000'),\n run_in_background: z\n .boolean()\n .optional()\n .describe(\n 'Run as a long-lived background process (dev servers, watchers). Returns immediately with a background id; read output with bash_output and stop with kill_shell. Auto-killed when the session ends.',\n ),\n})\n\ntype BashArgs = z.infer<typeof BashArgsSchema>\n\nconst logger = createLogger('@x-otto/tools:bash')\n\ntype ProgressCallback = (result: ToolResult) => void\n\nexport function createBash(\n projectRoot: string,\n onProgress?: ProgressCallback,\n sandbox?: SandboxStrategy | SandboxProvider,\n processRegistry?: BackgroundProcessPort,\n /**\n * RFC-297:可插拔命令执行后端(foreground 路径专用——`spawnBackground()` 的\n * `run_in_background` 分支不在本次范围,见 RFC-297 非目标)。晚绑定方式对齐\n * `sandbox` 的既有 provider 范式:可传实例或 `() => ExecutionBackend` provider。\n */\n backend?: ExecutionBackend | ExecutionBackendProvider,\n): AgentTool<BashArgs> {\n return {\n name: 'bash',\n description: 'Execute a shell command and return stdout/stderr. Default timeout is 30 seconds.',\n parameters: BashArgsSchema,\n\n async execute({ params, signal, id, sessionId }): Promise<ToolResult> {\n const targetDir = resolve(projectRoot, params.targetDir ?? '.')\n const timeout = params.timeout ?? TOOLS_EXECUTE_TIMEOUT_MS\n const strategy =\n typeof sandbox === 'function' ? sandbox({ command: params.command }) : sandbox\n const resolvedBackend = resolveExecutionBackend(backend)\n\n if (params.run_in_background) {\n if (!processRegistry || !sessionId) {\n return {\n content: [\n {\n type: 'text',\n text: 'Background execution is unavailable in this context (no process registry / session). Re-run without run_in_background, or run a short-lived foreground command.',\n },\n ],\n isError: true,\n errorKind: 'runtime',\n }\n }\n return runBackground(\n params.command,\n targetDir,\n projectRoot,\n strategy,\n processRegistry,\n sessionId,\n id,\n )\n }\n\n return await bash(\n params.command,\n targetDir,\n projectRoot,\n timeout,\n onProgress,\n signal,\n strategy,\n resolvedBackend,\n )\n },\n }\n}\n\n/** 从命令里 best-effort 嗅探端口(仅提示用,可能缺)。 */\nfunction sniffPort(command: string): number | undefined {\n const m = command.match(\n /(?:--port[ =]|-p\\s+|localhost:|0\\.0\\.0\\.0:|127\\.0\\.0\\.1:|http\\.server\\s+)(\\d{2,5})\\b/i,\n )\n if (m) {\n const n = Number(m[1])\n if (n > 0 && n <= 65535) {\n return n\n }\n }\n return undefined\n}\n\n/**\n * RFC-223 T223-5:把 `spawnBackground` 的原始 chunk 流按行切分并喂给\n * `detectServiceFromLine`,命中即回调。跨 chunk 的不完整行用一个闭包内的字符串缓冲区\n * 拼接(`\\n` 结束的部分立即消费,剩余部分留到下次 chunk 到达再拼)——纯字符串处理,\n * 无额外 I/O,符合重要事项规则 3(不引入新的定时器/轮询)。\n */\nfunction createLineBufferedDetector(onDetected: (service: import('./background-types').DetectedService) => void): (chunk: Buffer) => void {\n let buffer = ''\n return (chunk: Buffer): void => {\n buffer += chunk.toString('utf-8')\n let newlineIdx: number\n while ((newlineIdx = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, newlineIdx)\n buffer = buffer.slice(newlineIdx + 1)\n const service = detectServiceFromLine(line)\n if (service) {\n onDetected(service)\n }\n }\n }\n}\n\nfunction runBackground(\n command: string,\n targetDir: string,\n projectRoot: string,\n sandbox: SandboxStrategy | undefined,\n registry: BackgroundProcessPort,\n sessionId: string,\n toolCallId: string,\n): ToolResult {\n const bgId = `bg_${sessionId}_${toolCallId}`\n const logPath = createTempLoggerPath()\n void sweepStaleTempLogs(BASH_LOG_TTL_MS, Date.now())\n\n try {\n const detectAndUpdate = createLineBufferedDetector((service) => {\n registry.updateDetectedService(bgId, service)\n })\n const { child, pid } = spawnBackground('sh', ['-c', command], {\n cwd: targetDir,\n projectRoot,\n sandbox,\n logPath,\n onOutputChunk: (chunk) => detectAndUpdate(chunk),\n })\n const port = sniffPort(command)\n try {\n registry.register({ id: bgId, sessionId, child, command, cwd: targetDir, logPath, port })\n } catch (registerError) {\n try {\n process.kill(-pid, 'SIGKILL')\n } catch {\n try {\n child.kill('SIGKILL')\n } catch {}\n }\n throw registerError\n }\n\n const portLine = port ? `\\nlikely port: ${port}` : ''\n const text =\n `Started in background.\\nid: ${bgId}\\npid: ${pid}\\nlog: ${logPath}${portLine}\\n\\n` +\n `Read output with bash_output(background_id=\"${bgId}\"); stop it with kill_shell(background_id=\"${bgId}\"). ` +\n `It is auto-killed when the session ends.`\n return {\n content: [{ type: 'text', text }],\n details: { backgroundProcessId: bgId, pid, logPath, port },\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n logger.warn({ err: message }, 'Failed to start background process')\n return {\n content: [{ type: 'text', text: `Failed to start background process: ${message}` }],\n isError: true,\n errorKind: 'runtime',\n }\n }\n}\n\nconst PROGRESS_EMIT_BYTES = 4 * 1024\n/** RFC-091 F2(源码对比修复):progress 重建含全窗口 Buffer.concat+toString+truncate(~120KB),\n * 纯字节阈值下高吞吐命令(10MB 输出 → 2500 次重建)会拖累事件循环。加时间下限:两次 emit 至少间隔 100ms。 */\nconst PROGRESS_EMIT_MIN_INTERVAL_MS = 100\nconst BASH_LOG_TTL_MS = 60 * 60_000\n/** 项目本地溢出日志(.otto/tool-results/bash/)的 TTL:要在会话存活期内可被 read 恢复,\n * 用 7 天而非 /tmp 的 1h;每次新溢出发生时顺带清理过期文件(修复 RFC-093 T4 清理盲区)。 */\nconst PROJECT_OVERFLOW_LOG_TTL_MS = 7 * 24 * 60 * 60_000\n\nasync function bash(\n command: string,\n targetDir: string,\n projectRoot: string,\n timeout: number,\n onProgress?: ProgressCallback,\n signal?: AbortSignal,\n sandbox?: SandboxStrategy,\n backend?: ExecutionBackend,\n): Promise<ToolResult> {\n const maxWindowBytes = TOOLS_MAX_OUTPUT_BYTES * 2\n const output: Buffer[] = []\n let totalBytes = 0\n let windowBytes = 0\n let bytesSinceEmit = 0\n let lastEmitAt = 0\n let outputPath: string | undefined\n let outputStream: WriteStream | undefined\n\n const emitProgress = (): void => {\n if (!onProgress) {\n return\n }\n const truncation = truncateTail(Buffer.concat(output).toString('utf-8'), {\n maxLines: TOOLS_MAX_OUTPUT_LINES,\n maxBytes: TOOLS_MAX_OUTPUT_BYTES,\n })\n onProgress({\n content: [{ type: 'text', text: truncation.content || '' }],\n details: { truncation: toTruncationMeta(truncation), outputPath },\n })\n }\n\n const result = await spawn('sh', ['-c', command], {\n timeout,\n signal,\n cwd: targetDir,\n sandbox,\n projectRoot,\n backend,\n onProgress: (chunk) => {\n output.push(chunk)\n totalBytes += chunk.byteLength\n windowBytes += chunk.byteLength\n\n if (totalBytes > TOOLS_MAX_OUTPUT_BYTES && !outputPath) {\n void sweepStaleTempLogs(BASH_LOG_TTL_MS, Date.now())\n void sweepStaleProjectOverflowLogs(PROJECT_OVERFLOW_LOG_TTL_MS, Date.now())\n // RFC-093 T4:溢出日志写项目本地 .otto/tool-results/bash/(read 工具可达,模型可恢复全文)\n let overflowPath = createProjectOverflowLogPath()\n try {\n mkdirSync(dirname(overflowPath), { recursive: true })\n } catch {\n overflowPath = createTempLoggerPath() // fail-open 回退 /tmp\n }\n outputPath = overflowPath\n outputStream = createWriteStream(overflowPath)\n outputStream.on('error', (err) => {\n logger.warn(\n { err: err.message },\n 'bash overflow log write failed; disabling overflow log',\n )\n outputStream = undefined\n outputPath = undefined\n })\n for (const buf of output) {\n outputStream.write(buf)\n }\n } else if (outputStream) {\n outputStream.write(chunk)\n }\n\n while (windowBytes > maxWindowBytes && output.length > 1) {\n const removed = output.shift()\n if (removed) {\n windowBytes -= removed.byteLength\n }\n }\n\n bytesSinceEmit += chunk.byteLength\n const now = Date.now()\n if (bytesSinceEmit >= PROGRESS_EMIT_BYTES && now - lastEmitAt >= PROGRESS_EMIT_MIN_INTERVAL_MS) {\n bytesSinceEmit = 0\n lastEmitAt = now\n emitProgress()\n }\n },\n })\n\n outputStream?.end()\n\n const buffer = Buffer.concat(output)\n const fullOutput = buffer.toString('utf-8')\n\n const truncation = truncateTail(fullOutput, {\n maxLines: TOOLS_MAX_OUTPUT_LINES,\n maxBytes: TOOLS_MAX_OUTPUT_BYTES,\n })\n let text = truncation.content || '(no output)'\n\n const details = {\n truncation: toTruncationMeta(truncation),\n outputPath,\n }\n\n if (truncation.truncated) {\n const start = truncation.totalLines - truncation.outputLines + 1\n const end = truncation.totalLines\n // outputPath 只在字节溢出触发落盘时才有值(见上方 totalBytes > TOOLS_MAX_OUTPUT_BYTES 分支);\n // 纯行数截断(输出总字节未超阈值)时全文仍完整保留在内存 output 里,未落盘,不应提示\n // \"Full output: undefined\"(RFC review P1-1:模型误判全文丢失)。\n const fullOutputSuffix = outputPath ? ` Full output: ${outputPath}` : ''\n\n if (truncation.lastLinePartial) {\n const lastLineSize = formatBytes(\n Buffer.byteLength(fullOutput.split('\\n').pop() || '', 'utf-8'),\n )\n text += `\\n\\n(Showing last ${formatBytes(truncation.outputBytes)} of line ${end} (line is ${lastLineSize}).${fullOutputSuffix})`\n } else if (truncation.truncatedBy === 'lines') {\n text += `\\n\\n(Showing lines ${start}-${end} of ${truncation.totalLines}.${fullOutputSuffix})`\n } else {\n text += `\\n\\n(Showing lines ${start}-${end} of ${truncation.totalLines} (${formatBytes(TOOLS_MAX_OUTPUT_BYTES)} limit).${fullOutputSuffix})`\n }\n }\n\n if (result.exitCode !== 0) {\n text += `\\n\\nCommand exited with code ${result.exitCode}`\n return {\n content: [{ type: 'text', text }],\n isError: true,\n errorKind: 'runtime',\n details,\n }\n } else {\n return { content: [{ type: 'text', text }], details }\n }\n}\n","import { readFile } from 'node:fs/promises'\nimport { z } from 'zod'\nimport { truncateTail } from '@x-otto/shared'\nimport { TOOLS_MAX_OUTPUT_BYTES, TOOLS_MAX_OUTPUT_LINES, TOOLS_EXECUTE_TIMEOUT_MS } from '../limits'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport type { BackgroundProcessInfo, BackgroundProcessPort } from './background-types'\n\nconst BashOutputArgsSchema = z.object({\n background_id: z\n .string()\n .describe('Background process id returned by bash with run_in_background:true'),\n block: z\n .boolean()\n .optional()\n .describe('Wait until the process exits before returning (default false)'),\n timeout_ms: z\n .number()\n .int()\n .min(0)\n .max(600000)\n .optional()\n .describe('Max wait when block=true (ms), default from TOOLS_EXECUTE_TIMEOUT_MS (30000)'),\n})\n\ntype BashOutputArgs = z.infer<typeof BashOutputArgsSchema>\n\nconst POLL_INTERVAL_MS = 200\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))\n\nasync function readLog(info: BackgroundProcessInfo): Promise<string> {\n if (!info.logPath) {\n return '(no output captured)'\n }\n let raw: string\n try {\n raw = await readFile(info.logPath, 'utf-8')\n } catch {\n return '(output log unavailable)'\n }\n const truncation = truncateTail(raw, {\n maxLines: TOOLS_MAX_OUTPUT_LINES,\n maxBytes: TOOLS_MAX_OUTPUT_BYTES,\n })\n return truncation.content || '(no output yet)'\n}\n\n/** 读取后台进程累积输出与状态。 */\nexport function createBashOutput(registry: BackgroundProcessPort): AgentTool<BashOutputArgs> {\n return {\n name: 'bash_output',\n description:\n 'Read accumulated output and status of a background process started by bash run_in_background.',\n parameters: BashOutputArgsSchema,\n readonly: true,\n\n async execute({ params, sessionId }): Promise<ToolResult> {\n const id = params.background_id\n let info = sessionId ? registry.getForSession(sessionId, id) : registry.get(id)\n if (!info) {\n return {\n content: [{ type: 'text', text: `No background process with id \"${id}\".` }],\n isError: true,\n errorKind: 'not_found',\n }\n }\n\n const deadline = Date.now() + (params.timeout_ms ?? TOOLS_EXECUTE_TIMEOUT_MS)\n while (params.block && info.status === 'running' && Date.now() < deadline) {\n await sleep(POLL_INTERVAL_MS)\n const refreshed = registry.get(id)\n if (!refreshed) {\n return {\n content: [\n {\n type: 'text',\n text: `[status: untracked]\\nBackground process \"${id}\" is no longer tracked (likely evicted at the process limit).`,\n },\n ],\n details: { status: 'untracked', logPath: info.logPath },\n }\n }\n info = refreshed\n }\n\n const body = await readLog(info)\n const statusLine =\n info.status === 'running'\n ? '[status: running]'\n : `[status: ${info.status}${info.exitCode !== undefined ? `, exit ${info.exitCode}` : ''}]`\n return {\n content: [{ type: 'text', text: `${statusLine}\\n${body}` }],\n details: { status: info.status, exitCode: info.exitCode, logPath: info.logPath },\n }\n },\n }\n}\n","import { z } from 'zod'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport type { BackgroundProcessPort } from './background-types'\n\nconst KillShellArgsSchema = z.object({\n background_id: z\n .string()\n .describe('Background process id (returned by bash run_in_background) to stop'),\n})\n\ntype KillShellArgs = z.infer<typeof KillShellArgsSchema>\n\n/** 停止后台进程。 */\nexport function createKillShell(registry: BackgroundProcessPort): AgentTool<KillShellArgs> {\n return {\n name: 'kill_shell',\n description: 'Stop a background process started by bash with run_in_background:true.',\n parameters: KillShellArgsSchema,\n\n async execute({ params, sessionId }): Promise<ToolResult> {\n const id = params.background_id\n const info = sessionId ? registry.getForSession(sessionId, id) : registry.get(id)\n if (!info) {\n return {\n content: [{ type: 'text', text: `No background process with id \"${id}\".` }],\n isError: true,\n errorKind: 'not_found',\n }\n }\n if (info.status !== 'running' && info.status !== 'killing') {\n return {\n content: [{ type: 'text', text: `Background process \"${id}\" already ${info.status}.` }],\n }\n }\n await registry.kill(id)\n return { content: [{ type: 'text', text: `Stopped background process \"${id}\".` }] }\n },\n }\n}\n","import { z } from 'zod'\nimport { TOOLS_MAX_OUTPUT_BYTES, TOOLS_MAX_OUTPUT_LINES } from '../limits'\nimport {\n createLogger,\n exists,\n formatBytes,\n isDirectory,\n normalizePath,\n truncateHead,\n toTruncationMeta,\n} from '@x-otto/shared'\nimport { join, relative, resolve } from 'node:path'\nimport { glob } from 'node:fs/promises'\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport { toolError } from '../tool-error'\nimport type { BinaryToolExecutorRegistry } from '../binary/binary-executor-registry'\n\nconst logger = createLogger('@x-otto/tools:find')\n\nconst FindArgsSchema = z.object({\n pattern: z\n .string()\n .describe('File name pattern with optional wildcards (*, ?), e.g. \"*.ts\", \"data-??.json\"'),\n path: z.string().optional().describe('Search starting path (default is project root)'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(1000)\n .optional()\n .default(1000)\n .describe('Maximum number of results to return'),\n})\n\ntype FindArgs = z.infer<typeof FindArgsSchema>\ntype Glob = (\n pattern: string,\n cwd: string,\n options: { ignore: string[]; limit: number },\n) => Promise<string[]>\n\ninterface FindToolOptions {\n glob?: Glob\n binaryExecutorRegistry?: BinaryToolExecutorRegistry\n}\n\nexport function createFind(\n projectRoot: string,\n options: FindToolOptions = {},\n): AgentTool<FindArgs> {\n return {\n name: 'find',\n description: `Search for files by glob pattern. Returns matching file paths relative to the search directory. Respects .gitignore. Output is truncated to ${TOOLS_MAX_OUTPUT_LINES} results or ${TOOLS_MAX_OUTPUT_BYTES / 1024}KB (whichever is hit first).`,\n parameters: FindArgsSchema,\n pathParams: ['path'],\n readonly: true,\n\n async execute({ params }): Promise<ToolResult> {\n const searchDir = resolve(projectRoot, params.path ?? '.')\n const normalizedSearchDir = normalizePath(searchDir)\n const limit = params.limit ?? TOOLS_MAX_OUTPUT_LINES\n\n if (!(await exists(normalizedSearchDir))) {\n throw toolError(`Search path does not exist: ${params.path}`, 'not_found')\n }\n\n if (!(await isDirectory(normalizedSearchDir))) {\n throw toolError(`Search path is not a directory: ${params.path}`, 'validation')\n }\n\n return await find(\n normalizedSearchDir,\n params.pattern,\n limit,\n options.glob,\n options.binaryExecutorRegistry,\n )\n },\n }\n}\n\n/**\n * `.gitignore` 辅助收集的重目录剪枝名单——glob 的 `exclude` 在**目录**入口返回 true 时\n * 不下钻子树(Node 官方语义),而非过滤后逐条丢弃,故能在恶意/巨型目录树前提前收手。\n * 覆盖前端/后端/多语言生态最常见的重目录:node_modules(JS)、.git(VCS 内部对象库,\n * 与 fd 自身 --ignore-vcs 语义呼应)、dist/build/.next/target/vendor(各类构建产物)。\n */\nconst GITIGNORE_SCAN_EXCLUDE_DIRS = new Set([\n 'node_modules', '.git', 'dist', 'build', '.next', 'target', 'vendor',\n])\n\n/**\n * 2026-07-09 OOM 事故根因修复:`path` 指向巨型/系统级目录(如用户主目录)时,本辅助\n * 扫描(为让 fd 在非 git 仓库场景下也能识别嵌套 .gitignore)此前用无边界 `glob()`\n * 全量收集,会在扫描完成前持续在 V8 堆上累积中间状态直至 OOM(已用本地实验复现同构\n * 崩溃栈:node::fs::AfterScanDir → Heap::CollectGarbage → FatalProcessOutOfMemory)。\n * 二级熔断:时间预算(GITIGNORE_SCAN_BUDGET_MS)到点即停止消费迭代器——for-await 的\n * `break` 是拉取式生成器的天然背压信号,底层 readdir 递归链不会继续派发新扫描\n * (已实测验证:break 后堆内存不再增长)。同时用 exclude 剪掉常见重目录,减少绝大多数\n * 项目下触发熔断的概率。熔断触发只影响\"额外发现的嵌套 .gitignore 精度\",不影响正确性\n * 下限——根目录 .gitignore 与 git 仓库内 fd 自身的 VCS ignore 逻辑仍然生效。\n */\nconst GITIGNORE_SCAN_BUDGET_MS = 1_500\nconst GITIGNORE_SCAN_MAX_FILES = 200\n\nasync function prepareSearchArgs(pattern: string, normalizedSearchDir: string, limit: number) {\n const args: string[] = ['--glob', '--color=never', '--hidden', '--max-results', String(limit)]\n\n const ignores = new Set<string>()\n const root = join(normalizedSearchDir, '.gitignore')\n if (await exists(root)) {\n ignores.add(root)\n }\n\n try {\n const scanStart = Date.now()\n const iter = glob('**/.gitignore', {\n cwd: normalizedSearchDir,\n exclude: (name: string) => GITIGNORE_SCAN_EXCLUDE_DIRS.has(name),\n })\n for await (const file of iter) {\n ignores.add(file)\n if (\n ignores.size >= GITIGNORE_SCAN_MAX_FILES ||\n Date.now() - scanStart > GITIGNORE_SCAN_BUDGET_MS\n ) {\n logger.warn(\n { normalizedSearchDir, found: ignores.size, elapsedMs: Date.now() - scanStart },\n 'find: .gitignore discovery hit time/count budget, stopping early (best-effort)',\n )\n break\n }\n }\n } catch {}\n\n for (const path of ignores) {\n args.push('--ignore-file', path)\n }\n\n args.push('--', pattern, normalizedSearchDir)\n\n return args\n}\n\n/**\n * 两条搜索路径(custom glob / fd binary)共享的结果收尾格式化(RFC review P2-1/P2-5):\n * truncateHead → 组装 resultLimitReached/truncation notices → 拼接 \"\\n\\n(...)\" 提示。\n * `limitReachedMessage` 让两条路径各自表达差异化文案(custom glob 路径无 limit*2 提示,\n * fd 路径额外建议翻倍 limit);`extraNotices` 供 fd 路径追加 bufferTruncated 提示——\n * custom glob 路径没有底层 buffer 截断概念,不该被强行合并掉这条分支点。\n */\nfunction formatFindResults(\n relativized: string[],\n limit: number,\n limitReachedMessage: string,\n extraNotices: Array<{ text: string; detailKey: string; detailValue: unknown }> = [],\n): ToolResult {\n const resultLimitReached = relativized.length >= limit\n const raw = relativized.join('\\n')\n const truncation = truncateHead(raw, {\n maxLines: TOOLS_MAX_OUTPUT_LINES,\n maxBytes: TOOLS_MAX_OUTPUT_BYTES,\n })\n\n let content = truncation.content\n const details: Record<string, unknown> = {}\n const notices: string[] = []\n\n if (resultLimitReached) {\n notices.push(limitReachedMessage)\n details.resultLimitReached = limit\n }\n\n if (truncation.truncated) {\n notices.push(`${formatBytes(TOOLS_MAX_OUTPUT_BYTES)} limit reached`)\n details.truncation = toTruncationMeta(truncation)\n }\n\n for (const extra of extraNotices) {\n notices.push(extra.text)\n details[extra.detailKey] = extra.detailValue\n }\n\n if (notices.length > 0) {\n content += `\\n\\n(${notices.join('. ')})`\n }\n\n return {\n content: [{ type: 'text' as const, text: content }],\n details,\n }\n}\n\nasync function find(\n targetDir: string,\n pattern: string,\n limit: number,\n glob?: Glob,\n binaryExecutorRegistry?: BinaryToolExecutorRegistry,\n): Promise<ToolResult> {\n if (typeof glob === 'function') {\n logger.debug('Using custom glob implementation for find tool')\n\n const results = await glob(pattern, targetDir, {\n ignore: ['**/node_modules/**', '**/.git/**'],\n limit,\n })\n\n if (results.length === 0) {\n return {\n content: [{ type: 'text' as const, text: 'No files found matching pattern' }],\n }\n }\n\n const relativized = results.map((path) =>\n path.startsWith(targetDir) ? path.slice(targetDir.length + 1) : relative(targetDir, path),\n )\n\n return formatFindResults(relativized, limit, `${limit} results limit reached`)\n }\n\n logger.debug('No custom glob provided, using fd binary for find tool')\n\n const fd = await binaryExecutorRegistry?.ensure('fd')\n if (!fd) {\n throw toolError('Find tool requires a glob implementation or fd binary available', 'runtime')\n }\n\n const args = await prepareSearchArgs(pattern, targetDir, limit)\n const result = await fd.execute(args, {\n maxBuffer: TOOLS_MAX_OUTPUT_BYTES * 4,\n })\n\n if (result.exitCode !== 0) {\n return {\n content: [\n {\n type: 'text',\n text: result.stderr?.trim() || `fd exited with code ${result.exitCode}`,\n },\n ],\n }\n }\n\n const output = result.stdout?.trim() || ''\n\n if (!output) {\n return {\n content: [{ type: 'text', text: 'No files found matching pattern' }],\n }\n }\n\n const lines = output.split('\\n')\n const relativized: string[] = []\n\n for (const raw of lines) {\n const line = raw.replace(/\\r$/, '').trim()\n if (!line) {\n continue\n }\n\n const hadTrailingSlash = line.endsWith('/') || line.endsWith('\\\\')\n let relativePath = line\n\n if (line.startsWith(targetDir)) {\n relativePath = line.slice(targetDir.length + 1)\n } else {\n relativePath = relative(targetDir, line)\n }\n\n if (hadTrailingSlash && !relativePath.endsWith('/')) {\n relativePath += '/'\n }\n\n relativized.push(relativePath)\n }\n\n const extraNotices = result.truncated\n ? [\n {\n text: 'output exceeded buffer limit; results are incomplete (refine the pattern)',\n detailKey: 'bufferTruncated',\n detailValue: true,\n },\n ]\n : []\n\n return formatFindResults(\n relativized,\n limit,\n `${limit} results limit reached. Use limit=${limit * 2} for more, or refine pattern`,\n extraNotices,\n )\n}\n","import { z } from 'zod'\nimport { resolve, relative } from 'node:path'\nimport { spawn as nodeSpawn } from 'node:child_process'\nimport type { ProcessTracker } from '@x-otto/interchange'\nimport { TOOLS_MAX_OUTPUT_BYTES, TOOLS_MAX_OUTPUT_LINES } from '../limits'\nimport { exists, formatBytes, normalizePath, truncateHead, truncateLine, toTruncationMeta } from '@x-otto/shared'\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport { toolError } from '../tool-error'\nimport type { BinaryToolExecutorRegistry } from '../binary/binary-executor-registry'\nimport { BinaryToolExecutor } from '../binary/binary-executor'\n\nconst GREP_DEFAULT_LIMIT = 100\nconst GREP_MAX_LINE_LENGTH = 500\nconst GREP_MAX_STDOUT_BYTES = TOOLS_MAX_OUTPUT_BYTES * 8\n\nconst GrepArgsSchema = z.object({\n pattern: z.string().describe('Search pattern (regex or literal string)'),\n path: z.string().optional().describe('Directory or file to search (default: current directory)'),\n glob: z\n .string()\n .optional()\n .describe('Filter files by glob pattern, e.g. \"*.ts\" or \"**/*.spec.ts\"'),\n ignoreCase: z.boolean().optional().describe('Case-insensitive search (default: false)'),\n literal: z\n .boolean()\n .optional()\n .describe('Treat pattern as literal string instead of regex (default: false)'),\n context: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe('Number of lines to show before and after each match (default: 0)'),\n limit: z\n .number()\n .int()\n .min(1)\n .optional()\n .describe(`Maximum number of matches to return (default: ${GREP_DEFAULT_LIMIT})`),\n})\n\ntype GrepArgs = z.infer<typeof GrepArgsSchema>\n\ninterface GrepToolOptions {\n binaryToolExecutorRegistry?: BinaryToolExecutorRegistry\n /** RFC-095:进程追踪器(组合根注入,替代 globalProcessRuntime)。 */\n processTracker?: ProcessTracker\n}\n\nexport interface RgEntry {\n kind: 'match' | 'context'\n path: string\n lineNumber: number\n text: string\n}\n\nexport function createGrep(projectRoot: string, options: GrepToolOptions): AgentTool<GrepArgs> {\n return {\n name: 'grep',\n description: `Search file contents for pattern matches. Returns matching lines with file paths and line numbers. Respects .gitignore. Truncated to ${GREP_DEFAULT_LIMIT} matches or ${TOOLS_MAX_OUTPUT_BYTES / 1024}KB. Long lines truncated to ${GREP_MAX_LINE_LENGTH} chars.`,\n parameters: GrepArgsSchema,\n pathParams: ['path'],\n readonly: true,\n\n async execute({ params }): Promise<ToolResult> {\n const searchPath = resolve(projectRoot, params.path ?? '.')\n const normalizedSearchPath = normalizePath(searchPath)\n\n if (!(await exists(normalizedSearchPath))) {\n throw toolError(`Search path does not exist: ${params.path ?? '.'}`, 'not_found')\n }\n\n return await grep(\n params.pattern,\n normalizedSearchPath,\n projectRoot,\n params.glob,\n params.literal ?? false,\n params.ignoreCase ?? false,\n params.context ?? 0,\n params.limit ?? GREP_DEFAULT_LIMIT,\n options.binaryToolExecutorRegistry,\n options.processTracker,\n )\n },\n }\n}\n\nexport function buildRgArgs(\n pattern: string,\n targetDir: string,\n ignoreCase: boolean,\n literal: boolean,\n glob?: string,\n limit?: number,\n context?: number,\n): string[] {\n const args: string[] = ['--json', '--line-number', '--color=never', '--hidden']\n\n if (ignoreCase) {\n args.push('--ignore-case')\n }\n\n if (literal) {\n args.push('--fixed-strings')\n }\n\n if (glob) {\n args.push('--glob', glob)\n }\n\n if (limit != null) {\n args.push('--max-count', String(limit))\n }\n\n if (context != null && context > 0) {\n args.push('--context', String(context))\n }\n\n args.push('--', pattern, targetDir)\n\n return args\n}\n\nexport function parseRgJsonOutput(stdout: string): RgEntry[] {\n const entries: RgEntry[] = []\n\n for (const line of stdout.split('\\n')) {\n if (!line.trim()) {\n continue\n }\n\n try {\n const parsed = JSON.parse(line)\n\n if (parsed.type === 'match' || parsed.type === 'context') {\n const data = parsed.data\n entries.push({\n kind: parsed.type,\n path: data.path?.text ?? '',\n lineNumber: data.line_number ?? 0,\n text: data.lines?.text?.replace(/\\n$/, '') ?? '',\n })\n }\n } catch {}\n }\n\n return entries\n}\n\nfunction relativizePath(filePath: string, projectRoot: string, targetDir: string): string {\n return filePath.startsWith(projectRoot)\n ? relative(projectRoot, filePath)\n : relative(targetDir, filePath)\n}\n\nexport interface FormattedGrep {\n text: string\n totalMatches: number\n matchLimitReached: boolean\n linesTruncated: number\n}\n\nexport function formatRgEntries(\n entries: RgEntry[],\n projectRoot: string,\n targetDir: string,\n limit: number,\n): FormattedGrep {\n const totalMatches = entries.reduce((n, e) => n + (e.kind === 'match' ? 1 : 0), 0)\n const outputLines: string[] = []\n let matchCount = 0\n let linesTruncated = 0\n let lastPath: string | null = null\n let lastLine = 0\n let reachedLimit = false\n\n for (const entry of entries) {\n if (entry.kind === 'match') {\n if (matchCount >= limit) {\n break\n }\n matchCount++\n if (matchCount >= limit) {\n reachedLimit = true\n }\n } else if (reachedLimit && (entry.path !== lastPath || entry.lineNumber !== lastLine + 1)) {\n break\n }\n\n if (lastPath !== null && (entry.path !== lastPath || entry.lineNumber > lastLine + 1)) {\n outputLines.push('--')\n }\n\n const { text: truncated } = truncateLine(entry.text, GREP_MAX_LINE_LENGTH)\n if (entry.text.length > GREP_MAX_LINE_LENGTH) {\n linesTruncated++\n }\n const sep = entry.kind === 'match' ? ':' : '-'\n const rel = relativizePath(entry.path, projectRoot, targetDir)\n outputLines.push(`${rel}${sep}${entry.lineNumber}${sep} ${truncated}`)\n\n lastPath = entry.path\n lastLine = entry.lineNumber\n }\n\n return {\n text: outputLines.join('\\n'),\n totalMatches,\n matchLimitReached: totalMatches > limit,\n linesTruncated,\n }\n}\n\nasync function executeRg(\n rgPath: string,\n args: string[],\n maxStdoutBytes: number,\n processTracker?: ProcessTracker,\n): Promise<{ stdout: string; exitCode: number | null; error?: string; streamTruncated: boolean }> {\n return new Promise((resolve) => {\n const ps = nodeSpawn(rgPath, args, { stdio: 'pipe' })\n // RFC-095: 纳入 ProcessTracker 统一追踪(短命 ripgrep spawn,退出即自动移除)\n processTracker?.registerChild(\n { command: rgPath, args, owner: { type: 'agent-tool', id: 'ripgrep' }, category: 'tool', lifecycle: 'evictable' },\n ps,\n )\n const stdout: Buffer[] = []\n let bytes = 0\n let streamTruncated = false\n\n ps.stdout.on('data', (data: Buffer) => {\n if (streamTruncated) {\n return\n }\n if (bytes + data.byteLength > maxStdoutBytes) {\n const remaining = maxStdoutBytes - bytes\n if (remaining > 0) {\n stdout.push(data.subarray(0, remaining))\n bytes = maxStdoutBytes\n }\n streamTruncated = true\n ps.kill()\n return\n }\n stdout.push(data)\n bytes += data.byteLength\n })\n ps.stderr.on('data', () => {})\n\n ps.on('error', (err) => {\n resolve({ stdout: '', exitCode: -999, error: err.message, streamTruncated: false })\n })\n\n ps.on('close', (code) => {\n resolve({\n stdout: Buffer.concat(stdout).toString('utf-8'),\n exitCode: streamTruncated ? 0 : code,\n streamTruncated,\n })\n })\n })\n}\n\nasync function grep(\n pattern: string,\n targetDir: string,\n projectRoot: string,\n glob: string | undefined,\n literal: boolean,\n ignoreCase: boolean,\n contextSize: number,\n limit: number,\n binaryExecutorRegistry?: BinaryToolExecutorRegistry,\n processTracker?: ProcessTracker,\n): Promise<ToolResult> {\n if (!binaryExecutorRegistry) {\n throw toolError('Binary tool executor registry is not available', 'runtime')\n }\n\n const rgTool = binaryExecutorRegistry.get('rg')\n if (!rgTool) {\n throw toolError('ripgrep (rg) executor is not available', 'runtime')\n }\n\n let rgPath: string\n if (rgTool instanceof BinaryToolExecutor) {\n rgPath = await rgTool.ensure()\n } else {\n rgPath = 'rg'\n }\n\n const args = buildRgArgs(pattern, targetDir, ignoreCase, literal, glob, limit, contextSize)\n\n const result = await executeRg(rgPath, args, GREP_MAX_STDOUT_BYTES, processTracker)\n\n if (result.exitCode === -999) {\n throw toolError(`Failed to execute ripgrep: ${result.error ?? 'unknown error'}`, 'io')\n }\n\n if (result.exitCode === 2 && !result.streamTruncated) {\n throw toolError(`ripgrep error while searching for \"${pattern}\"`, 'io')\n }\n\n const entries = parseRgJsonOutput(result.stdout)\n const formatted = formatRgEntries(entries, projectRoot, targetDir, limit)\n\n if (formatted.totalMatches === 0) {\n return {\n content: [{ type: 'text', text: 'No matches found.' }],\n }\n }\n\n const truncation = truncateHead(formatted.text, {\n maxLines: TOOLS_MAX_OUTPUT_LINES,\n maxBytes: TOOLS_MAX_OUTPUT_BYTES,\n })\n\n let output = truncation.content\n const details: Record<string, unknown> = { truncation: toTruncationMeta(truncation) }\n const notices: string[] = []\n\n if (formatted.matchLimitReached) {\n notices.push(`${limit} match limit reached — use a more specific pattern or increase limit`)\n details.matchLimitReached = limit\n }\n\n if (truncation.truncated) {\n notices.push(`Output truncated (${formatBytes(TOOLS_MAX_OUTPUT_BYTES)} limit)`)\n }\n\n if (result.streamTruncated) {\n notices.push('search output exceeded buffer limit; results are incomplete (refine the pattern)')\n details.streamTruncated = true\n }\n\n if (formatted.linesTruncated > 0) {\n notices.push(\n `${formatted.linesTruncated} long lines truncated to ${GREP_MAX_LINE_LENGTH} chars`,\n )\n details.linesTruncated = formatted.linesTruncated\n }\n\n if (notices.length > 0) {\n output += '\\n\\n(' + notices.join('. ') + ')'\n }\n\n return {\n content: [{ type: 'text', text: output }],\n details,\n }\n}\n","import { z } from 'zod'\nimport { readFile } from 'node:fs/promises'\nimport { createReadStream } from 'node:fs'\nimport { createInterface } from 'node:readline'\nimport { resolve } from 'node:path'\nimport {\n isFile,\n exists,\n mime,\n probeImageDimensions,\n API_IMAGE_DIMENSION_LIMIT_PX,\n} from '@x-otto/shared'\nimport { truncateHead, toTruncationMeta, formatBytes, normalizePath, PROVIDER_IMAGE_INLINE_MAX_BYTES } from '@x-otto/shared'\nimport { TOOLS_MAX_OUTPUT_BYTES, TOOLS_MAX_OUTPUT_LINES } from '../limits'\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport { toolError } from '../tool-error'\n\nconst ReadArgsSchema = z.object({\n path: z.string().describe('Path to the file to read (relative or absolute)'),\n limit: z\n .number()\n .int()\n .min(1)\n .optional()\n .describe('Maximum number of lines to read (text files only)'),\n offset: z\n .number()\n .int()\n .min(1)\n .optional()\n .describe('Starting line number (1-based, text files only)'),\n})\n\nexport type ReadArgs = z.infer<typeof ReadArgsSchema>\n\nexport function createRead(projectRoot: string): AgentTool<ReadArgs> {\n return {\n name: 'read',\n description: 'Read file content. For text files, can specify line range with limit and offset.',\n parameters: ReadArgsSchema,\n pathParams: ['path'],\n resolvePath: (raw) => resolve(projectRoot, raw),\n readonly: true,\n\n async execute({ params }): Promise<ToolResult> {\n const resolvedPath = resolve(projectRoot, params.path)\n const normalizedPath = normalizePath(resolvedPath)\n\n if (!(await exists(normalizedPath))) {\n throw toolError(`File not found: ${params.path}`, 'not_found')\n }\n\n if (!(await isFile(normalizedPath))) {\n throw toolError(`Not a file: ${params.path}`, 'validation')\n }\n\n const mt = await mime(normalizedPath)\n const isSupportedImage = mt?.startsWith('image/') && mt !== 'image/svg+xml'\n\n if (isSupportedImage) {\n return readImage(normalizedPath, params.path, mt)\n }\n\n return readTextWithRange(normalizedPath, params.path, params.offset, params.limit)\n },\n }\n}\n\n/** 文件系统图片字节上限——跨包单源 @x-otto/shared PROVIDER_IMAGE_INLINE_MAX_BYTES(C 组收敛)。 */\nconst MAX_IMAGE_FILE_BYTES = PROVIDER_IMAGE_INLINE_MAX_BYTES\n\nasync function readImage(\n absolutePath: string,\n displayPath: string,\n mt?: string,\n): Promise<ToolResult> {\n const buffer = await readFile(absolutePath)\n\n // 字节守卫:fs/read 的图无\"重拍更小\"的重试路径(文件即源),超限直接拒绝。\n if (buffer.length > MAX_IMAGE_FILE_BYTES) {\n return {\n content: [\n {\n type: 'text',\n text: `[image omitted: ${displayPath} is ${buffer.length} bytes, exceeds 5MB limit for inline image context — view a downscaled copy or crop the region you need]`,\n },\n ],\n details: {\n path: absolutePath,\n type: 'image',\n size: buffer.length,\n mime: mt || 'application/octet-stream',\n },\n }\n }\n\n // 维度守卫:2000px many-image 规则由 work-loop D6 cap(≤20 张/请求)保证不适用,\n // 此处只拦 API 必拒的 >8000px。探测 undefined → fail-open 放行。\n if (buffer.length > 0) {\n const dims = probeImageDimensions(buffer.toString('base64'), mt || '')\n if (dims && (dims.width > API_IMAGE_DIMENSION_LIMIT_PX || dims.height > API_IMAGE_DIMENSION_LIMIT_PX)) {\n return {\n content: [\n {\n type: 'text',\n text: `[image omitted: ${displayPath} is ${dims.width}x${dims.height}px, exceeds Anthropic per-image dimension hard limit of ${API_IMAGE_DIMENSION_LIMIT_PX}px — view a downscaled copy or crop the region you need]`,\n },\n ],\n details: {\n path: absolutePath,\n type: 'image',\n size: buffer.length,\n mime: mt || 'application/octet-stream',\n dimensions: dims,\n },\n }\n }\n }\n\n const base64 = buffer.toString('base64')\n\n return {\n content: [\n {\n type: 'text',\n text: `Read image file ${displayPath} (${buffer.length} bytes)`,\n },\n {\n type: 'image',\n mime: mt || 'application/octet-stream',\n source: `data:${mt || 'application/octet-stream'};base64,${base64}`,\n },\n ],\n details: {\n path: absolutePath,\n type: 'image',\n size: buffer.length,\n mime: mt || 'application/octet-stream',\n },\n }\n}\n\ninterface LineWindow {\n lines: string[]\n hasMore: boolean\n totalLinesIfBeyond?: number\n}\n\nasync function scanLineWindow(\n absolutePath: string,\n start: number,\n limit: number,\n maxBytes: number,\n): Promise<LineWindow> {\n const stream = createReadStream(absolutePath, 'utf-8')\n const rl = createInterface({ input: stream, crlfDelay: Infinity })\n\n const lines: string[] = []\n let lineNo = 0\n let bytes = 0\n let hasMore = false\n\n try {\n for await (const line of rl) {\n if (lineNo >= start) {\n if (lines.length >= limit || bytes >= maxBytes) {\n hasMore = true\n break\n }\n lines.push(line)\n bytes += Buffer.byteLength(line, 'utf-8') + 1\n }\n lineNo++\n }\n } finally {\n rl.close()\n stream.destroy()\n }\n\n if (lines.length === 0 && start >= lineNo) {\n return { lines, hasMore: false, totalLinesIfBeyond: lineNo }\n }\n return { lines, hasMore }\n}\n\nasync function readTextWithRange(\n absolutePath: string,\n _displayPath: string,\n offset: number = 1,\n limit: number = TOOLS_MAX_OUTPUT_LINES,\n maxBytes: number = TOOLS_MAX_OUTPUT_BYTES,\n): Promise<ToolResult> {\n const start = offset ? Math.max(0, offset - 1) : 0\n const displayLine = start + 1\n\n const window = await scanLineWindow(absolutePath, start, limit, maxBytes)\n\n if (window.totalLinesIfBeyond !== undefined) {\n return {\n content: [\n {\n type: 'text',\n text: `Offset ${offset} is beyond end of file (${window.totalLinesIfBeyond} lines total)`,\n },\n ],\n }\n }\n\n const selectedContent = window.lines.join('\\n')\n const truncation = truncateHead(selectedContent, {\n maxBytes: TOOLS_MAX_OUTPUT_BYTES,\n maxLines: TOOLS_MAX_OUTPUT_LINES,\n })\n let output: string\n\n if (truncation.firstLineExceedsLimit) {\n const size = formatBytes(Buffer.byteLength(window.lines[0] ?? '', 'utf-8'))\n output = `(Line ${displayLine} is ${size}, exceeds ${formatBytes(maxBytes)} limit. Use bash: sed -n '${displayLine}p' ${absolutePath} | head -c ${maxBytes})`\n } else if (truncation.truncated) {\n const endLine = displayLine + truncation.outputLines - 1\n const nextOffset = endLine + 1\n output = truncation.content\n if (truncation.truncatedBy === 'lines') {\n output += `\\n\\n(Showing lines ${displayLine}-${endLine}. Use offset=${nextOffset} to continue)`\n } else {\n output += `\\n\\n(Showing lines ${displayLine}-${endLine} (${formatBytes(maxBytes)} limit). Use offset=${nextOffset} to continue)`\n }\n } else if (window.hasMore) {\n const endLine = displayLine + window.lines.length - 1\n const nextOffset = endLine + 1\n output = truncation.content\n output += `\\n\\n(More lines follow. Use offset=${nextOffset} to continue)`\n } else {\n output = truncation.content\n }\n\n return {\n content: [{ type: 'text', text: output }],\n details: { truncation: toTruncationMeta(truncation) },\n }\n}\n","import { dirname } from 'node:path'\nimport { mkdirp, normalizePath } from '@x-otto/shared'\nimport { resolve } from 'node:path'\nimport { writeFile, stat } from 'node:fs/promises'\nimport { z } from 'zod'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport { toolError } from '../tool-error'\nimport { TOOLS_MAX_WRITE_BYTES } from '../limits'\n\nconst WriteArgsSchema = z.object({\n path: z.string().describe('Path to the file to write (relative or absolute)'),\n content: z.string().describe('Content to write to the file'),\n createDirectories: z\n .boolean()\n .optional()\n .default(true)\n .describe('Whether to create parent directories if they do not exist'),\n})\n\nexport type WriteArgs = z.infer<typeof WriteArgsSchema>\n\nexport function createWrite(projectRoot: string): AgentTool<WriteArgs> {\n return {\n name: 'write',\n description:\n 'Create or overwrite a file, automatically creating parent directories if they do not exist',\n parameters: WriteArgsSchema,\n pathParams: ['path'],\n resolvePath: (raw) => resolve(projectRoot, raw),\n\n async execute({ params, signal }): Promise<ToolResult> {\n const resolvedPath = resolve(projectRoot, params.path)\n const normalizedPath = normalizePath(resolvedPath)\n\n const byteLength = Buffer.byteLength(params.content, 'utf-8')\n if (byteLength > TOOLS_MAX_WRITE_BYTES) {\n throw toolError(\n `Content too large: ${byteLength} bytes exceeds the write limit of ${TOOLS_MAX_WRITE_BYTES} bytes`,\n 'validation',\n )\n }\n\n if (params.createDirectories !== false) {\n await mkdirp(dirname(normalizedPath))\n }\n\n if (signal.aborted) {\n throw toolError('Write operation was aborted', 'aborted')\n }\n\n return await write(normalizedPath, params.content, signal)\n },\n }\n}\n\nasync function write(path: string, content: string, signal: AbortSignal): Promise<ToolResult> {\n if (signal.aborted) {\n throw toolError('Write operation was aborted', 'aborted')\n }\n\n const existed = await fileExists(path)\n\n await writeFile(path, content, 'utf-8')\n\n return {\n content: [{ type: 'text', text: `Successfully ${existed ? 'overwrote' : 'created'} ${path}` }],\n details: { overwritten: existed },\n }\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await stat(path)\n return true\n } catch {\n return false\n }\n}\n","import { diffLines } from 'diff'\n\ninterface DiffResult {\n content: string\n firstChangedLine: number | undefined\n}\n\ninterface DiffPart {\n added?: boolean\n removed?: boolean\n value: string\n}\n\nconst ANSI_GREEN = '\\x1b[32m'\nconst ANSI_RED = '\\x1b[31m'\nconst ANSI_DIM = '\\x1b[2m'\nconst ANSI_RESET = '\\x1b[0m'\n\nexport function diff(oldContent: string, newContent: string, contextLines = 4): DiffResult {\n const parts = diffLines(oldContent, newContent) as DiffPart[]\n const output: string[] = []\n\n const oldLines = oldContent.split('\\n')\n const newLines = newContent.split('\\n')\n const maxLine = Math.max(oldLines.length, newLines.length)\n const lineWidth = String(maxLine).length\n\n let oldLine = 1\n let newLine = 1\n let lastWasChange = false\n let firstChangedLine: number | undefined\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i] as DiffPart\n const raw = part.value.split('\\n')\n\n if (raw[raw.length - 1] === '') {\n raw.pop()\n }\n\n if (part.added || part.removed) {\n if (firstChangedLine === undefined) {\n firstChangedLine = newLine\n }\n\n for (const line of raw) {\n if (part.added) {\n const lineNum = String(newLine).padStart(lineWidth, ' ')\n output.push(`${ANSI_GREEN}+${lineNum} ${line}${ANSI_RESET}`)\n newLine++\n } else {\n const lineNum = String(oldLine).padStart(lineWidth, ' ')\n output.push(`${ANSI_RED}-${lineNum} ${line}${ANSI_RESET}`)\n oldLine++\n }\n }\n lastWasChange = true\n } else {\n const nextPart = parts[i + 1] as DiffPart\n const nextPartIsChange = i < parts.length - 1 && (nextPart.added || nextPart.removed)\n\n if (lastWasChange || nextPartIsChange) {\n let linesToShow = raw\n let skipStart = 0\n let skipEnd = 0\n\n if (!lastWasChange) {\n skipStart = Math.max(0, raw.length - contextLines)\n linesToShow = raw.slice(skipStart)\n }\n\n if (!nextPartIsChange && linesToShow.length > contextLines) {\n skipEnd = linesToShow.length - contextLines\n linesToShow = linesToShow.slice(0, contextLines)\n }\n\n if (skipStart > 0) {\n output.push(` ${''.padStart(lineWidth, ' ')} ...`)\n oldLine += skipStart\n newLine += skipStart\n }\n\n for (const line of linesToShow) {\n const lineNum = String(oldLine).padStart(lineWidth, ' ')\n output.push(`${ANSI_DIM} ${lineNum} ${line}${ANSI_RESET}`)\n oldLine++\n newLine++\n }\n\n if (skipEnd > 0) {\n output.push(` ${''.padStart(lineWidth, ' ')} ...`)\n oldLine += skipEnd\n newLine += skipEnd\n }\n } else {\n oldLine += raw.length\n newLine += raw.length\n }\n\n lastWasChange = false\n }\n }\n\n return {\n content: output.join('\\n'),\n firstChangedLine,\n }\n}\n","/**\n * fuzzy-match-impl.ts — RFC-089 M5:edit 工具的纯计算函数(共享源码)\n *\n * 主线程与 worker 共用,避免重复实现导致行为不一致。\n * 全部为纯函数:无副作用、不依赖注入 ports、不访问 I/O。\n */\n\nconst QUOTE_SINGLE = /[\\u2018\\u2019\\u201A\\u201B]/\nconst QUOTE_DOUBLE = /[\\u201C\\u201D\\u201E\\u201F]/\nconst DASH = /[\\u2010\\u2011\\u2012\\u2013\\u2014\\u2015\\u2212]/\nconst SPACE = /[\\u00A0\\u2002-\\u200A\\u202F\\u205F\\u3000]/\n\nexport function normalizeChar(ch: string): string {\n if (QUOTE_SINGLE.test(ch)) return \"'\"\n if (QUOTE_DOUBLE.test(ch)) return '\"'\n if (DASH.test(ch)) return '-'\n if (SPACE.test(ch)) return ' '\n return ch\n}\n\nexport interface NormalizedWithMap {\n text: string\n map: number[]\n}\n\nexport function normalizeWithMap(content: string): NormalizedWithMap {\n const out: string[] = []\n const map: number[] = []\n const len = content.length\n let lineStart = 0\n\n for (let i = 0; i <= len; i++) {\n if (i < len && content[i] !== '\\n') {\n continue\n }\n\n const line = content.slice(lineStart, i)\n const keptLen = line.trimEnd().length\n for (let j = 0; j < keptLen; j++) {\n out.push(normalizeChar(line.charAt(j)))\n map.push(lineStart + j)\n }\n\n if (i < len) {\n out.push('\\n')\n map.push(i)\n }\n\n lineStart = i + 1\n }\n\n return { text: out.join(''), map }\n}\n\nexport function countOccurrences(haystack: string, needle: string): number {\n if (needle.length === 0) return 0\n return haystack.split(needle).length - 1\n}\n\n/**\n * 纯函数 fuzzy match:已知 oldContent 嵌入 content 时,找到精确位与次数。\n * 先精确匹配,失败后走 normalize(Unicode 引号/空格/连字符归一化)。\n * 输入:content(文件全文)、oldContent(要匹配的片段)。\n * 输出:FuzzyMatch。不依赖 I/O、不抛异常。\n */\nexport interface FuzzyMatch {\n found: boolean\n index: number\n matchLength: number\n occurrences: number\n usedFuzzyMatch: boolean\n}\n\nexport const NOT_FOUND: FuzzyMatch = Object.freeze({\n found: false,\n index: -1,\n matchLength: 0,\n occurrences: 0,\n usedFuzzyMatch: false,\n})\n\nexport function fuzzyMatchPure(content: string, oldContent: string): FuzzyMatch {\n const exactIndex = content.indexOf(oldContent)\n if (exactIndex !== -1 && oldContent.length > 0) {\n return {\n found: true,\n index: exactIndex,\n matchLength: oldContent.length,\n occurrences: countOccurrences(content, oldContent),\n usedFuzzyMatch: false,\n }\n }\n\n const { text: normContent, map } = normalizeWithMap(content)\n const normOld = normalizeWithMap(oldContent).text\n if (normOld.length === 0) return NOT_FOUND\n\n const firstIndex = normContent.indexOf(normOld)\n if (firstIndex === -1) return NOT_FOUND\n\n const start = map[firstIndex] ?? 0\n const end = (map[firstIndex + normOld.length - 1] ?? start) + 1\n\n return {\n found: true,\n index: start,\n matchLength: end - start,\n occurrences: countOccurrences(normContent, normOld),\n usedFuzzyMatch: true,\n }\n}\n","import { z } from 'zod'\nimport { exists, isFile, normalizePath } from '@x-otto/shared'\nimport { resolve } from 'node:path'\nimport { readFile, writeFile } from 'node:fs/promises'\nimport { Worker } from 'node:worker_threads'\nimport { diff } from './diff'\nimport {\n fuzzyMatchPure,\n countOccurrences,\n type FuzzyMatch,\n} from './fuzzy-match-impl'\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport { toolError } from '../tool-error'\n\nconst EditArgsSchema = z\n .object({\n path: z.string().describe('Path to the file to edit (relative or absolute)'),\n oldContent: z\n .string()\n .describe(\n 'Exact existing text to locate (must be unique in the file). Provide it verbatim — no diff \"-\" prefixes.',\n ),\n newContent: z\n .string()\n .describe(\n 'The full replacement text for oldContent. It REPLACES oldContent entirely — do NOT repeat oldContent inside it, and do NOT include diff \"+\"/\"-\" prefixes. Example: to rename a-b → a_b, oldContent=\"a-b\", newContent=\"a_b\" (NOT \"a-b,a_b\").',\n ),\n })\n .superRefine((args, ctx) => {\n const oldValue = args.oldContent\n const newValue = args.newContent\n\n if (oldValue === undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'Missing old content text.',\n path: ['oldContent'],\n })\n }\n\n if (newValue === undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'Missing new content text.',\n path: ['newContent'],\n })\n }\n })\n\nexport type EditArgs = z.infer<typeof EditArgsSchema>\n\ntype LineEnding = '\\r\\n' | '\\n'\n\n// ---- RFC-089 M5: worker 池 —— 大文件 fuzzy match 下沉 ----\n/** 下沉阈值(字节):≥50KB 的文件走 worker 池 fuzzy match(M0 bench 拐点)。 */\nconst WORKER_FUZZY_THRESHOLD_BYTES = 50 * 1024\n\nconst normalizeLineEndings = (content: string): string => {\n return content.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n')\n}\n\n/**\n * fuzzyMatch:≤50KB 主线程同步;>50KB 走 worker 池。\n * 先尝试精确匹配(O(n) indexOf,快),仅失败时才 normalize(走 worker)。\n */\nexport async function fuzzyMatch(\n content: string,\n oldContent: string,\n signal: AbortSignal,\n): Promise<FuzzyMatch> {\n // 精确匹配 → 主线程同步(<1ms,不入 worker)\n const exactIndex = content.indexOf(oldContent)\n if (exactIndex !== -1 && oldContent.length > 0) {\n return {\n found: true,\n index: exactIndex,\n matchLength: oldContent.length,\n occurrences: countOccurrences(content, oldContent),\n usedFuzzyMatch: false,\n }\n }\n\n // <=50KB → 主线程同步(benchmark: ≤6.82ms mean,可接受)\n if (content.length <= WORKER_FUZZY_THRESHOLD_BYTES) {\n return fuzzyMatchPure(content, oldContent)\n }\n\n // >50KB → worker 池(避免主线程 45–128ms 假死)\n return fuzzyMatchInWorker(content, oldContent, signal)\n}\n\nfunction fuzzyMatchInWorker(\n content: string,\n oldContent: string,\n signal: AbortSignal,\n): Promise<FuzzyMatch> {\n return new Promise((resolve, reject) => {\n const worker = new Worker(new URL('./fuzzy-match-worker.ts', import.meta.url), {\n workerData: { content, oldContent },\n })\n\n const onAbort = () => {\n worker.terminate()\n reject(new DOMException('Aborted', 'AbortError'))\n }\n\n if (signal.aborted) {\n onAbort()\n return\n }\n signal.addEventListener('abort', onAbort, { once: true })\n\n worker.on('message', (result: FuzzyMatch) => {\n signal.removeEventListener('abort', onAbort)\n worker.terminate()\n resolve(result)\n })\n\n worker.on('error', (_err) => {\n signal.removeEventListener('abort', onAbort)\n worker.terminate()\n // 降级:worker 失败回主线程同步算\n resolve(fuzzyMatchPure(content, oldContent))\n })\n })\n}\n\n// ---- 旧定义已迁至 fuzzy-match-impl.ts(RFC-089 M5 规则2: 共享源码) ----\n\nexport function createEdit(projectRoot: string): AgentTool<EditArgs> {\n return {\n name: 'edit',\n description:\n 'Edit a file by replacing exact text. newContent fully replaces oldContent — never echo oldContent back inside newContent (that duplicates it), and never include diff +/- line prefixes.',\n parameters: EditArgsSchema,\n pathParams: ['path'],\n resolvePath: (raw) => resolve(projectRoot, raw),\n execute: async ({ params, signal }): Promise<ToolResult> => {\n const oldContent = params.oldContent\n const newContent = params.newContent\n\n if (oldContent === undefined) {\n throw toolError('Missing oldContent', 'validation')\n }\n\n if (newContent === undefined) {\n throw toolError('Missing newContent', 'validation')\n }\n\n const resolvedPath = resolve(projectRoot, params.path)\n const normalizedPath = normalizePath(resolvedPath)\n\n if (!(await exists(normalizedPath))) {\n throw toolError(`File not found: ${params.path}`, 'not_found')\n }\n\n if (!(await isFile(normalizedPath))) {\n throw toolError(`Not a file: ${params.path}`, 'validation')\n }\n\n return await edit(normalizedPath, oldContent, newContent, signal)\n },\n }\n}\n\nasync function edit(\n path: string,\n oldContent: string,\n newContent: string,\n signal: AbortSignal,\n): Promise<ToolResult> {\n const raw = await readFile(path, 'utf-8')\n\n if (signal.aborted) {\n throw toolError('Operation aborted', 'aborted')\n }\n\n const { bom, content: stripedContent } = raw.startsWith('\\uFEFF')\n ? { bom: '\\uFEFF', content: raw.slice(1) }\n : { bom: '', content: raw }\n\n const crlf = stripedContent.indexOf('\\r\\n')\n const lf = stripedContent.indexOf('\\n')\n\n let lineEnding: LineEnding = '\\n'\n if (lf === -1) {\n lineEnding = '\\n'\n } else if (crlf === -1) {\n lineEnding = '\\n'\n } else {\n lineEnding = crlf < lf ? '\\r\\n' : '\\n'\n }\n\n const normalizedContent = normalizeLineEndings(stripedContent)\n const normalizedOldContent = normalizeLineEndings(oldContent)\n const normalizedNewContent = normalizeLineEndings(newContent)\n\n const match = await fuzzyMatch(normalizedContent, normalizedOldContent, signal)\n\n if (!match.found) {\n return {\n content: [\n {\n type: 'text',\n text: `Could not find the specified text in ${path}. No changes were made.`,\n },\n ],\n }\n }\n\n if (match.occurrences > 1) {\n return {\n content: [\n {\n type: 'text',\n text: `Found ${match.occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\n },\n ],\n }\n }\n\n const updatedContent =\n normalizedContent.slice(0, match.index) +\n normalizedNewContent +\n normalizedContent.slice(match.index + match.matchLength)\n\n if (updatedContent === normalizedContent) {\n return {\n content: [\n {\n type: 'text',\n text: `No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,\n },\n ],\n }\n }\n\n const finalContent =\n bom + (lineEnding === '\\r\\n' ? updatedContent.replace(/\\n/g, '\\r\\n') : updatedContent)\n\n await writeFile(path, finalContent)\n\n return {\n content: [{ type: 'text', text: `Successfully replaced text in ${path}.` }],\n details: { diff: diff(normalizedContent, updatedContent) },\n }\n}\n","const BLOCKED_PROTOCOLS = new Set(['file:', 'ftp:', 'data:', 'javascript:'])\n\nconst BLOCKED_HOSTS = new Set([\n 'localhost',\n '127.0.0.1',\n '0.0.0.0',\n '[::1]',\n '::1',\n '::',\n 'metadata.google.internal',\n '169.254.169.254',\n])\n\nfunction parseIpv4(ip: string): number[] | null {\n const m = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(ip)\n if (!m) {\n return null\n }\n const octets = m.slice(1, 5).map(Number)\n return octets.every((n) => n >= 0 && n <= 255) ? octets : null\n}\n\nfunction isBlockedIpv4(octets: number[]): boolean {\n const [a = 0, b = 0] = octets\n return (\n a === 0 ||\n a === 10 ||\n a === 127 ||\n (a === 169 && b === 254) ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && b === 168) ||\n (a === 100 && b >= 64 && b <= 127)\n )\n}\n\nfunction expandIpv6(input: string): number[] | null {\n let h = input.split('%')[0]!.toLowerCase()\n if (!h.includes(':')) {\n return null\n }\n const dotted = /^(.*:)(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})$/.exec(h)\n if (dotted) {\n const v4 = parseIpv4(dotted[2]!)\n if (!v4) {\n return null\n }\n const hi = ((v4[0]! << 8) | v4[1]!).toString(16)\n const lo = ((v4[2]! << 8) | v4[3]!).toString(16)\n h = `${dotted[1]}${hi}:${lo}`\n }\n\n const halves = h.split('::')\n if (halves.length > 2) {\n return null\n }\n const toHextets = (segment: string): number[] =>\n segment === ''\n ? []\n : segment.split(':').map((x) => (/^[0-9a-f]{1,4}$/.test(x) ? parseInt(x, 16) : NaN))\n\n const left = toHextets(halves[0]!)\n const right = halves.length === 2 ? toHextets(halves[1]!) : []\n\n let hextets: number[]\n if (halves.length === 2) {\n const fill = 8 - left.length - right.length\n if (fill < 0) {\n return null\n }\n hextets = [...left, ...Array.from({ length: fill }, () => 0), ...right]\n } else {\n hextets = left\n }\n\n if (hextets.length !== 8 || hextets.some((x) => !Number.isInteger(x) || x < 0 || x > 0xffff)) {\n return null\n }\n return hextets\n}\n\nfunction isBlockedIpv6(x: number[]): boolean {\n if (x.every((h) => h === 0)) {\n return true\n }\n if (x.slice(0, 7).every((h) => h === 0) && x[7] === 1) {\n return true\n }\n if ((x[0]! & 0xffc0) === 0xfe80) {\n return true\n }\n if ((x[0]! & 0xfe00) === 0xfc00) {\n return true\n }\n const isMapped = x.slice(0, 5).every((h) => h === 0) && x[5] === 0xffff\n const isCompat = x.slice(0, 6).every((h) => h === 0) && (x[6] !== 0 || x[7] !== 0)\n if (isMapped || isCompat) {\n const v4 = [x[6]! >> 8, x[6]! & 0xff, x[7]! >> 8, x[7]! & 0xff]\n return isBlockedIpv4(v4)\n }\n if (x[0] === 0x2002) {\n return isBlockedIpv4([x[1]! >> 8, x[1]! & 0xff, x[2]! >> 8, x[2]! & 0xff])\n }\n if (x[0] === 0x0064 && x[1] === 0xff9b && x.slice(2, 6).every((h) => h === 0)) {\n return isBlockedIpv4([x[6]! >> 8, x[6]! & 0xff, x[7]! >> 8, x[7]! & 0xff])\n }\n if ((x[0]! & 0xff00) === 0xff00 || (x[0]! & 0xffc0) === 0xfec0) {\n return true\n }\n return false\n}\n\nfunction parsePackedIpv4(host: string): number[] | null {\n if (!/^\\d+$/.test(host)) {\n return null\n }\n const n = Number(host)\n if (!Number.isInteger(n) || n < 0 || n > 0xffffffff) {\n return null\n }\n return [(n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff]\n}\n\n/**\n * 一个 IP 字面量(IPv4 点分 / 打包整数 / IPv6 任意拼写,含 IPv4-mapped)是否落在\n * 私网/环回/链路本地/未指定段。**按解析后的字节判定**,不依赖字符串拼写(防 hex/压缩绕过)。\n */\nexport function isBlockedAddress(host: string): boolean {\n const h = host.replace(/^\\[|\\]$/g, '').toLowerCase()\n\n const v4 = parseIpv4(h)\n if (v4) {\n return isBlockedIpv4(v4)\n }\n\n const packed = parsePackedIpv4(h)\n if (packed) {\n return isBlockedIpv4(packed)\n }\n\n const v6 = expandIpv6(h)\n if (v6) {\n return isBlockedIpv6(v6)\n }\n\n return false\n}\n\nfunction isIpLiteral(host: string): boolean {\n return parseIpv4(host) !== null || parsePackedIpv4(host) !== null || host.includes(':')\n}\n\nexport function validateUrl(raw: string): URL {\n let url: URL\n try {\n url = new URL(raw)\n } catch {\n throw new Error(`Invalid URL: ${raw}`)\n }\n\n if (BLOCKED_PROTOCOLS.has(url.protocol)) {\n throw new Error(`Blocked protocol: ${url.protocol}`)\n }\n\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new Error(`Unsupported protocol: ${url.protocol}`)\n }\n\n const hostname = url.hostname.replace(/^\\[|\\]$/g, '')\n\n if (BLOCKED_HOSTS.has(hostname)) {\n throw new Error(`Blocked host: ${hostname}`)\n }\n\n if (isBlockedAddress(hostname)) {\n throw new Error(`Blocked private IP: ${hostname}`)\n }\n\n return url\n}\n\nexport type DnsResolver = (hostname: string) => Promise<string[]>\n\nconst defaultResolver: DnsResolver = async (hostname) => {\n const { lookup } = await import('node:dns/promises')\n const records = await lookup(hostname, { all: true })\n return records.map((r) => r.address)\n}\n\nexport interface UrlDnsOptions {\n /** 注入 DNS 解析(测试用)。缺省走 node:dns lookup(all)。 */\n resolve?: DnsResolver\n}\n\nexport interface PinnedTarget {\n address: string\n family: 4 | 6\n}\n\n/**\n * validateUrl + DNS 复查,并**返回已校验的 IP** 供连接 pin(堵 DNS-rebinding TOCTOU:校验与连接\n * 各自解析会被攻击者用不同应答骗过)。主机名是 IP 字面量时无 rebinding 面,address 为空(直连)。\n */\nexport async function validateAndResolveTarget(\n raw: string,\n options: UrlDnsOptions = {},\n): Promise<{ url: URL; pin?: PinnedTarget }> {\n const url = validateUrl(raw)\n const host = url.hostname.replace(/^\\[|\\]$/g, '')\n\n if (isIpLiteral(host)) {\n return { url }\n }\n\n const resolve = options.resolve ?? defaultResolver\n let addresses: string[]\n try {\n addresses = await resolve(host)\n } catch {\n throw new Error(`DNS resolution failed for ${host}`)\n }\n\n if (addresses.length === 0) {\n throw new Error(`DNS resolution returned no addresses for ${host}`)\n }\n for (const address of addresses) {\n if (isBlockedAddress(address)) {\n throw new Error(`Blocked resolved IP: ${address} (for ${host})`)\n }\n }\n\n const address = addresses[0]!\n return { url, pin: { address, family: address.includes(':') ? 6 : 4 } }\n}\n\n/**\n * validateUrl + DNS 复查:主机名为域名时解析其 A/AAAA,对每个解析 IP 复查私网段。\n * 主机名本身是 IP 字面量时 validateUrl 已覆盖,跳过 DNS。\n */\nexport async function validateUrlWithDns(raw: string, options: UrlDnsOptions = {}): Promise<URL> {\n return (await validateAndResolveTarget(raw, options)).url\n}\n\ntype FetchLike = (input: string, init: RequestInit) => Promise<Response>\n\nexport interface SafeFetchOptions extends UrlDnsOptions {\n headers?: Record<string, string>\n signal?: AbortSignal\n /** 最大重定向跳数(默认 5)。 */\n maxRedirects?: number\n /** 注入 fetch(测试用)。 */\n fetchImpl?: FetchLike\n}\n\n/**\n * 把连接 pin 到已校验 IP 的 fetch:走 node:http(s) 自定义 lookup 锁定 IP(防 rebinding——globalThis.fetch\n * 会自行再解析 DNS,给了攻击者第二次应答机会),TLS SNI/证书仍按 hostname 校验。返回真 Response\n * (Readable.toWeb 包 IncomingMessage),供既有 readBodyCapped/重定向逻辑无缝消费。\n * 限制:HTTP/1.1(node:http(s) 不走 h2);不自动跟随重定向(本就 redirect:manual)。\n */\nexport async function pinnedFetch(\n href: string,\n init: { headers?: Record<string, string>; signal?: AbortSignal },\n pin?: PinnedTarget,\n): Promise<Response> {\n const isHttps = new URL(href).protocol === 'https:'\n const [mod, { Readable }] = await Promise.all([\n isHttps ? import('node:https') : import('node:http'),\n import('node:stream'),\n ])\n\n return new Promise<Response>((resolve, reject) => {\n const req = mod.request(\n href,\n {\n method: 'GET',\n headers: init.headers,\n signal: init.signal,\n ...(pin\n ? {\n lookup: (\n _hostname: string,\n opts: { all?: boolean } | undefined,\n cb: (\n err: Error | null,\n address: string | Array<{ address: string; family: number }>,\n family?: number,\n ) => void,\n ) => {\n if (opts?.all) {\n cb(null, [{ address: pin.address, family: pin.family }])\n } else {\n cb(null, pin.address, pin.family)\n }\n },\n }\n : {}),\n },\n (im) => {\n const headers = new Headers()\n for (const [k, v] of Object.entries(im.headers)) {\n if (Array.isArray(v)) {\n for (const vv of v) headers.append(k, vv)\n } else if (v != null) {\n headers.set(k, v)\n }\n }\n const status = im.statusCode ?? 502\n const body =\n status === 204 || status === 304 ? null : (Readable.toWeb(im) as ReadableStream)\n resolve(new Response(body, { status, statusText: im.statusMessage ?? '', headers }))\n },\n )\n req.on('error', reject)\n req.end()\n })\n}\n\n/**\n * 带 SSRF 防护的 fetch:初始 URL + 每个重定向目标都过 validateAndResolveTarget;redirect 手动跟随。\n * 默认连接 pin 到已校验 IP(堵 DNS-rebinding TOCTOU);测试可注入 fetchImpl 绕过。\n */\nexport async function fetchWithSsrfGuard(\n rawUrl: string,\n options: SafeFetchOptions,\n): Promise<Response> {\n const maxRedirects = options.maxRedirects ?? 5\n\n let target = await validateAndResolveTarget(rawUrl, options)\n let redirects = 0\n\n for (;;) {\n const doFetch: FetchLike =\n options.fetchImpl ?? ((href, init) => pinnedFetch(href, init as never, target.pin))\n const response = await doFetch(target.url.href, {\n headers: options.headers,\n signal: options.signal,\n redirect: 'manual',\n })\n\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('location')\n if (!location) {\n return response\n }\n if (++redirects > maxRedirects) {\n throw new Error(`Too many redirects (> ${maxRedirects}) for ${rawUrl}`)\n }\n target = await validateAndResolveTarget(new URL(location, target.url).href, options)\n continue\n }\n\n return response\n }\n}\n","const HTML_ENTITIES: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '"': '\"',\n ''': \"'\",\n ''': \"'\",\n ' ': ' ',\n '–': '–',\n '—': '—',\n '«': '«',\n '»': '»',\n '©': '©',\n '®': '®',\n '™': '™',\n '…': '…',\n}\n\nfunction decodeHtmlEntities(text: string): string {\n let result = text.replace(/&[a-zA-Z]+;/g, (entity) => {\n return HTML_ENTITIES[entity] ?? entity\n })\n\n result = result.replace(/&#(\\d+);/g, (_, code) => {\n const num = parseInt(code, 10)\n return num > 0 && num < 0x10ffff ? String.fromCodePoint(num) : ''\n })\n\n result = result.replace(/&#x([0-9a-fA-F]+);/g, (_, code) => {\n const num = parseInt(code, 16)\n return num > 0 && num < 0x10ffff ? String.fromCodePoint(num) : ''\n })\n\n return result\n}\n\nexport function htmlToText(html: string): string {\n let text = html\n\n text = text.replace(/<script[\\s\\S]*?<\\/script>/gi, '')\n text = text.replace(/<style[\\s\\S]*?<\\/style>/gi, '')\n text = text.replace(/<noscript[\\s\\S]*?<\\/noscript>/gi, '')\n text = text.replace(/<svg[\\s\\S]*?<\\/svg>/gi, '')\n text = text.replace(/<!--[\\s\\S]*?-->/g, '')\n\n text = text.replace(/<h[1-6][^>]*>([\\s\\S]*?)<\\/h[1-6]>/gi, '\\n\\n## $1\\n\\n')\n text = text.replace(/<li[^>]*>([\\s\\S]*?)<\\/li>/gi, '\\n• $1')\n text = text.replace(/<br\\s*\\/?>/gi, '\\n')\n text = text.replace(/<\\/p>/gi, '\\n\\n')\n text = text.replace(/<\\/div>/gi, '\\n')\n text = text.replace(/<\\/tr>/gi, '\\n')\n text = text.replace(/<td[^>]*>/gi, '\\t')\n text = text.replace(/<th[^>]*>/gi, '\\t')\n text = text.replace(/<hr[^>]*\\/?>/gi, '\\n---\\n')\n text = text.replace(/<\\/blockquote>/gi, '\\n')\n text = text.replace(/<blockquote[^>]*>/gi, '\\n> ')\n\n text = text.replace(/<a[^>]+href=\"([^\"]*)\"[^>]*>([\\s\\S]*?)<\\/a>/gi, '$2 ($1)')\n\n text = text.replace(/<img[^>]+alt=\"([^\"]*)\"[^>]*\\/?>/gi, '[$1]')\n\n text = text.replace(/<[^>]+>/g, '')\n\n text = decodeHtmlEntities(text)\n\n text = text.replace(/\\n{3,}/g, '\\n\\n')\n text = text.replace(/[ \\t]+/g, ' ')\n text = text.replace(/^ +/gm, '')\n text = text.trim()\n\n return text\n}\n\nexport function htmlToMarkdown(html: string): string {\n let text = html\n\n text = text.replace(/<script[\\s\\S]*?<\\/script>/gi, '')\n text = text.replace(/<style[\\s\\S]*?<\\/style>/gi, '')\n text = text.replace(/<noscript[\\s\\S]*?<\\/noscript>/gi, '')\n text = text.replace(/<svg[\\s\\S]*?<\\/svg>/gi, '')\n text = text.replace(/<!--[\\s\\S]*?-->/g, '')\n\n text = text.replace(/<h1[^>]*>([\\s\\S]*?)<\\/h1>/gi, '\\n\\n# $1\\n\\n')\n text = text.replace(/<h2[^>]*>([\\s\\S]*?)<\\/h2>/gi, '\\n\\n## $1\\n\\n')\n text = text.replace(/<h3[^>]*>([\\s\\S]*?)<\\/h3>/gi, '\\n\\n### $1\\n\\n')\n text = text.replace(/<h4[^>]*>([\\s\\S]*?)<\\/h4>/gi, '\\n\\n#### $1\\n\\n')\n text = text.replace(/<h5[^>]*>([\\s\\S]*?)<\\/h5>/gi, '\\n\\n##### $1\\n\\n')\n text = text.replace(/<h6[^>]*>([\\s\\S]*?)<\\/h6>/gi, '\\n\\n###### $1\\n\\n')\n\n text = text.replace(/<strong[^>]*>([\\s\\S]*?)<\\/strong>/gi, '**$1**')\n text = text.replace(/<b[^>]*>([\\s\\S]*?)<\\/b>/gi, '**$1**')\n text = text.replace(/<em[^>]*>([\\s\\S]*?)<\\/em>/gi, '*$1*')\n text = text.replace(/<i[^>]*>([\\s\\S]*?)<\\/i>/gi, '*$1*')\n text = text.replace(/<code[^>]*>([\\s\\S]*?)<\\/code>/gi, '`$1`')\n text = text.replace(/<pre[^>]*>([\\s\\S]*?)<\\/pre>/gi, '\\n```\\n$1\\n```\\n')\n\n text = text.replace(/<a[^>]+href=\"([^\"]*)\"[^>]*>([\\s\\S]*?)<\\/a>/gi, '[$2]($1)')\n text = text.replace(/<img[^>]+alt=\"([^\"]*)\"[^>]+src=\"([^\"]*)\"[^>]*\\/?>/gi, '')\n text = text.replace(/<img[^>]+src=\"([^\"]*)\"[^>]*\\/?>/gi, '')\n\n text = text.replace(/<li[^>]*>([\\s\\S]*?)<\\/li>/gi, '\\n- $1')\n text = text.replace(/<br\\s*\\/?>/gi, '\\n')\n text = text.replace(/<\\/p>/gi, '\\n\\n')\n text = text.replace(/<hr[^>]*\\/?>/gi, '\\n---\\n')\n text = text.replace(/<\\/div>/gi, '\\n')\n text = text.replace(/<\\/tr>/gi, '\\n')\n text = text.replace(/<td[^>]*>/gi, ' | ')\n text = text.replace(/<th[^>]*>/gi, ' | ')\n text = text.replace(/<blockquote[^>]*>([\\s\\S]*?)<\\/blockquote>/gi, (_, content: string) => {\n return content\n .split('\\n')\n .map((line: string) => `> ${line}`)\n .join('\\n')\n })\n\n text = text.replace(/<[^>]+>/g, '')\n\n text = decodeHtmlEntities(text)\n\n text = text.replace(/\\n{3,}/g, '\\n\\n')\n text = text.replace(/[ \\t]+$/gm, '')\n text = text.trim()\n\n return text\n}\n","import { z } from 'zod'\nimport { TOOLS_MAX_OUTPUT_BYTES, TOOLS_EXECUTE_TIMEOUT_MS, TOOLS_MAX_FETCH_BYTES } from '../limits'\nimport { formatBytes, truncateTail, toTruncationMeta } from '@x-otto/shared'\nimport { fetchWithSsrfGuard } from './url-validator'\nimport { htmlToText, htmlToMarkdown } from './html-to-text'\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\n\nconst DEFAULT_USER_AGENT = 'OttoBot/1.0'\n\nconst WebFetchArgsSchema = z.object({\n url: z.string().describe('URL to fetch content from'),\n format: z\n .enum(['text', 'markdown', 'raw'])\n .optional()\n .default('text')\n .describe('Output format: text (cleaned, default), markdown, or raw HTML'),\n headers: z.record(z.string(), z.string()).optional().describe('Additional HTTP headers'),\n maxLength: z\n .number()\n .int()\n .min(1000)\n .max(500_000)\n .optional()\n .describe('Maximum output length in bytes (capped at the global output limit)'),\n})\n\ntype WebFetchArgs = z.infer<typeof WebFetchArgsSchema>\n\nexport function createWebFetch(_projectRoot: string): AgentTool<WebFetchArgs> {\n return {\n name: 'web_fetch',\n description: `Fetch a web page and return its content. Supports text (default), markdown, and raw HTML formats. Cannot render JavaScript-heavy pages (SPAs). Best for documentation, articles, and static pages. Output truncated to ${formatBytes(TOOLS_MAX_OUTPUT_BYTES)}.`,\n parameters: WebFetchArgsSchema,\n readonly: true,\n\n async execute({ params, signal }): Promise<ToolResult> {\n const response = await fetchWithSsrfGuard(params.url, {\n headers: {\n 'User-Agent': DEFAULT_USER_AGENT,\n Accept: 'text/html, application/json, text/plain, */*',\n ...params.headers,\n },\n signal: signal ?? AbortSignal.timeout(TOOLS_EXECUTE_TIMEOUT_MS),\n })\n\n if (!response.ok) {\n return {\n content: [\n {\n type: 'text',\n text: `HTTP ${response.status} ${response.statusText} for ${response.url || params.url}`,\n },\n ],\n isError: true,\n errorKind: 'network',\n }\n }\n\n const contentType = response.headers.get('content-type') ?? ''\n const contentLength = parseInt(response.headers.get('content-length') ?? '0', 10)\n\n const tooLarge = (bytes: number): ToolResult => ({\n content: [\n {\n type: 'text',\n text: `Response too large: ${formatBytes(bytes)} (limit: ${formatBytes(TOOLS_MAX_FETCH_BYTES)})`,\n },\n ],\n isError: true,\n errorKind: 'runtime',\n })\n\n if (contentLength > TOOLS_MAX_FETCH_BYTES) {\n return tooLarge(contentLength)\n }\n\n const body = await readBodyCapped(response, TOOLS_MAX_FETCH_BYTES)\n if (body.overflow) {\n return tooLarge(TOOLS_MAX_FETCH_BYTES)\n }\n\n const buffer = body.data\n const raw = new TextDecoder().decode(buffer)\n\n let output: string\n\n if (contentType.includes('application/json')) {\n try {\n output = JSON.stringify(JSON.parse(raw), null, 2)\n } catch {\n output = raw\n }\n } else if (contentType.includes('text/html')) {\n const format = params.format ?? 'text'\n output =\n format === 'raw' ? raw : format === 'markdown' ? htmlToMarkdown(raw) : htmlToText(raw)\n } else {\n output = raw\n }\n\n const maxOutputBytes = params.maxLength ?? TOOLS_MAX_OUTPUT_BYTES\n const truncation = truncateTail(output, {\n maxBytes: Math.min(maxOutputBytes, TOOLS_MAX_OUTPUT_BYTES),\n maxLines: Infinity,\n })\n\n let text = truncation.content\n if (truncation.truncated) {\n text += `\\n\\n(Content truncated. Showing ${formatBytes(truncation.outputBytes)} of ${formatBytes(truncation.totalBytes)})`\n }\n\n // RFC-170 M1:与 bash/read/grep/find 统一为\"无条件带元数据\"(此前仅 truncated 时带,\n // 造成消费方需判空分叉;元数据轻量,无条件携带不构成负担)。\n return {\n content: [{ type: 'text', text }],\n details: {\n url: response.url || params.url,\n contentType,\n size: buffer.byteLength,\n truncation: toTruncationMeta(truncation),\n },\n }\n },\n }\n}\n\nexport async function readBodyCapped(\n response: Response,\n maxBytes: number,\n): Promise<{ data: Uint8Array; overflow: boolean }> {\n const reader = response.body?.getReader()\n if (!reader) {\n const fallback = new Uint8Array(await response.arrayBuffer())\n return { data: fallback, overflow: fallback.byteLength > maxBytes }\n }\n\n const chunks: Uint8Array[] = []\n let total = 0\n for (;;) {\n const { done, value } = await reader.read()\n if (done) {\n break\n }\n if (value) {\n total += value.byteLength\n if (total > maxBytes) {\n await reader.cancel().catch(() => undefined)\n return { data: new Uint8Array(0), overflow: true }\n }\n chunks.push(value)\n }\n }\n\n const data = new Uint8Array(total)\n let offset = 0\n for (const chunk of chunks) {\n data.set(chunk, offset)\n offset += chunk.byteLength\n }\n return { data, overflow: false }\n}\n","import { z } from 'zod'\nimport { TOOLS_EXECUTE_TIMEOUT_MS } from '../limits'\nimport { htmlToText } from './html-to-text'\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\n\nconst BRAVE_SEARCH_URL = 'https://search.brave.com/search'\nconst BRAVE_API_URL = 'https://api.search.brave.com/res/v1/web/search'\nconst DEFAULT_USER_AGENT =\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36'\n\nexport interface SearchResult {\n title: string\n url: string\n snippet: string\n}\n\ntype FetchLike = (input: string, init?: RequestInit) => Promise<Response>\n\nexport interface WebSearchProvider {\n readonly id: string\n /** 结构化/官方 API=true;HTML 抓取(易随页面改版失效)=false → 输出标注不可靠。 */\n readonly reliable: boolean\n search(query: string, limit: number, signal?: AbortSignal): Promise<SearchResult[]>\n}\n\nconst WebSearchArgsSchema = z.object({\n query: z.string().min(1).max(500).describe('Search query'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(20)\n .optional()\n .default(10)\n .describe('Maximum number of results to return (default: 10)'),\n})\n\ntype WebSearchArgs = z.infer<typeof WebSearchArgsSchema>\n\nfunction parseSearchResults(html: string, limit: number): SearchResult[] {\n const results: SearchResult[] = []\n\n const resultBlocks = html.split(/data-type=\"web\"/)\n\n for (let i = 1; i < resultBlocks.length && results.length < limit; i++) {\n const block = resultBlocks[i]!\n\n const urlMatch = block.match(/<a\\s+href=\"(https?:\\/\\/[^\"]+)\"[^>]*class=\"[^\"]*svelte-/)\n if (!urlMatch) {\n continue\n }\n\n const url = urlMatch[1]!\n\n const titleAttrMatch = block.match(\n /class=\"title\\s+search-snippet-title[^\"]*\"[^>]*title=\"([^\"]*)\"/,\n )\n let title = ''\n if (titleAttrMatch) {\n title = titleAttrMatch[1]!\n } else {\n const titleInnerMatch = block.match(\n /class=\"title\\s+search-snippet-title[^\"]*\"[^>]*>([\\s\\S]*?)<\\/div>/,\n )\n if (titleInnerMatch) {\n title = htmlToText(titleInnerMatch[1]!).trim()\n }\n }\n\n const snippetMatch = block.match(\n /class=\"generic-snippet[^\"]*\"[\\s\\S]*?class=\"content[^\"]*\"[^>]*>([\\s\\S]*?)<\\/div>/,\n )\n const snippet = snippetMatch ? htmlToText(snippetMatch[1]!).trim() : ''\n\n if (url && title) {\n results.push({ title, url, snippet })\n }\n }\n\n return results\n}\n\nfunction formatResults(query: string, results: SearchResult[], reliable: boolean): string {\n if (results.length === 0) {\n return `No results found for: \"${query}\"`\n }\n\n const lines = [`Search results for: \"${query}\"\\n`]\n\n for (let i = 0; i < results.length; i++) {\n const r = results[i]!\n lines.push(`${i + 1}. ${r.title}`)\n lines.push(` ${r.url}`)\n if (r.snippet) {\n lines.push(` ${r.snippet}`)\n }\n lines.push('')\n }\n\n let text = lines.join('\\n').trim()\n if (!reliable) {\n text +=\n '\\n\\n(Note: results were scraped from search HTML and may be incomplete or out of date. ' +\n 'Set BRAVE_SEARCH_API_KEY to use the official Brave Search API for reliable results.)'\n }\n return text\n}\n\nexport class BraveApiSearchProvider implements WebSearchProvider {\n readonly id = 'brave-api'\n readonly reliable = true\n constructor(\n private readonly apiKey: string,\n private readonly fetchImpl: FetchLike = fetch,\n ) {}\n\n async search(query: string, limit: number, signal?: AbortSignal): Promise<SearchResult[]> {\n const params = new URLSearchParams({ q: query, count: String(limit) })\n const response = await this.fetchImpl(`${BRAVE_API_URL}?${params}`, {\n headers: { 'X-Subscription-Token': this.apiKey, Accept: 'application/json' },\n signal: signal ?? AbortSignal.timeout(TOOLS_EXECUTE_TIMEOUT_MS),\n })\n if (!response.ok) {\n throw new Error(`Brave Search API error: HTTP ${response.status}`)\n }\n const data = (await response.json()) as {\n web?: { results?: Array<{ title?: string; url?: string; description?: string }> }\n }\n const items = data.web?.results ?? []\n return items\n .slice(0, limit)\n .map((r) => ({ title: r.title ?? '', url: r.url ?? '', snippet: r.description ?? '' }))\n .filter((r) => r.url)\n }\n}\n\nexport class BraveHtmlSearchProvider implements WebSearchProvider {\n readonly id = 'brave-html'\n readonly reliable = false\n constructor(private readonly fetchImpl: FetchLike = fetch) {}\n\n async search(query: string, limit: number, signal?: AbortSignal): Promise<SearchResult[]> {\n const params = new URLSearchParams({ q: query, source: 'web' })\n const response = await this.fetchImpl(`${BRAVE_SEARCH_URL}?${params}`, {\n headers: {\n 'User-Agent': DEFAULT_USER_AGENT,\n Accept: 'text/html,application/xhtml+xml',\n 'Accept-Language': 'en-US,en;q=0.9',\n },\n redirect: 'follow',\n signal: signal ?? AbortSignal.timeout(TOOLS_EXECUTE_TIMEOUT_MS),\n })\n if (!response.ok) {\n throw new Error(`Search request failed: HTTP ${response.status}`)\n }\n return parseSearchResults(await response.text(), limit)\n }\n}\n\nexport interface WebSearchOptions {\n /** 直接注入 provider(测试/自定义后端)。优先于 env 推断。 */\n provider?: WebSearchProvider\n /** Brave API key(覆盖 env)。 */\n apiKey?: string\n fetchImpl?: FetchLike\n}\n\n/**\n * 选择搜索后端(M6-04):配置了 Brave API key(opts.apiKey 或 BRAVE_SEARCH_API_KEY)→ 官方 API;\n * 否则回退 HTML 抓取(标注不可靠)。env 可注入便于测试。\n */\nexport function resolveWebSearchProvider(\n opts: WebSearchOptions = {},\n env: Record<string, string | undefined> = process.env,\n): WebSearchProvider {\n if (opts.provider) {\n return opts.provider\n }\n const apiKey = opts.apiKey ?? env.BRAVE_SEARCH_API_KEY\n if (apiKey) {\n return new BraveApiSearchProvider(apiKey, opts.fetchImpl)\n }\n return new BraveHtmlSearchProvider(opts.fetchImpl)\n}\n\nexport function createWebSearch(\n _projectRoot: string,\n options: WebSearchOptions = {},\n): AgentTool<WebSearchArgs> {\n return {\n name: 'web_search',\n description:\n 'Search the web and return results with titles, URLs, and snippets. Use this to discover relevant pages before fetching them with web_fetch.',\n parameters: WebSearchArgsSchema,\n readonly: true,\n\n async execute({ params, signal }): Promise<ToolResult> {\n const { query, limit = 10 } = params\n const provider = resolveWebSearchProvider(options)\n\n let results: SearchResult[]\n try {\n results = await provider.search(query, limit, signal)\n } catch (error) {\n return {\n content: [\n {\n type: 'text',\n text: error instanceof Error ? error.message : 'Search request failed',\n },\n ],\n isError: true,\n errorKind: 'network',\n }\n }\n\n return {\n content: [{ type: 'text', text: formatResults(query, results, provider.reliable) }],\n details: {\n query,\n provider: provider.id,\n reliable: provider.reliable,\n resultCount: results.length,\n results,\n },\n }\n },\n }\n}\n","import type { ToolResult } from '@x-otto/interchange'\n\n/**\n * 缺组能力统一降级。orchestration 工具在其依赖的宿主回调未注入时,\n * 一律返回**统一形态**的 isError ToolResult——不再三套并存(throw / 各写一套 isError 文案 / stub)。\n * 文案统一为 `<tool> not available: this capability is not configured in the host[. <hint>]`,\n * 保留旧子串「<tool> not available」便于现有断言与模型识别。\n */\nexport function missingCapability(tool: string, hint?: string): ToolResult {\n const text = `${tool} not available: this capability is not configured in the host.${\n hint ? ` ${hint}` : ''\n }`\n // 缺组能力=运行时(宿主未注入回调);产生处定型,免下游字符串猜测。\n return { content: [{ type: 'text', text }], isError: true, errorKind: 'runtime' }\n}\n","/**\n * agent-call-description.ts —— declared agent profiles 的描述段构建器(RFC-302 共享模块)。\n *\n * RFC-302(委派单一入口):原 `buildAgentCallDescription` 把「探询句 + agents 段」揉在\n * 一个函数里,只服务 agent_call。合并后 agents 段由 task_delegate / agent_call 共用,\n * 探询句由各调用方自己的静态主体提供——本函数只产「agents 段」:空 profiles → '',\n * 非空 → 'Available agents:' 列表 + fuzzy match 提示(不带探询句,调用方拼接)。\n * M3 结案时 agent_call 工厂删除,本共享模块保留。\n */\n\ninterface AgentSummary {\n name: string\n description: string\n slot?: string\n readOnly?: boolean\n}\n\nconst MAX_VISIBLE = 8\nconst DESC_MAX = 80\n\n/**\n * 生成 declared agents 描述段(含 Available agents 列表 + fuzzy match 提示)。\n * 空 profiles → ''(调用方描述回退为自己的静态主体)。拼接约定:`静态主体 + (段 ? '\\n' + 段 : '')`,\n * 与旧 buildAgentCallDescription 非空分支输出字节一致(探询句\\nAvailable agents:…)。\n */\nexport function buildDelegationAgentListDescription(profiles: AgentSummary[]): string {\n if (profiles.length === 0) {\n return ''\n }\n\n const visible = profiles.slice(0, MAX_VISIBLE)\n const more =\n profiles.length > MAX_VISIBLE\n ? `… and ${profiles.length - MAX_VISIBLE} more — pass any name, fuzzy match applies.`\n : ''\n\n const lines = visible.map((p) => {\n const desc =\n p.description.length > DESC_MAX ? p.description.slice(0, DESC_MAX - 3) + '…' : p.description\n const locks: string[] = []\n if (p.slot) locks.push(`slot=${p.slot}`)\n if (p.readOnly) locks.push('read-only')\n const lockStr = locks.length > 0 ? ` (${locks.join(', ')})` : ''\n return `- **${p.name}**: ${desc}${lockStr}`\n })\n\n return ['Available agents:', ...lines, more, 'Pass the agent name — fuzzy match applies for unknown names.']\n .filter(Boolean)\n .join('\\n')\n}\n","import { z } from 'zod'\nimport { WORKFLOW_SLOTS } from '@x-otto/ai'\nimport { missingCapability } from './missing-capability'\nimport { buildDelegationAgentListDescription } from './agent-call-description'\n\nimport type { AgentTool, ToolResult, TodoItem } from '@x-otto/interchange'\nimport type { TaskDispatch, WriteTodos, CallAgent } from '@x-otto/orchestration-contracts'\nimport { checkDelegationDepth } from '@x-otto/orchestration-contracts'\nimport type { ForkRunner, RunTeam } from './delegation-runners'\n\nconst sessionIdField = z\n .string()\n .optional()\n .describe(\n 'Optional child session ID. When used with sticky mode, later calls can reuse the same child context.',\n )\n\nconst sessionModeField = z\n .enum(['ephemeral', 'sticky'])\n .optional()\n .default('ephemeral')\n .describe(\n 'Child session lifecycle. ephemeral deletes the child session after the task; sticky keeps it for later reuse.',\n )\n\nconst slotField = z\n .enum(WORKFLOW_SLOTS)\n .optional()\n .describe('Model slot to use for this task')\n\nconst DelegateTaskArgsSchema = z\n .object({\n prompt: z.string().describe('Task description to delegate (for mode=team, the input handed to the team)'),\n subagent: z.string().optional().describe('Agent name to delegate the task to (e.g. \"explore\"). Used by mode=sync/background.'),\n category: z\n .string()\n .optional()\n .describe(\n 'Task category — drives automatic model selection for the sub-agent. Use \"search\"/\"quick\" for shallow/high-volume work (routes to a cheaper, faster model) and \"deep\"/\"critique\"/\"review\" for work needing strong reasoning (routes to the powerful model). Set this on mode=sync/background whenever the task fits a category, even without a named subagent.',\n ),\n team: z\n .string()\n .optional()\n .describe('Declared team name (see .otto/teams/*.md). Required for mode=team.'),\n mode: z\n .enum(['sync', 'background', 'fork', 'team'])\n .optional()\n .default('sync')\n .describe(\n \"Execution mode. 'sync' (default): delegate to a sub-agent (subagent/category) in the shared workspace, returns inline — use for tracked subtasks that edit files or whose output you need now. 'background': same but detached and READ-ONLY (no edit/write/bash) — analysis/research only. 'fork': run a self-contained side quest in a fresh ISOLATED session that does NOT inherit your history/system-prompt/tools (put all needed context in prompt) — best for summarization, batch checks, in-context Q&A. 'team': run a declared multi-agent team (sequential/parallel/hierarchical/handoff/router) from .otto/teams/*.md on the prompt.\",\n ),\n sessionId: sessionIdField,\n sessionMode: sessionModeField,\n slot: slotField,\n model: z\n .string()\n .optional()\n .describe(\n 'Explicit model id chosen by capability strength (overrides slot/category). Pick only models marked usable by list_models; unauthenticated or rate-limit-risky models (e.g. subscription-OAuth Claude) are rejected and fall back. Prefer leaving this unset and using `category` so the host picks a usable model automatically.',\n ),\n /** 显式关联父级 write_todos 任务项;子代理完成后自动标记 done。 */\n taskIds: z\n .array(z.string())\n .optional()\n .describe(\n 'Task IDs from the parent write_todos list that this sub-agent is responsible for completing.',\n ),\n /**\n * RFC-302:治理开关(缺省 true)。false = 轻量探询语义(agent_call 字节级等价):\n * 不建档/不重试/恒 ephemeral/真同步,直连 callAgent 旁路。仅 mode=sync(缺省)可用,\n * 必须显式 subagent,禁 category/team/sessionId/sessionMode/taskIds。\n */\n tracked: z\n .boolean()\n .optional()\n .default(true)\n .describe(\n 'Host governance: true (default) = full task tracking (recorded, retryable, todo-sync). false = lightweight probe (no record/retry, ephemeral, synchronous) — use for quick agent opinions/reviews; requires explicit subagent name.',\n ),\n })\n .refine(\n (data) => {\n const mode = data.mode ?? 'sync'\n if (data.tracked === false) {\n // untracked 只消费 subagent/prompt/slot/model——其余参数会被 callAgent 静默忽略,显式拒绝(RFC-302 D2)。\n return (\n mode === 'sync' &&\n data.subagent !== undefined &&\n data.category === undefined &&\n data.team === undefined &&\n data.sessionId === undefined &&\n data.sessionMode === undefined &&\n data.taskIds === undefined\n )\n }\n if (mode === 'team') return data.team !== undefined\n if (mode === 'fork') return true\n return data.subagent !== undefined || data.category !== undefined\n },\n {\n message:\n 'mode=sync/background requires subagent or category; mode=team requires team; mode=fork needs only prompt; ' +\n 'tracked:false requires explicit subagent with mode=sync (no category/team/sessionId/sessionMode/taskIds)',\n },\n )\n\ntype DelegateTaskArgs = z.infer<typeof DelegateTaskArgsSchema>\n\n/**\n * 子代理完成后的 todo 自动同步。\n *\n * 规则(避免误伤):\n * - taskIds 显式指定 → 只标记这些项为 done。\n * - taskIds 未指定 + 恰好一个 in_progress → 标记该 in_progress 项为 done。\n * - 其余情况(0 或多 in_progress、writeTodos 未配置)→ 不自动同步,文本提示。\n * - background 模式不同步(终态未知)。\n * - 子代理失败不同步(保留 in_progress 供重试)。\n */\nasync function syncTodosOnSubagentComplete(\n writeTodos: WriteTodos | undefined,\n parentSessionId: string | undefined,\n taskIds: string[] | undefined,\n currentTodos: TodoItem[],\n): Promise<string | undefined> {\n if (!writeTodos) return undefined\n\n let targetIds: string[] | undefined\n if (taskIds && taskIds.length > 0) {\n targetIds = taskIds.filter((id) => currentTodos.some((t) => t.id === id && t.status !== 'done'))\n } else {\n const inProgress = currentTodos.filter((t) => t.status === 'in_progress')\n if (inProgress.length === 1) {\n targetIds = [inProgress[0]!.id]\n }\n }\n\n if (!targetIds || targetIds.length === 0) return undefined\n\n const updated = currentTodos.map((t) =>\n targetIds!.includes(t.id) ? { ...t, status: 'done' as const } : t,\n )\n\n const result = await writeTodos({\n action: 'update',\n todos: updated,\n sessionId: parentSessionId,\n })\n\n if (result.ok) {\n return `✓ auto-synced parent task list: ${targetIds.join(', ')} → done`\n }\n return undefined\n}\n\n/** mode=fork:吸收原 fork_call.execute——全新隔离 prompt-only 会话 + 委派深度护栏(单源 helper)。 */\nasync function runForkMode(\n fork: ForkRunner | undefined,\n prompt: string,\n depth: number | undefined,\n signal: AbortSignal | undefined,\n maxDelegationDepth: number,\n): Promise<ToolResult> {\n if (!fork) {\n return missingCapability('task_delegate (mode=fork)')\n }\n const { childDepth, exceeded } = checkDelegationDepth(depth, maxDelegationDepth)\n if (exceeded) {\n return {\n isError: true,\n errorKind: 'validation',\n content: [\n {\n type: 'text',\n text: `fork rejected: delegation depth ${childDepth} exceeds max ${maxDelegationDepth}`,\n },\n ],\n }\n }\n const result = await fork(prompt, childDepth, signal)\n if (result.ok) {\n return { content: [{ type: 'text', text: result.value || '(no output)' }] }\n }\n return {\n content: [{ type: 'text', text: `Fork failed: ${result.error}` }],\n isError: true,\n errorKind: 'runtime',\n }\n}\n\n/** mode=team:吸收原 team_run.execute——委派声明式多 agent team(topology 由 swarm 决)。 */\nasync function runTeamMode(\n runTeam: RunTeam | undefined,\n team: string | undefined,\n input: string,\n signal: AbortSignal | undefined,\n depth: number | undefined,\n): Promise<ToolResult> {\n if (!runTeam) {\n return missingCapability('task_delegate (mode=team)', 'Declare a team in .otto/teams/*.md.')\n }\n if (!team) {\n return {\n content: [{ type: 'text', text: 'mode=team requires a team name' }],\n isError: true,\n errorKind: 'validation',\n }\n }\n const result = await runTeam(team, input, signal, depth)\n if (!result.success) {\n return {\n content: [{ type: 'text', text: `Team \"${team}\" failed: ${result.error}` }],\n isError: true,\n errorKind: 'runtime',\n }\n }\n return {\n content: [{ type: 'text', text: result.output ?? '' }],\n details: { team, pattern: result.pattern },\n }\n}\n\nexport function createTaskDelegate(\n _workspaceDir: string,\n dispatch: TaskDispatch,\n writeTodos?: WriteTodos,\n fork?: ForkRunner,\n runTeam?: RunTeam,\n maxDelegationDepth = 1,\n callAgent?: CallAgent,\n profiles?: Array<{ name: string; description: string; preferredModelTier?: string }>,\n): AgentTool<DelegateTaskArgs> {\n // RFC-302 D4:eager 描述——静态主体 + profiles 非空时追加 agents 段(空回退静态文本,字节一致)。\n const seg = buildDelegationAgentListDescription(\n (profiles ?? []).map((p) => ({\n name: p.name,\n description: p.description,\n slot: p.preferredModelTier,\n })),\n )\n const description =\n 'Delegate work to a sub-agent, an isolated fork, or a declared team. mode=sync/background → sub-agent (provide subagent/category); mode=fork → fresh isolated self-contained session (prompt only, no inheritance); mode=team → declared multi-agent team (provide team). ' +\n 'tracked:false (sync only, explicit subagent) → lightweight probe: no task record, no retry, returns inline — use for quick agent opinions/reviews.' +\n (seg ? `\\n${seg}` : '')\n\n return {\n name: 'task_delegate',\n description,\n parameters: DelegateTaskArgsSchema,\n\n isConcurrencySafe: (input) =>\n input?.mode === 'fork' || input?.mode === 'team' || input?.sessionMode !== 'sticky',\n\n // RFC-144 采纳评估:task_delegate 是首个接入 lifecycleAsync 的真实工具——子代理\n // 执行天然分钟级(数十步工具调用),是全部内置工具中最大的单点阻塞源;且从主代理\n // 视角只读(子代理在自己的上下文干活,主代理只收文本摘要),无\"不可取消后台写入\"\n // 顾虑。仅 mode=sync 生效:background 本就立即返回;fork/team 语义各异,首期不动\n // (对照系 Claude Code v2.1.198 起 background subagent 为默认行为)。\n //\n // sticky 排除(独立评审 3c):sticky 子会话被异步化后,同 sessionId 的第二次调用\n // 若在第一次完成前发起,两个异步 executor 会并发写同一子会话(无代码级护栏)。\n // sticky 场景保持同步执行(其 isConcurrencySafe=false 本就进串行桶,语义一致)。\n lifecycleAsync: (input) =>\n (input?.mode === 'sync' || input?.mode === undefined) &&\n input?.sessionMode !== 'sticky' &&\n input?.tracked !== false,\n lifecycleAsyncNote:\n 'Note: the delegated sub-agent may edit files in the shared workspace while it runs. ' +\n 'Until you receive its completion notice, do not read or write paths its task may touch.',\n\n async execute({ params, depth, signal, sessionId }): Promise<ToolResult> {\n // RFC-302 D2/D3:untracked 分支置于 mode 分支之前(fail-closed——schema refine 在\n // 框架层已拒非法组合,此处为路由层防御,防绕过 refine 的直调)。字节级对齐 agent_call:\n // 走 callAgent 旁路,不经 dispatch。\n if (params.tracked === false) {\n const mode = params.mode ?? 'sync'\n const illegal =\n mode !== 'sync' ||\n params.category !== undefined ||\n params.team !== undefined ||\n params.sessionId !== undefined ||\n params.sessionMode !== undefined ||\n params.taskIds !== undefined ||\n params.subagent === undefined\n if (illegal) {\n return {\n isError: true,\n errorKind: 'validation',\n content: [\n {\n type: 'text',\n text: 'tracked:false requires explicit subagent with mode=sync (no category/team/sessionId/sessionMode/taskIds)',\n },\n ],\n }\n }\n if (!callAgent) {\n return missingCapability('task_delegate (tracked:false)')\n }\n const result = await callAgent(params.subagent!, params.prompt, {\n slot: params.slot,\n model: params.model,\n depth,\n signal,\n })\n if (result.ok) {\n return { content: [{ type: 'text', text: result.value || '(no output)' }] }\n }\n return {\n content: [{ type: 'text', text: `Agent call failed: ${result.error}` }],\n isError: true,\n errorKind: 'runtime',\n }\n }\n // fork/team 吸收为 mode——绕 orchestrator 的轻量旁路(语义与原 fork_call/team_run 字节一致)。\n if (params.mode === 'fork') {\n return runForkMode(fork, params.prompt, depth, signal, maxDelegationDepth)\n }\n if (params.mode === 'team') {\n return runTeamMode(runTeam, params.team, params.prompt, signal, depth)\n }\n\n if (!dispatch) {\n return missingCapability('task_delegate')\n }\n\n let currentTodos: TodoItem[] = []\n if (params.mode !== 'background' && writeTodos) {\n const snapshot = await writeTodos({ action: 'update', todos: [], sessionId })\n if (snapshot.ok) currentTodos = snapshot.value\n }\n\n let augmentedPrompt = params.prompt\n if (params.taskIds && params.taskIds.length > 0) {\n const ids = params.taskIds.map((id) => `\"${id}\"`).join(', ')\n augmentedPrompt = `You are responsible for completing parent task item(s): ${ids}. When done, your host will auto-sync these to \"done\". No need to call write_todos for parent items.\\n\\n${params.prompt}`\n }\n\n const result = await dispatch({\n prompt: augmentedPrompt,\n subagent: params.subagent,\n category: params.category,\n mode: params.mode,\n sessionId: params.sessionId,\n sessionMode: params.sessionMode,\n slot: params.slot,\n model: params.model,\n depth,\n signal,\n })\n\n if (result.ok) {\n const { id, status, output } = result.value\n const isBackground = params.mode === 'background'\n\n const todoNote = !isBackground\n ? await syncTodosOnSubagentComplete(writeTodos, sessionId, params.taskIds, currentTodos)\n : undefined\n\n const textParts: string[] = []\n if (isBackground) {\n textParts.push(`Task delegated in background${id ? `: ${id}` : ''}`)\n } else {\n textParts.push(`Task delegated successfully`)\n }\n if (output) textParts.push(output)\n if (todoNote) textParts.push(todoNote)\n\n return {\n content: [{ type: 'text', text: textParts.join('\\n') }],\n details: {\n mode: params.mode,\n status,\n sessionId: params.sessionId,\n sessionMode: params.sessionMode,\n ...(todoNote ? { todoSync: todoNote } : {}),\n },\n }\n }\n\n return {\n content: [{ type: 'text', text: `Task delegation failed: ${result.error}` }],\n isError: true,\n errorKind: 'runtime',\n }\n },\n }\n}\n","import { z } from 'zod'\nimport { missingCapability } from './missing-capability'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\n\n/**\n * delegate_job —— RFC-056 / task-delegate-job-unification Phase 2②:模型触发的**隔离可变作业**。\n *\n * 与 task_delegate 的分工(按结果契约):\n * - task_delegate(sync) 共享 FS、内联返回文本——当前对话里的子任务。\n * - task_delegate(background) 只读、异步产文本——后台分析/调研(Phase 0 收紧)。\n * - delegate_job worktree 隔离、产**待审 diff**——模型想改文件但要隔离 + 审计。\n *\n * 默认(autonomy L0)下:作业产出 diff,经既有通知注入\"待审\",由人 `/job apply` 落地——\n * 即\"模型触发版 /job\"。自动落地需 autonomy≥L2 + M89 接线(RFC-056),默认关闭。\n *\n * **fork-bomb 防护**:detached 作业是全新 agent(depth=0),深度护栏挡不住跨作业递归——\n * 故作业 agent 的工具集**不含** delegate_job(在 coding/app/agent-job-wiring 过滤)。本工具\n * 只在主会话/子会话可见,不在作业内可见。\n */\n\nconst DelegateJobArgsSchema = z.object({\n prompt: z\n .string()\n .describe(\n 'Self-contained instruction for the background job. It runs in an isolated git worktree with full tools (incl. file edits + MCP), so include all needed context.',\n ),\n title: z\n .string()\n .optional()\n .describe('Short label for the job (shown in the /jobs panel). Defaults to a slice of the prompt.'),\n})\n\ntype DelegateJobArgs = z.infer<typeof DelegateJobArgsSchema>\n\n/** 起一个隔离作业的回调(app 注入:→ agentJobs.start({origin:'main'}))。同步返回作业 id。 */\nexport type StartJob = (input: { prompt: string; title: string; sessionId: string }) => { jobId: string }\n\nexport function createDelegateJob(startJob?: StartJob): AgentTool<DelegateJobArgs> {\n return {\n name: 'delegate_job',\n description:\n 'Delegate a file-changing task to an isolated background job. Runs in a separate git worktree with full tools (edits + MCP), then produces a REVIEWABLE DIFF — it does NOT touch your working tree directly. Use for independent changes you want isolated and audited (e.g. a self-contained refactor) while you keep working. The diff is applied by the user via /job apply (autonomous apply is off by default). For read-only background analysis use task_delegate(background); for in-conversation edits use task_delegate(sync).',\n parameters: DelegateJobArgsSchema,\n\n async execute({ params, sessionId }): Promise<ToolResult> {\n if (!startJob) {\n return missingCapability('delegate_job')\n }\n if (!sessionId) {\n return {\n isError: true,\n errorKind: 'validation',\n content: [{ type: 'text', text: 'delegate_job requires a session context (no sessionId available).' }],\n }\n }\n const title = params.title?.trim() || params.prompt.replace(/\\s+/g, ' ').slice(0, 48)\n const { jobId } = startJob({ prompt: params.prompt, title, sessionId })\n return {\n content: [\n {\n type: 'text',\n text:\n `Delegated background job ${jobId} (isolated worktree). It will produce a reviewable diff; ` +\n `you'll get a notification when it's ready. The user applies it with /job apply ${jobId} ` +\n `(or reviews it in the /jobs panel). It does not modify the current working tree.`,\n },\n ],\n details: { jobId, origin: 'main' },\n }\n },\n }\n}\n","import { z } from 'zod'\nimport { missingCapability } from './missing-capability'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\n\n/**\n * capability_gap —— RFC-287 M4:自迭代回路的**认知入口**。\n *\n * 让模型在\"我做不到这件事\"时,能主动查生态里有没有现成能力、没有则拿到造一个的蓝图,\n * 而不是简单地回一句\"我不支持\"。这是 otto 作为开放 Agent 容器的关键一环:能力面不再\n * 只能由人类预先装好,模型可以参与发现与补齐。\n *\n * ## 无副作用契约(R1 零豁免的结构性保证,不是保守取舍)\n *\n * 本工具**只返回文本引导,绝不执行安装**。理由是安全而非谨慎:\n * - 工具无任何写盘/装载副作用 → 结构上不存在\"模型自己把插件装上了\"的路径;\n * - 用户执行 `otto extension install` 时走的是既有 `runPluginInstall` —— 高危能力确认\n * 弹窗 → `trustPlugin`/`grantPluginCapabilities`,与人类第三方插件**完全同一条路径**;\n * - **不新增任何 scope/来源标记**来区分\"模型生成的插件\"。RFC-229 §10 的事故(借\n * `builtin` scope 绕过信任门)已经证明:任何为自动化开的旁路,最终都会变成提权通道。\n *\n * 因此\"模型能造插件\"与\"模型能给自己装插件\"是两件事——本里程碑只做前者。\n */\n\nconst CapabilityGapArgsSchema = z.object({\n need: z\n .string()\n .min(1)\n .describe(\n 'What capability is missing, in plain language (e.g. \"query Jira issue status\"). Describe the user-facing need, not an implementation.',\n ),\n searchTerms: z\n .array(z.string().min(1))\n .min(1)\n .describe(\n 'Keywords to search the plugin ecosystem with (e.g. [\"jira\", \"issue tracker\"]). Use 2-4 distinct terms; a single narrow term often misses existing plugins.',\n ),\n})\n\ntype CapabilityGapArgs = z.infer<typeof CapabilityGapArgsSchema>\n\n/** registry 命中项(宿主侧检索后回传的最小投影)。 */\nexport interface CapabilityGapHit {\n id: string\n description: string\n /** 能力预披露(registry 声明值,非执法源——真实执法在安装时的 manifest 信任门)。 */\n capabilities?: string[]\n sourceName: string\n /** registry 缓存已过期(结果可能不是最新)。 */\n stale?: boolean\n}\n\n/**\n * 宿主注入的生态检索能力。返回 `undefined` 表示 registry 不可达(网络/配置问题)——\n * 与\"检索到 0 条\"是不同语义,前者要如实告知模型\"没查成\"而非\"生态里没有\"。\n */\nexport type SearchEcosystem = (\n searchTerms: string[],\n) => Promise<{ hits: CapabilityGapHit[]; warnings: string[] } | undefined>\n\n/**\n * 会话级去重键:searchTerms 归一化(小写 + 去重 + 排序 + 连接)。\n *\n * 用 searchTerms 而非 `need` 做键——`need` 是自由文本,同一缺口每轮措辞都可能不同,\n * 用它去重等于不去重;searchTerms 是模型给出的检索意图,重复上报时高度稳定。\n */\nexport function capabilityGapDedupKey(searchTerms: string[]): string {\n return [...new Set(searchTerms.map((t) => t.trim().toLowerCase()).filter(Boolean))].sort().join('|')\n}\n\n/** 由缺口描述推导一个合法的插件目录/包名(scaffold 引导用)。 */\nfunction suggestPluginName(need: string): string {\n const slug = need\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .split('-')\n .filter(Boolean)\n .slice(0, 3)\n .join('-')\n return `plugin-${slug || 'custom'}`\n}\n\nexport function createCapabilityGap(searchEcosystem?: SearchEcosystem): AgentTool<CapabilityGapArgs> {\n /**\n * 会话级已上报缺口。防止模型每轮重复上报同一缺口造成引导噪音爆炸——重复时只回精简提示。\n * 生命周期与工具实例一致(每会话装配一次),不跨会话泄漏。\n */\n const reported = new Set<string>()\n\n return {\n name: 'capability_gap',\n description:\n 'Report a capability you lack and discover whether the plugin ecosystem already provides it. ' +\n 'Use this when the user asks for something you genuinely cannot do with your current tools — ' +\n 'instead of only saying \"I can\\'t\", check whether a plugin exists, and if not, get a blueprint for building one. ' +\n 'This tool only returns guidance: it never installs anything (installation always requires explicit user approval).',\n parameters: CapabilityGapArgsSchema,\n\n async execute({ params }): Promise<ToolResult> {\n if (!searchEcosystem) {\n return missingCapability('capability_gap')\n }\n\n const dedupKey = capabilityGapDedupKey(params.searchTerms)\n const isRepeat = reported.has(dedupKey)\n reported.add(dedupKey)\n\n if (isRepeat) {\n return {\n content: [\n {\n type: 'text',\n text:\n `Already reported this capability gap earlier in the session (search terms: ${params.searchTerms.join(', ')}). ` +\n `Don't re-report it — either act on the earlier guidance, or tell the user plainly that this capability is unavailable.`,\n },\n ],\n details: { deduped: true },\n }\n }\n\n const result = await searchEcosystem(params.searchTerms)\n\n // registry 不可达 —— 与\"0 命中\"严格区分:不能因为没查成就告诉模型\"生态里没有\"。\n if (!result) {\n const name = suggestPluginName(params.need)\n return {\n content: [\n {\n type: 'text',\n text:\n `Could not reach the plugin registry (network or configuration issue), so it is unknown whether ` +\n `an existing plugin provides \"${params.need}\".\\n\\n` +\n `You can still offer to build it: ask the user whether they want a plugin scaffolded, then\\n` +\n ` otto extension create ${name} --with-tool\\n` +\n `and use delegate_job to implement it in an isolated worktree (produces a reviewable diff).`,\n },\n ],\n details: { suggestion: 'generate', registryReachable: false },\n }\n }\n\n const { hits, warnings } = result\n const warningText = warnings.length > 0 ? `\\n\\nRegistry warnings:\\n${warnings.map((w) => `- ${w}`).join('\\n')}` : ''\n\n if (hits.length > 0) {\n const lines = hits.map((h) => {\n const caps = h.capabilities?.length ? ` [declares: ${h.capabilities.join(', ')}]` : ''\n const stale = h.stale ? ' (cached index may be out of date)' : ''\n return `- ${h.id} — ${h.description}${caps} (from ${h.sourceName})${stale}\\n install: otto extension install ${h.id}`\n })\n return {\n content: [\n {\n type: 'text',\n text:\n `Found ${hits.length} existing plugin(s) that may provide \"${params.need}\":\\n\\n` +\n `${lines.join('\\n')}\\n\\n` +\n `Tell the user what you found and let THEM run the install command — you cannot install it yourself, ` +\n `and installing high-risk capabilities requires their explicit approval.` +\n warningText,\n },\n ],\n details: { suggestion: 'install', hits: hits.length },\n }\n }\n\n const name = suggestPluginName(params.need)\n return {\n content: [\n {\n type: 'text',\n text:\n `No existing plugin in the ecosystem provides \"${params.need}\".\\n\\n` +\n `You can offer to build one. Blueprint:\\n` +\n ` 1. Ask the user whether they want this capability added as a plugin.\\n` +\n ` 2. Scaffold it: otto extension create ${name} --with-tool\\n` +\n ` 3. Implement it with delegate_job (isolated worktree, produces a reviewable diff) — ` +\n `describe the tool's name, parameters, and behaviour in the job prompt.\\n` +\n ` 4. The user reviews the diff (/job apply), then installs it — installation and any ` +\n `high-risk capability grants always go through their explicit approval.` +\n warningText,\n },\n ],\n details: { suggestion: 'generate', hits: 0 },\n }\n },\n }\n}\n","import { z } from 'zod'\nimport { missingCapability } from './missing-capability'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport type { GetTask, ListTasks, GetBackgroundOutput } from '@x-otto/orchestration-contracts'\n\nconst TaskInspectArgsSchema = z.object({\n action: z\n .enum(['get', 'list', 'output'])\n .describe('get task by ID, list all tasks, or get background task output'),\n taskId: z.string().optional().describe('Task ID (required for get/output actions)'),\n statusFilter: z\n .enum(['all', 'pending', 'running', 'completed', 'failed', 'cancelled'])\n .optional()\n .default('all')\n .describe('Filter by status (list action only)'),\n})\n\ntype TaskInspectArgs = z.infer<typeof TaskInspectArgsSchema>\n\nexport interface TaskInspectOptions {\n get?: GetTask\n list?: ListTasks\n output?: GetBackgroundOutput\n}\n\nexport function createTaskInspect(\n _projectRoot: string,\n options: TaskInspectOptions,\n): AgentTool<TaskInspectArgs> {\n const { get, list, output } = options\n\n return {\n name: 'task_inspect',\n description:\n 'Inspect tasks: get details by ID, list with optional status filter, or get background task output.',\n parameters: TaskInspectArgsSchema,\n readonly: true,\n\n async execute({ params }): Promise<ToolResult> {\n switch (params.action) {\n case 'get': {\n if (!get) return missingCapability('task_inspect(get)')\n if (!params.taskId) {\n return {\n content: [{ type: 'text', text: 'taskId is required for get action' }],\n isError: true,\n errorKind: 'validation',\n }\n }\n const result = await get(params.taskId)\n if (!result.ok) {\n return { content: [{ type: 'text', text: result.error }], isError: true, errorKind: 'runtime' }\n }\n const task = result.value\n const isFailure = task.status === 'failed' || Boolean(task.error)\n const text = [\n `Task: ${task.id}`,\n `Status: ${task.status}`,\n task.prompt ? `Prompt: ${task.prompt}` : undefined,\n `Output: ${task.output ?? 'N/A'}`,\n task.error ? `Error: ${task.error}` : undefined,\n ]\n .filter((line): line is string => Boolean(line))\n .join('\\n')\n return { content: [{ type: 'text', text }], isError: isFailure, details: { task } }\n }\n\n case 'list': {\n if (!list) return missingCapability('task_inspect(list)')\n const filter = params.statusFilter === 'all' ? undefined : params.statusFilter\n const result = await list(filter)\n if (result.ok) {\n const tasks = result.value\n if (tasks.length === 0) {\n return { content: [{ type: 'text', text: 'No tasks found.' }] }\n }\n const text = tasks\n .map((t) => `- [${t.status}] ${t.id}: ${t.prompt.slice(0, 80)}`)\n .join('\\n')\n return { content: [{ type: 'text', text }] }\n }\n throw new Error(result.error)\n }\n\n case 'output': {\n if (!output) return missingCapability('task_inspect(output)')\n if (!params.taskId) {\n return {\n content: [{ type: 'text', text: 'taskId is required for output action' }],\n isError: true,\n errorKind: 'validation',\n }\n }\n const result = await output(params.taskId)\n if (result.ok) {\n const { status, output: text } = result.value\n return {\n content: [\n { type: 'text', text: `Status: ${status}\\nOutput:\\n${text ?? '(no output)'}` },\n ],\n details: { status },\n }\n }\n return { content: [{ type: 'text', text: result.error }], isError: true, errorKind: 'runtime' }\n }\n }\n },\n }\n}\n","import { z } from 'zod'\nimport { missingCapability } from './missing-capability'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport type { UpdateTask, CancelBackground } from '@x-otto/orchestration-contracts'\n\nconst TaskControlArgsSchema = z.object({\n action: z.enum(['cancel', 'retry']).describe('cancel a running task, or retry a failed task'),\n taskId: z.string().describe('Task ID to act on'),\n})\n\ntype TaskControlArgs = z.infer<typeof TaskControlArgsSchema>\n\nexport interface TaskControlOptions {\n update?: UpdateTask\n cancelBackground?: CancelBackground\n}\n\nexport function createTaskControl(\n _projectRoot: string,\n options: TaskControlOptions,\n): AgentTool<TaskControlArgs> {\n const { update, cancelBackground } = options\n\n return {\n name: 'task_control',\n description: 'Control tasks: cancel a running task or retry a failed one.',\n parameters: TaskControlArgsSchema,\n\n async execute({ params }): Promise<ToolResult> {\n switch (params.action) {\n case 'cancel': {\n if (update) {\n const result = await update(params.taskId, 'cancel')\n if (result.ok) {\n return { content: [{ type: 'text', text: result.value }] }\n }\n return { content: [{ type: 'text', text: result.error }], isError: true, errorKind: 'runtime' }\n }\n if (cancelBackground) {\n const result = await cancelBackground(params.taskId)\n if (result.ok) {\n return {\n content: [{ type: 'text', text: `Task ${params.taskId} cancelled successfully` }],\n }\n }\n return { content: [{ type: 'text', text: result.error }], isError: true, errorKind: 'runtime' }\n }\n return missingCapability('task_control(cancel)')\n }\n\n case 'retry': {\n if (!update) return missingCapability('task_control(retry)')\n const result = await update(params.taskId, 'retry')\n if (result.ok) {\n return { content: [{ type: 'text', text: result.value }] }\n }\n return { content: [{ type: 'text', text: result.error }], isError: true, errorKind: 'runtime' }\n }\n }\n },\n }\n}\n","/**\n * list-models.ts — 列出当前会话可用模型 + 能力优势,供大模型为 subagent 选型。\n *\n * 只读。返回的「可用集合」跟随会话配置(持久化、--continue 回灌)——见 host 注入的 ModelCatalogQuery。\n * 模型来自实时拉取 + 缓存(model-fetch / ModelCatalogStore),非内置硬编码默认。\n */\nimport { z } from 'zod'\nimport { missingCapability } from './missing-capability'\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\n\nexport interface ModelCatalogEntry {\n id: string\n provider: string\n /** 能力优势标签(planning/knowledge/coding/reasoning/vision/speed/long-context)。 */\n strengths: string[]\n /** 是否经实时拉取确认「当前账号可用」(会话配置内)。 */\n available: boolean\n /**\n * 是否通过 usability gate(已认证 + 非 429 风险)——subagent 选型应只从 usable!==false 中选。\n * 缺省(undefined)视为可用(host 未提供 gate 信息时向后兼容)。\n */\n usable?: boolean\n /** usable===false 时的原因(unauthenticated / oauth-429-risk),供 LLM 理解为何不可选。 */\n unusableReason?: string\n}\n\n/** host 注入:按 sessionId 返回该会话的模型目录(含会话级可用集合 + 可用性)。可同步或异步。 */\nexport type ModelCatalogQuery = (\n sessionId?: string,\n) => ModelCatalogEntry[] | Promise<ModelCatalogEntry[]>\n\nconst ListModelsArgsSchema = z.object({\n availableOnly: z\n .boolean()\n .optional()\n .describe('Only list models usable by the current account (authenticated, not rate-limit-risky) — recommended before choosing a subagent model'),\n})\n\ntype ListModelsArgs = z.infer<typeof ListModelsArgsSchema>\n\nexport function createListModels(query?: ModelCatalogQuery, sessionId?: string): AgentTool<ListModelsArgs> {\n return {\n name: 'list_models',\n description:\n 'List models with their capability strengths (planning/knowledge/coding/reasoning/vision/speed/long-context) and usability. '\n + 'Use this to choose the best model for a sub-agent via the `model` param of task_delegate. '\n + 'Only pick models marked ✓ (usable); ✗ models are unauthenticated or rate-limit-risky and will be rejected.',\n parameters: ListModelsArgsSchema,\n readonly: true,\n isConcurrencySafe: () => true,\n\n async execute({ params }): Promise<ToolResult> {\n if (!query) return missingCapability('list_models')\n let entries = await query(sessionId)\n if (params.availableOnly) entries = entries.filter((e) => e.usable !== false)\n if (entries.length === 0) {\n return { content: [{ type: 'text', text: 'No usable models. Run /login or /model refresh first.' }] }\n }\n const lines = entries.map((e) => {\n const mark = e.usable === false ? '✗' : '✓'\n const note = e.usable === false && e.unusableReason ? ` (${e.unusableReason})` : ''\n return `${mark} ${e.id} [${e.strengths.join('/') || 'general'}]${note}`\n })\n return {\n content: [\n { type: 'text', text: `Models (✓ = usable, ✗ = unauthenticated/rate-limit-risky):\\n${lines.join('\\n')}` },\n ],\n }\n },\n }\n}\n","import { z } from 'zod'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport type { WriteTodos } from '@x-otto/orchestration-contracts'\nimport { createTodoStore } from '@x-otto/orchestration-contracts'\n\nconst TodoItemSchema = z.object({\n id: z.string().describe('Unique todo identifier'),\n title: z.string().describe('Short description of the task'),\n status: z\n .enum(['pending', 'in_progress', 'done', 'skipped', 'failed'])\n .describe('Current status'),\n})\n\nconst WriteTodosArgsSchema = z.object({\n title: z\n .string()\n .optional()\n .describe(\n 'Optional title for the todo list (e.g. \"Bug fixes\", \"Feature implementation\"). Defaults to \"Task\" if not provided.',\n ),\n action: z.enum(['set', 'update']).describe('set: replace all todos; update: merge by id'),\n todos: z.array(TodoItemSchema).describe('Todo items'),\n})\n\ntype WriteTodosArgs = z.infer<typeof WriteTodosArgsSchema>\n\nexport function createWriteTodos(writeTodos?: WriteTodos): AgentTool<WriteTodosArgs> {\n // 单源实现(终局架构 review O2)——未注入宿主回调时兜底走同一份 createTodoStore(),\n // 消除此前 orchestrator.ts 与本文件各自手写一份等价 merge 逻辑的重复维护。\n const handler = writeTodos ?? createTodoStore()\n\n return {\n name: 'write_todos',\n description:\n 'Track progress with a lightweight todo list (shown pinned above the input). Use \"set\" to replace all todos, \"update\" to merge changes by id. ' +\n 'Set a meaningful \"title\" to label the task list (e.g. \"Bug fixes\", \"Feature implementation\"). Defaults to \"Task\" if omitted. ' +\n 'Use for multi-step tasks (3+ steps). Keep EXACTLY ONE item in_progress at a time. ' +\n 'Mark an item done the moment it finishes (do not batch). Prune items no longer relevant by removing them from the list.',\n parameters: WriteTodosArgsSchema,\n\n async execute({ params, sessionId }): Promise<ToolResult> {\n const result = await handler({\n title: params.title,\n action: params.action,\n todos: params.todos,\n sessionId,\n })\n\n if (result.ok) {\n const todos = result.value\n const summary = todos.map((t) => `[${t.status}] ${t.id}: ${t.title}`).join('\\n')\n\n return {\n content: [{ type: 'text', text: summary || '(empty todo list)' }],\n details: { todos },\n }\n }\n\n return {\n content: [{ type: 'text', text: 'Failed to update todos' }],\n isError: true,\n errorKind: 'runtime',\n }\n },\n }\n}\n","import { z } from 'zod'\nimport { missingCapability } from './missing-capability'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\n\nconst CaptureFileStateArgsSchema = z.object({\n plan_status: z.string().optional().describe('Current plan execution status summary'),\n recent_files: z.array(z.string()).optional().describe('Recently modified file paths to focus on'),\n findings: z.array(z.string()).optional().describe('Key findings or observations to record'),\n})\n\ntype CaptureFileStateArgs = z.infer<typeof CaptureFileStateArgsSchema>\n\nexport type CaptureFileState = (args: {\n planStatus?: string\n recentFiles?: string[]\n findings?: string[]\n}) => Promise<{\n success: boolean\n summary: string\n timestamp: number\n}>\n\nexport function createCaptureFileState(\n captureFileState?: CaptureFileState,\n): AgentTool<CaptureFileStateArgs> {\n return {\n name: 'capture_file_state',\n description: 'Capture a snapshot of the current workspace file state.',\n parameters: CaptureFileStateArgsSchema,\n readonly: true,\n\n async execute({ params }): Promise<ToolResult> {\n if (!captureFileState) {\n return missingCapability('capture_file_state')\n }\n\n const result = await captureFileState({\n planStatus: params.plan_status,\n recentFiles: params.recent_files,\n findings: params.findings,\n })\n\n if (result.success) {\n return {\n content: [{ type: 'text', text: result.summary }],\n details: { timestamp: result.timestamp },\n }\n }\n\n return {\n content: [{ type: 'text', text: 'Failed to capture file state' }],\n isError: true,\n errorKind: 'io',\n }\n },\n }\n}\n","import { z } from 'zod'\nimport { missingCapability } from './missing-capability'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\n\nconst LearnArgsSchema = z.object({\n tags: z.array(z.string()).describe('Free-form tags categorizing this lesson'),\n trigger: z.string().describe('What situation or pattern triggered this insight'),\n insight: z.string().describe('The lesson learned — what to do or avoid'),\n scope: z\n .enum(['workspace', 'local'])\n .optional()\n .default('workspace')\n .describe(\n \"Lesson scope (default 'workspace'). Use 'workspace' for durable, project-applicable lessons \" +\n \"that hold on any machine. Use 'local' for machine-specific lessons (tied to this machine's \" +\n \"local paths, installed tools/plugins, or environment quirks) that should never sync to other devices.\",\n ),\n})\n\ntype LearnArgs = z.infer<typeof LearnArgsSchema>\n\nexport type LearnCallback = (args: {\n tags: string[]\n trigger: string\n insight: string\n sessionId: string\n scope?: 'workspace' | 'local'\n}) => Promise<{\n success: boolean\n lessonId?: string\n /** RFC-036 R4:命中近似既有经验、复用而非新增(去重)。 */\n deduped?: boolean\n error?: string\n}>\n\nexport function createLearn(sessionId: string, learn?: LearnCallback): AgentTool<LearnArgs> {\n return {\n name: 'learn',\n description: 'Record an operational lesson learned during this session for future reference.',\n parameters: LearnArgsSchema,\n\n async execute({ params }): Promise<ToolResult> {\n if (!learn) {\n return missingCapability('learn')\n }\n\n const result = await learn({\n tags: params.tags,\n trigger: params.trigger,\n insight: params.insight,\n sessionId,\n scope: params.scope,\n })\n\n if (result.success) {\n const verb = result.deduped ? 'Similar lesson already recorded, reusing' : 'Lesson recorded'\n return {\n content: [\n {\n type: 'text',\n text: `${verb} (${result.lessonId}): [${params.tags.join(', ')}] ${params.trigger} → ${params.insight}`,\n },\n ],\n details: { lessonId: result.lessonId, deduped: result.deduped ?? false },\n }\n }\n\n return {\n content: [\n { type: 'text', text: `Failed to record lesson: ${result.error ?? 'Unknown error'}` },\n ],\n isError: true,\n errorKind: 'runtime',\n }\n },\n }\n}\n","import { z } from 'zod'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport type { RegisteredTool } from '../types'\n\nconst ToolSearchArgsSchema = z.object({\n query: z.string().describe('Tool name or keyword to search for in the deferred tool pool'),\n})\n\ntype ToolSearchArgs = z.infer<typeof ToolSearchArgsSchema>\n\nexport function createToolSearch(\n /** 返回当前 deferred 工具池(由 registry.project() 提供)。 */\n getDeferredTools: () => RegisteredTool[],\n): AgentTool<ToolSearchArgs> {\n return {\n name: 'tool_search',\n description:\n 'Search for available tools not shown in the default tool list (e.g. MCP server tools). Use this to discover and load tool schemas on demand.',\n parameters: ToolSearchArgsSchema,\n readonly: true,\n\n async execute({ params }): Promise<ToolResult> {\n const pool = getDeferredTools()\n const q = params.query.toLowerCase()\n\n const exact = pool.find((t) => t.name.toLowerCase() === q)\n if (exact) {\n return formatToolSchema(exact)\n }\n\n const matches = pool.filter(\n (t) =>\n t.name.toLowerCase().includes(q) ||\n (typeof t.description === 'string' && t.description.toLowerCase().includes(q)),\n )\n\n if (matches.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: `No deferred tools found matching \"${params.query}\". Available deferred tools: ${pool.map((t) => t.name).join(', ') || '(none)'}`,\n },\n ],\n }\n }\n\n if (matches.length === 1) {\n return formatToolSchema(matches[0]!)\n }\n\n const list = matches\n .map(\n (t) =>\n `- **${t.name}**: ${typeof t.description === 'string' ? t.description.slice(0, 100) : '(no description)'}`,\n )\n .join('\\n')\n return {\n content: [\n {\n type: 'text',\n text: `Multiple deferred tools match \"${params.query}\":\\n\\n${list}\\n\\nUse tool_search with the exact tool name to load its full schema.`,\n },\n ],\n }\n },\n }\n}\n\nfunction formatToolSchema(tool: RegisteredTool): ToolResult {\n let schemaText = '(no parameters)'\n if (tool.parameters) {\n try {\n const zodSchema = tool.parameters as { toJSONSchema?: () => unknown }\n if (typeof zodSchema.toJSONSchema === 'function') {\n schemaText = JSON.stringify(zodSchema.toJSONSchema(), null, 2)\n } else {\n schemaText = '(binary schema — parameters not introspectable)'\n }\n } catch {\n schemaText = '(schema serialization failed)'\n }\n }\n\n const desc = typeof tool.description === 'string' ? tool.description : ''\n return {\n content: [\n {\n type: 'text',\n text: [\n `## ${tool.name}`,\n '',\n desc ? `${desc}` : '',\n '',\n '**Parameters:**',\n '```json',\n schemaText,\n '```',\n ]\n .filter(Boolean)\n .join('\\n'),\n },\n ],\n }\n}\n","import type { LSPServerConfig } from './types'\n\nexport const SYMBOL_KIND_MAP: Record<number, string> = {\n 1: 'File',\n 2: 'Module',\n 3: 'Namespace',\n 4: 'Package',\n 5: 'Class',\n 6: 'Method',\n 7: 'Property',\n 8: 'Field',\n 9: 'Constructor',\n 10: 'Enum',\n 11: 'Interface',\n 12: 'Function',\n 13: 'Variable',\n 14: 'Constant',\n 15: 'String',\n 16: 'Number',\n 17: 'Boolean',\n 18: 'Array',\n 19: 'Object',\n 20: 'Key',\n 21: 'Null',\n 22: 'EnumMember',\n 23: 'Struct',\n 24: 'Event',\n 25: 'Operator',\n 26: 'TypeParameter',\n}\n\nexport const SEVERITY_MAP: Record<number, string> = {\n 1: 'error',\n 2: 'warning',\n 3: 'information',\n 4: 'hint',\n}\n\nexport const EXT_TO_LANG: Record<string, string> = {\n '.abap': 'abap',\n '.bat': 'bat',\n '.bib': 'bibtex',\n '.bibtex': 'bibtex',\n '.clj': 'clojure',\n '.cljs': 'clojure',\n '.cljc': 'clojure',\n '.edn': 'clojure',\n '.coffee': 'coffeescript',\n '.c': 'c',\n '.cpp': 'cpp',\n '.cxx': 'cpp',\n '.cc': 'cpp',\n '.c++': 'cpp',\n '.cs': 'csharp',\n '.css': 'css',\n '.d': 'd',\n '.pas': 'pascal',\n '.pascal': 'pascal',\n '.diff': 'diff',\n '.patch': 'diff',\n '.dart': 'dart',\n '.dockerfile': 'dockerfile',\n '.ex': 'elixir',\n '.exs': 'elixir',\n '.erl': 'erlang',\n '.hrl': 'erlang',\n '.fs': 'fsharp',\n '.fsi': 'fsharp',\n '.fsx': 'fsharp',\n '.fsscript': 'fsharp',\n '.gitcommit': 'git-commit',\n '.gitrebase': 'git-rebase',\n '.go': 'go',\n '.groovy': 'groovy',\n '.gleam': 'gleam',\n '.hbs': 'handlebars',\n '.handlebars': 'handlebars',\n '.hs': 'haskell',\n '.html': 'html',\n '.htm': 'html',\n '.ini': 'ini',\n '.java': 'java',\n '.js': 'javascript',\n '.jsx': 'javascriptreact',\n '.json': 'json',\n '.jsonc': 'jsonc',\n '.tex': 'latex',\n '.latex': 'latex',\n '.less': 'less',\n '.lua': 'lua',\n '.makefile': 'makefile',\n makefile: 'makefile',\n '.md': 'markdown',\n '.markdown': 'markdown',\n '.m': 'objective-c',\n '.mm': 'objective-cpp',\n '.pl': 'perl',\n '.pm': 'perl',\n '.pm6': 'perl6',\n '.php': 'php',\n '.ps1': 'powershell',\n '.psm1': 'powershell',\n '.pug': 'jade',\n '.jade': 'jade',\n '.py': 'python',\n '.pyi': 'python',\n '.r': 'r',\n '.cshtml': 'razor',\n '.razor': 'razor',\n '.rb': 'ruby',\n '.rake': 'ruby',\n '.gemspec': 'ruby',\n '.ru': 'ruby',\n '.erb': 'erb',\n '.html.erb': 'erb',\n '.js.erb': 'erb',\n '.css.erb': 'erb',\n '.json.erb': 'erb',\n '.rs': 'rust',\n '.scss': 'scss',\n '.sass': 'sass',\n '.scala': 'scala',\n '.shader': 'shaderlab',\n '.sh': 'shellscript',\n '.bash': 'shellscript',\n '.zsh': 'shellscript',\n '.ksh': 'shellscript',\n '.sql': 'sql',\n '.svelte': 'svelte',\n '.swift': 'swift',\n '.ts': 'typescript',\n '.tsx': 'typescriptreact',\n '.mts': 'typescript',\n '.cts': 'typescript',\n '.mtsx': 'typescriptreact',\n '.ctsx': 'typescriptreact',\n '.xml': 'xml',\n '.xsl': 'xsl',\n '.yaml': 'yaml',\n '.yml': 'yaml',\n '.mjs': 'javascript',\n '.cjs': 'javascript',\n '.vue': 'vue',\n '.zig': 'zig',\n '.zon': 'zig',\n '.astro': 'astro',\n '.ml': 'ocaml',\n '.mli': 'ocaml',\n '.tf': 'terraform',\n '.tfvars': 'terraform-vars',\n '.hcl': 'hcl',\n '.nix': 'nix',\n '.typ': 'typst',\n '.typc': 'typst',\n '.ets': 'typescript',\n '.lhs': 'haskell',\n '.kt': 'kotlin',\n '.kts': 'kotlin',\n '.prisma': 'prisma',\n '.h': 'c',\n '.hpp': 'cpp',\n '.hh': 'cpp',\n '.hxx': 'cpp',\n '.h++': 'cpp',\n '.objc': 'objective-c',\n '.objcpp': 'objective-cpp',\n '.fish': 'fish',\n '.graphql': 'graphql',\n '.gql': 'graphql',\n}\n\nexport const DEFAULT_MAX_REFERENCES = 200\nexport const DEFAULT_MAX_SYMBOLS = 200\nexport const DEFAULT_MAX_DIAGNOSTICS = 200\n\nexport const LSP_INSTALL_HINTS: Record<string, string> = {\n typescript: 'npm install -g typescript-language-server typescript',\n deno: 'Install Deno from https://deno.land',\n vue: 'npm install -g @vue/language-server',\n eslint: 'npm install -g vscode-langservers-extracted',\n oxlint: 'npm install -g oxlint',\n biome: 'npm install -g @biomejs/biome',\n gopls: 'go install golang.org/x/tools/gopls@latest',\n 'ruby-lsp': 'gem install ruby-lsp',\n basedpyright: 'pip install basedpyright',\n pyright: 'pip install pyright',\n ty: 'pip install ty',\n ruff: 'pip install ruff',\n 'elixir-ls': 'See https://github.com/elixir-lsp/elixir-ls',\n zls: 'See https://github.com/zigtools/zls',\n csharp: 'dotnet tool install -g csharp-ls',\n fsharp: 'dotnet tool install -g fsautocomplete',\n 'sourcekit-lsp': 'Included with Xcode or Swift toolchain',\n rust: 'rustup component add rust-analyzer',\n clangd: 'See https://clangd.llvm.org/installation',\n svelte: 'npm install -g svelte-language-server',\n astro: 'npm install -g @astrojs/language-server',\n 'bash-ls': 'npm install -g bash-language-server',\n jdtls: 'See https://github.com/eclipse-jdtls/eclipse.jdt.ls',\n 'yaml-ls': 'npm install -g yaml-language-server',\n 'lua-ls': 'See https://github.com/LuaLS/lua-language-server',\n php: 'npm install -g intelephense',\n dart: 'Included with Dart SDK',\n 'terraform-ls': 'See https://github.com/hashicorp/terraform-ls',\n terraform: 'See https://github.com/hashicorp/terraform-ls',\n prisma: 'npm install -g prisma',\n 'ocaml-lsp': 'opam install ocaml-lsp-server',\n texlab: 'See https://github.com/latex-lsp/texlab',\n dockerfile: 'npm install -g dockerfile-language-server-nodejs',\n gleam: 'See https://gleam.run/getting-started/installing/',\n 'clojure-lsp': 'See https://clojure-lsp.io/installation/',\n nixd: 'nix profile install nixpkgs#nixd',\n tinymist: 'See https://github.com/Myriad-Dreamin/tinymist',\n 'haskell-language-server': 'ghcup install hls',\n bash: 'npm install -g bash-language-server',\n 'kotlin-ls': 'See https://github.com/Kotlin/kotlin-lsp',\n}\n\nexport const BUILTIN_SERVERS: Record<string, Omit<LSPServerConfig, 'id'>> = {\n typescript: {\n command: ['typescript-language-server', '--stdio'],\n extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts'],\n },\n deno: { command: ['deno', 'lsp'], extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs'] },\n vue: { command: ['vue-language-server', '--stdio'], extensions: ['.vue'] },\n eslint: {\n command: ['vscode-eslint-language-server', '--stdio'],\n extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts', '.vue'],\n },\n oxlint: {\n command: ['oxlint', '--lsp'],\n extensions: [\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.mts',\n '.cts',\n '.vue',\n '.astro',\n '.svelte',\n ],\n },\n biome: {\n command: ['biome', 'lsp-proxy', '--stdio'],\n extensions: [\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.mts',\n '.cts',\n '.json',\n '.jsonc',\n '.vue',\n '.astro',\n '.svelte',\n '.css',\n '.graphql',\n '.gql',\n '.html',\n ],\n },\n gopls: { command: ['gopls'], extensions: ['.go'] },\n 'ruby-lsp': { command: ['rubocop', '--lsp'], extensions: ['.rb', '.rake', '.gemspec', '.ru'] },\n basedpyright: { command: ['basedpyright-langserver', '--stdio'], extensions: ['.py', '.pyi'] },\n pyright: { command: ['pyright-langserver', '--stdio'], extensions: ['.py', '.pyi'] },\n ty: { command: ['ty', 'server'], extensions: ['.py', '.pyi'] },\n ruff: { command: ['ruff', 'server'], extensions: ['.py', '.pyi'] },\n 'elixir-ls': { command: ['elixir-ls'], extensions: ['.ex', '.exs'] },\n zls: { command: ['zls'], extensions: ['.zig', '.zon'] },\n csharp: { command: ['csharp-ls'], extensions: ['.cs'] },\n fsharp: { command: ['fsautocomplete'], extensions: ['.fs', '.fsi', '.fsx', '.fsscript'] },\n 'sourcekit-lsp': { command: ['sourcekit-lsp'], extensions: ['.swift', '.objc', '.objcpp'] },\n rust: { command: ['rust-analyzer'], extensions: ['.rs'] },\n clangd: {\n command: ['clangd', '--background-index', '--clang-tidy'],\n extensions: ['.c', '.cpp', '.cc', '.cxx', '.c++', '.h', '.hpp', '.hh', '.hxx', '.h++'],\n },\n svelte: { command: ['svelteserver', '--stdio'], extensions: ['.svelte'] },\n astro: { command: ['astro-ls', '--stdio'], extensions: ['.astro'] },\n bash: {\n command: ['bash-language-server', 'start'],\n extensions: ['.sh', '.bash', '.zsh', '.ksh'],\n },\n 'bash-ls': {\n command: ['bash-language-server', 'start'],\n extensions: ['.sh', '.bash', '.zsh', '.ksh'],\n },\n jdtls: { command: ['jdtls'], extensions: ['.java'] },\n 'yaml-ls': { command: ['yaml-language-server', '--stdio'], extensions: ['.yaml', '.yml'] },\n 'lua-ls': { command: ['lua-language-server'], extensions: ['.lua'] },\n php: { command: ['intelephense', '--stdio'], extensions: ['.php'] },\n dart: { command: ['dart', 'language-server', '--lsp'], extensions: ['.dart'] },\n terraform: { command: ['terraform-ls', 'serve'], extensions: ['.tf', '.tfvars'] },\n 'terraform-ls': { command: ['terraform-ls', 'serve'], extensions: ['.tf', '.tfvars'] },\n prisma: { command: ['prisma', 'language-server'], extensions: ['.prisma'] },\n 'ocaml-lsp': { command: ['ocamllsp'], extensions: ['.ml', '.mli'] },\n texlab: { command: ['texlab'], extensions: ['.tex', '.bib'] },\n dockerfile: { command: ['docker-langserver', '--stdio'], extensions: ['.dockerfile'] },\n gleam: { command: ['gleam', 'lsp'], extensions: ['.gleam'] },\n 'clojure-lsp': {\n command: ['clojure-lsp', 'listen'],\n extensions: ['.clj', '.cljs', '.cljc', '.edn'],\n },\n nixd: { command: ['nixd'], extensions: ['.nix'] },\n tinymist: { command: ['tinymist'], extensions: ['.typ', '.typc'] },\n 'haskell-language-server': {\n command: ['haskell-language-server-wrapper', '--lsp'],\n extensions: ['.hs', '.lhs'],\n },\n 'kotlin-ls': { command: ['kotlin-lsp'], extensions: ['.kt', '.kts'] },\n}\n","import { existsSync } from 'node:fs'\nimport { readFile, writeFile, rename, unlink } from 'node:fs/promises'\nimport { fileURLToPath } from 'node:url'\nimport type { TextEdit, WorkspaceEdit } from './types'\n\nexport interface ApplyResult {\n success: boolean\n filesModified: string[]\n totalEdits: number\n errors: string[]\n}\n\nexport function uriToPath(uri: string): string {\n return fileURLToPath(uri)\n}\n\nasync function applyTextEditsToFile(\n filePath: string,\n edits: TextEdit[],\n): Promise<{ success: boolean; editCount: number; error?: string }> {\n try {\n const content = await readFile(filePath, 'utf-8')\n const lines = content.split('\\n')\n\n const sortedEdits = [...edits].sort((a, b) => {\n if (b.range.start.line !== a.range.start.line) {\n return b.range.start.line - a.range.start.line\n }\n return b.range.start.character - a.range.start.character\n })\n\n for (const edit of sortedEdits) {\n const { start, end } = edit.range\n\n if (start.line === end.line) {\n const line = lines[start.line] || ''\n lines[start.line] =\n line.substring(0, start.character) + edit.newText + line.substring(end.character)\n } else {\n const firstLine = lines[start.line] || ''\n const lastLine = lines[end.line] || ''\n const newContent =\n firstLine.substring(0, start.character) + edit.newText + lastLine.substring(end.character)\n lines.splice(start.line, end.line - start.line + 1, ...newContent.split('\\n'))\n }\n }\n\n await writeFile(filePath, lines.join('\\n'), 'utf-8')\n return { success: true, editCount: edits.length }\n } catch (err) {\n return {\n success: false,\n editCount: 0,\n error: err instanceof Error ? err.message : String(err),\n }\n }\n}\n\nexport async function applyWorkspaceEdit(edit: WorkspaceEdit | null): Promise<ApplyResult> {\n if (!edit) {\n return { success: false, filesModified: [], totalEdits: 0, errors: ['No edit provided'] }\n }\n\n const result: ApplyResult = { success: true, filesModified: [], totalEdits: 0, errors: [] }\n\n if (edit.changes) {\n for (const [uri, edits] of Object.entries(edit.changes)) {\n const filePath = uriToPath(uri)\n const applyResult = await applyTextEditsToFile(filePath, edits)\n\n if (applyResult.success) {\n result.filesModified.push(filePath)\n result.totalEdits += applyResult.editCount\n } else {\n result.success = false\n result.errors.push(`${filePath}: ${applyResult.error}`)\n }\n }\n }\n\n if (edit.documentChanges) {\n for (const change of edit.documentChanges) {\n if ('kind' in change) {\n try {\n if (change.kind === 'create') {\n const filePath = uriToPath(change.uri)\n // LSP CreateFile semantics: only truncate an existing file when\n // overwrite is set; otherwise (ignoreIfExists / default) preserve it.\n if (!existsSync(filePath) || change.options?.overwrite) {\n await writeFile(filePath, '', 'utf-8')\n result.filesModified.push(filePath)\n }\n } else if (change.kind === 'rename') {\n const oldPath = uriToPath(change.oldUri)\n const newPath = uriToPath(change.newUri)\n await rename(oldPath, newPath)\n result.filesModified.push(newPath)\n } else if (change.kind === 'delete') {\n const filePath = uriToPath(change.uri)\n await unlink(filePath)\n result.filesModified.push(filePath)\n }\n } catch (err) {\n result.success = false\n result.errors.push(`${change.kind} ${(change as { uri?: string }).uri ?? ''}: ${err}`)\n }\n } else {\n const filePath = uriToPath(change.textDocument.uri)\n const applyResult = await applyTextEditsToFile(filePath, change.edits)\n\n if (applyResult.success) {\n result.filesModified.push(filePath)\n result.totalEdits += applyResult.editCount\n } else {\n result.success = false\n result.errors.push(`${filePath}: ${applyResult.error}`)\n }\n }\n }\n }\n\n return result\n}\n","import { SYMBOL_KIND_MAP, SEVERITY_MAP } from './constants'\nimport { uriToPath, type ApplyResult } from './workspace-edit'\nimport type {\n Diagnostic,\n DocumentSymbol,\n Location,\n LocationLink,\n PrepareRenameDefaultBehavior,\n PrepareRenameResult,\n Range,\n SymbolInfo,\n TextEdit,\n WorkspaceEdit,\n} from './types'\n\nexport function formatLocation(loc: Location | LocationLink): string {\n if ('targetUri' in loc) {\n const path = uriToPath(loc.targetUri)\n const line = loc.targetRange.start.line + 1\n const char = loc.targetRange.start.character\n return `${path}:${line}:${char}`\n }\n\n const path = uriToPath(loc.uri)\n const line = loc.range.start.line + 1\n const char = loc.range.start.character\n return `${path}:${line}:${char}`\n}\n\nexport function formatSymbolKind(kind: number): string {\n return SYMBOL_KIND_MAP[kind] || `Unknown(${kind})`\n}\n\nexport function formatSeverity(severity: number | undefined): string {\n if (!severity) {\n return 'unknown'\n }\n return SEVERITY_MAP[severity] || `unknown(${severity})`\n}\n\nexport function formatDocumentSymbol(symbol: DocumentSymbol, indent = 0): string {\n const prefix = ' '.repeat(indent)\n const kind = formatSymbolKind(symbol.kind)\n const line = symbol.range.start.line + 1\n let result = `${prefix}${symbol.name} (${kind}) - line ${line}`\n\n if (symbol.children && symbol.children.length > 0) {\n for (const child of symbol.children) {\n result += '\\n' + formatDocumentSymbol(child, indent + 1)\n }\n }\n\n return result\n}\n\nexport function formatSymbolInfo(symbol: SymbolInfo): string {\n const kind = formatSymbolKind(symbol.kind)\n const loc = formatLocation(symbol.location)\n const container = symbol.containerName ? ` (in ${symbol.containerName})` : ''\n return `${symbol.name} (${kind})${container} - ${loc}`\n}\n\nexport function formatDiagnostic(diag: Diagnostic): string {\n const severity = formatSeverity(diag.severity)\n const line = diag.range.start.line + 1\n const char = diag.range.start.character\n const source = diag.source ? `[${diag.source}]` : ''\n const code = diag.code ? ` (${diag.code})` : ''\n return `${severity}${source}${code} at ${line}:${char}: ${diag.message}`\n}\n\nexport function filterDiagnosticsBySeverity(\n diagnostics: Diagnostic[],\n severityFilter?: 'error' | 'warning' | 'information' | 'hint' | 'all',\n): Diagnostic[] {\n if (!severityFilter || severityFilter === 'all') {\n return diagnostics\n }\n\n const severityMap: Record<string, number> = {\n error: 1,\n warning: 2,\n information: 3,\n hint: 4,\n }\n\n const targetSeverity = severityMap[severityFilter]\n return diagnostics.filter((d) => d.severity === targetSeverity)\n}\n\nexport function formatPrepareRenameResult(\n result: PrepareRenameResult | PrepareRenameDefaultBehavior | Range | null,\n): string {\n if (!result) {\n return 'Cannot rename at this position'\n }\n\n if ('defaultBehavior' in result) {\n return result.defaultBehavior\n ? 'Rename supported (using default behavior)'\n : 'Cannot rename at this position'\n }\n\n if ('range' in result && result.range) {\n const startLine = result.range.start.line + 1\n const startChar = result.range.start.character\n const endLine = result.range.end.line + 1\n const endChar = result.range.end.character\n const placeholder = result.placeholder ? ` (current: \"${result.placeholder}\")` : ''\n return `Rename available at ${startLine}:${startChar}-${endLine}:${endChar}${placeholder}`\n }\n\n if ('start' in result && 'end' in result) {\n const startLine = result.start.line + 1\n const startChar = result.start.character\n const endLine = result.end.line + 1\n const endChar = result.end.character\n return `Rename available at ${startLine}:${startChar}-${endLine}:${endChar}`\n }\n\n return 'Cannot rename at this position'\n}\n\nexport function formatTextEdit(edit: TextEdit): string {\n const startLine = edit.range.start.line + 1\n const startChar = edit.range.start.character\n const endLine = edit.range.end.line + 1\n const endChar = edit.range.end.character\n const rangeStr = `${startLine}:${startChar}-${endLine}:${endChar}`\n const preview = edit.newText.length > 50 ? edit.newText.substring(0, 50) + '...' : edit.newText\n return ` ${rangeStr}: \"${preview}\"`\n}\n\nexport function formatWorkspaceEdit(edit: WorkspaceEdit | null): string {\n if (!edit) {\n return 'No changes'\n }\n\n const lines: string[] = []\n\n if (edit.changes) {\n for (const [uri, edits] of Object.entries(edit.changes)) {\n const filePath = uriToPath(uri)\n lines.push(`File: ${filePath}`)\n for (const textEdit of edits) {\n lines.push(formatTextEdit(textEdit))\n }\n }\n }\n\n if (edit.documentChanges) {\n for (const change of edit.documentChanges) {\n if ('kind' in change) {\n if (change.kind === 'create') {\n lines.push(`Create: ${change.uri}`)\n } else if (change.kind === 'rename') {\n lines.push(`Rename: ${change.oldUri} -> ${change.newUri}`)\n } else if (change.kind === 'delete') {\n lines.push(`Delete: ${change.uri}`)\n }\n } else {\n const filePath = uriToPath(change.textDocument.uri)\n lines.push(`File: ${filePath}`)\n for (const textEdit of change.edits) {\n lines.push(formatTextEdit(textEdit))\n }\n }\n }\n }\n\n return lines.length === 0 ? 'No changes' : lines.join('\\n')\n}\n\nexport function formatApplyResult(result: ApplyResult): string {\n const lines: string[] = []\n\n if (result.success) {\n lines.push(`Applied ${result.totalEdits} edit(s) to ${result.filesModified.length} file(s):`)\n for (const file of result.filesModified) {\n lines.push(` - ${file}`)\n }\n } else {\n lines.push('Failed to apply some changes:')\n for (const err of result.errors) {\n lines.push(` Error: ${err}`)\n }\n if (result.filesModified.length > 0) {\n lines.push(`Successfully modified: ${result.filesModified.join(', ')}`)\n }\n }\n\n return lines.join('\\n')\n}\n","type EventOnce = {\n once(event: string, listener: (...args: unknown[]) => void): unknown\n}\n\n/**\n * 等子进程成功 spawn 的真实信号:'spawn' → resolve;'error'/'exit'(早退) → reject;\n * 超时 → resolve(兜底,假定已起;与旧\"sleep 后检查 exitCode\"等价但**一就绪即返回**)。\n */\nexport function waitForProcessSpawn(proc: EventOnce, timeoutMs: number): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n let settled = false\n const finish = (fn: () => void): void => {\n if (settled) {\n return\n }\n settled = true\n clearTimeout(timer)\n fn()\n }\n const timer = setTimeout(() => finish(resolve), timeoutMs)\n proc.once('spawn', () => finish(resolve))\n proc.once('error', (err) =>\n finish(() => reject(err instanceof Error ? err : new Error(String(err)))),\n )\n proc.once('exit', (code) =>\n finish(() => reject(new Error(`process exited early with code ${code}`))),\n )\n })\n}\n\nexport interface DiagnosticsWaiter {\n /** 等某 URI 的 publishDiagnostics;命中即 resolve,超时兜底 resolve(无诊断不挂死)。 */\n wait(uri: string, timeoutMs: number): Promise<void>\n /** 收到该 URI 的 publishDiagnostics 时调用,唤醒其等待者。 */\n notify(uri: string): void\n /** 清空所有等待者(停止/重置时)。 */\n clear(): void\n}\n\nexport function createDiagnosticsWaiter(): DiagnosticsWaiter {\n const waiters = new Map<string, Set<() => void>>()\n return {\n wait(uri, timeoutMs) {\n return new Promise<void>((resolve) => {\n let done = false\n const finish = (): void => {\n if (done) {\n return\n }\n done = true\n clearTimeout(timer)\n waiters.get(uri)?.delete(finish)\n resolve()\n }\n const timer = setTimeout(finish, timeoutMs)\n const set = waiters.get(uri) ?? new Set<() => void>()\n set.add(finish)\n waiters.set(uri, set)\n })\n },\n notify(uri) {\n const set = waiters.get(uri)\n if (set) {\n waiters.delete(uri)\n for (const finish of set) {\n finish()\n }\n }\n },\n clear() {\n waiters.clear()\n },\n }\n}\n\n/**\n * 构造 workspace/configuration 应答:优先用 ResolvedServer.settings 中匹配 section 的配置;\n * 未配置时 json 段回退 `{ validate: { enable: true } }`(保持既有行为),其余回退空对象。\n */\nexport function buildConfigurationResponse(\n items: Array<{ section?: string }>,\n settings?: Record<string, unknown>,\n): unknown[] {\n return items.map((item) => {\n if (item.section && settings && Object.prototype.hasOwnProperty.call(settings, item.section)) {\n return settings[item.section]\n }\n if (item.section === 'json') {\n return { validate: { enable: true } }\n }\n return {}\n })\n}\n","import type { ProcessTracker } from '@x-otto/interchange'\nimport { LSPClient } from './lsp-client'\nimport type { ResolvedServer } from './types'\n\ninterface ManagedClient {\n client: LSPClient\n lastUsedAt: number\n refCount: number\n initPromise?: Promise<void>\n isInitializing: boolean\n initializingSince?: number\n}\n\nexport class LSPServerManager {\n private static instance: LSPServerManager\n private clients = new Map<string, ManagedClient>()\n private cleanupInterval: ReturnType<typeof setInterval> | null = null\n private readonly IDLE_TIMEOUT = 5 * 60 * 1000\n private readonly INIT_TIMEOUT = 60 * 1000\n\n /** RFC-095:进程追踪器(组合根注入)。 */\n private processTracker?: ProcessTracker\n\n private constructor() {\n this.startCleanupTimer()\n }\n\n static getInstance(): LSPServerManager {\n if (!LSPServerManager.instance) {\n LSPServerManager.instance = new LSPServerManager()\n }\n return LSPServerManager.instance\n }\n\n /** RFC-095:设置进程追踪器(组合根装配时调用)。 */\n setProcessTracker(tracker: ProcessTracker): void {\n this.processTracker = tracker\n }\n\n private getKey(root: string, serverId: string): string {\n return `${root}::${serverId}`\n }\n\n private startCleanupTimer(): void {\n if (this.cleanupInterval) {\n return\n }\n this.cleanupInterval = setInterval(() => this.cleanupIdleClients(), 60_000)\n if (this.cleanupInterval.unref) {\n this.cleanupInterval.unref()\n }\n }\n\n private cleanupIdleClients(): void {\n const now = Date.now()\n for (const [key, managed] of this.clients) {\n if (managed.refCount === 0 && now - managed.lastUsedAt > this.IDLE_TIMEOUT) {\n void managed.client.stop()\n this.clients.delete(key)\n }\n }\n }\n\n // RFC-085 M4:旧 exit/SIGINT/SIGTERM 自有 reaper 已移除。LSP 进程改由 ProcessRuntime\n // 统一追踪(getClient/warmupClient 注入 globalProcessRuntime,lifecycle:'pinned'),\n // 退出清退经 app.stop() → globalProcessRuntime.killAll() + Layer 3 installExitReaper。\n // 移除本地 signal handler 亦消除 RFC-063 陪跑回归风险(H1,全局 handler 须进 chaperone 前拆除)。\n\n async getClient(root: string, server: ResolvedServer): Promise<LSPClient> {\n const key = this.getKey(root, server.id)\n let managed = this.clients.get(key)\n\n if (managed) {\n const now = Date.now()\n if (\n managed.isInitializing &&\n managed.initializingSince !== undefined &&\n now - managed.initializingSince >= this.INIT_TIMEOUT\n ) {\n try {\n await managed.client.stop()\n } catch {}\n this.clients.delete(key)\n managed = undefined\n }\n }\n\n if (managed) {\n if (managed.initPromise) {\n try {\n await managed.initPromise\n } catch {\n try {\n await managed.client.stop()\n } catch {}\n this.clients.delete(key)\n managed = undefined\n }\n }\n\n if (managed) {\n if (managed.client.isAlive()) {\n managed.refCount++\n managed.lastUsedAt = Date.now()\n return managed.client\n }\n try {\n await managed.client.stop()\n } catch {}\n this.clients.delete(key)\n }\n }\n\n const client = new LSPClient(root, server, this.processTracker)\n const initStartedAt = Date.now()\n const initPromise = (async () => {\n await client.start()\n await client.initialize()\n })()\n\n this.clients.set(key, {\n client,\n lastUsedAt: initStartedAt,\n refCount: 1,\n initPromise,\n isInitializing: true,\n initializingSince: initStartedAt,\n })\n\n try {\n await initPromise\n } catch (error) {\n this.clients.delete(key)\n try {\n await client.stop()\n } catch {}\n throw error\n }\n\n const m = this.clients.get(key)\n if (m) {\n m.initPromise = undefined\n m.isInitializing = false\n m.initializingSince = undefined\n }\n\n return client\n }\n\n warmupClient(root: string, server: ResolvedServer): void {\n const key = this.getKey(root, server.id)\n if (this.clients.has(key)) {\n return\n }\n\n const client = new LSPClient(root, server, this.processTracker)\n const initStartedAt = Date.now()\n const initPromise = (async () => {\n await client.start()\n await client.initialize()\n })()\n\n this.clients.set(key, {\n client,\n lastUsedAt: initStartedAt,\n refCount: 0,\n initPromise,\n isInitializing: true,\n initializingSince: initStartedAt,\n })\n\n initPromise\n .then(() => {\n const m = this.clients.get(key)\n if (m) {\n m.initPromise = undefined\n m.isInitializing = false\n m.initializingSince = undefined\n }\n })\n .catch(() => {\n this.clients.delete(key)\n void client.stop().catch(() => {})\n })\n }\n\n releaseClient(root: string, serverId: string): void {\n const key = this.getKey(root, serverId)\n const managed = this.clients.get(key)\n if (managed && managed.refCount > 0) {\n managed.refCount--\n managed.lastUsedAt = Date.now()\n }\n }\n\n isServerInitializing(root: string, serverId: string): boolean {\n const key = this.getKey(root, serverId)\n return this.clients.get(key)?.isInitializing ?? false\n }\n\n async stopAll(): Promise<void> {\n for (const [, managed] of this.clients) {\n await managed.client.stop()\n }\n this.clients.clear()\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval)\n this.cleanupInterval = null\n }\n }\n}\n\nexport const lspManager = LSPServerManager.getInstance()\n","import { spawn, type ChildProcess } from 'node:child_process'\nimport { existsSync, statSync } from 'node:fs'\nimport { readFile } from 'node:fs/promises'\nimport { resolve, extname } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { createLogger, buildAllowedEnv } from '@x-otto/shared'\nimport type { ProcessTracker } from '@x-otto/interchange'\nimport { toolError } from '../tool-error'\nimport { EXT_TO_LANG } from './constants'\nimport {\n buildConfigurationResponse,\n createDiagnosticsWaiter,\n waitForProcessSpawn,\n} from './readiness'\nimport type { DiagnosticsWaiter } from './readiness'\nimport type { Diagnostic, ResolvedServer } from './types'\n\nconst logger = createLogger('@x-otto/tools:lsp')\n\nconst SPAWN_TIMEOUT_MS = 2_000\nconst DIAGNOSTICS_TIMEOUT_MS = 2_000\n\nconst CONTENT_LENGTH = 'Content-Length: '\nconst MAX_LSP_MESSAGE_BYTES = 32 * 1024 * 1024\n\nfunction encodeMessage(body: string): Buffer {\n const buf = Buffer.from(body, 'utf-8')\n return Buffer.concat([Buffer.from(`Content-Length: ${buf.byteLength}\\r\\n\\r\\n`, 'ascii'), buf])\n}\n\ninterface PendingRequest {\n resolve: (value: unknown) => void\n reject: (reason: Error) => void\n timer: ReturnType<typeof setTimeout>\n}\n\nexport class LSPClient {\n private process: ChildProcess | null = null\n private nextId = 1\n private pending = new Map<number, PendingRequest>()\n private buffer: Buffer = Buffer.alloc(0)\n private contentLength = -1\n private processExited = false\n private stderrBuffer: string[] = []\n private diagnosticsStore = new Map<string, Diagnostic[]>()\n private readonly diagnosticsWaiter: DiagnosticsWaiter = createDiagnosticsWaiter()\n\n private openedFiles = new Set<string>()\n private documentVersions = new Map<string, number>()\n private lastSyncedText = new Map<string, string>()\n\n private readonly REQUEST_TIMEOUT = 15_000\n\n constructor(\n private readonly root: string,\n private readonly server: ResolvedServer,\n private readonly processTracker?: ProcessTracker,\n ) {}\n\n async start(): Promise<void> {\n const cwdValidation = validateCwd(this.root)\n if (!cwdValidation.valid) {\n throw toolError(`[LSP] ${cwdValidation.error}`, 'validation')\n }\n\n if (this.server.command.length === 0) {\n throw toolError('[LSP] Server command is empty', 'validation')\n }\n\n const [cmd, ...args] = this.server.command\n this.process = spawn(cmd!, args, {\n cwd: this.root,\n // 白名单 env,不透传父进程 API key\n env: buildAllowedEnv(this.server.env),\n stdio: ['pipe', 'pipe', 'pipe'],\n detached: true, // RFC-085 C1: 子进程成为进程组长,kill 走 -pgid\n })\n // RFC-095: 纳入 ProcessTracker 统一追踪(pinned——长驻 LSP 免 LRU 驱逐)。\n this.processTracker?.registerChild(\n {\n command: cmd!,\n args,\n owner: { type: 'lsp-server', id: this.root },\n category: 'lsp',\n lifecycle: 'pinned',\n cwd: this.root,\n },\n this.process,\n )\n\n const proc = this.process\n\n proc.on('exit', (code) => {\n this.processExited = true\n logger.debug(`LSP server exited with code ${code}`)\n this.rejectAllPending(new Error(`LSP server exited with code ${code ?? 'null'}`))\n })\n\n proc.on('error', (err) => {\n this.processExited = true\n logger.error(`LSP spawn error: ${err.message}`)\n this.rejectAllPending(new Error(`LSP spawn error: ${err.message}`))\n })\n\n try {\n await waitForProcessSpawn(proc, SPAWN_TIMEOUT_MS)\n } catch (err) {\n const stderr = this.stderrBuffer.join('\\n')\n throw toolError(\n `LSP server failed to start: ${(err as Error).message}` +\n (stderr ? `\\nstderr: ${stderr}` : ''),\n 'runtime',\n )\n }\n\n if (proc.exitCode !== null) {\n const stderr = this.stderrBuffer.join('\\n')\n throw toolError(\n `LSP server exited immediately with code ${proc.exitCode}` +\n (stderr ? `\\nstderr: ${stderr}` : ''),\n 'runtime',\n )\n }\n\n proc.stdout?.on('data', (chunk: Buffer) => {\n this.processBuffer(chunk)\n })\n\n proc.stderr?.on('data', (chunk: Buffer) => {\n const text = chunk.toString('utf-8')\n this.stderrBuffer.push(text)\n if (this.stderrBuffer.length > 100) {\n this.stderrBuffer.shift()\n }\n })\n }\n\n async initialize(): Promise<void> {\n const rootUri = pathToFileURL(this.root).href\n await this.sendRequest('initialize', {\n processId: process.pid,\n rootUri,\n rootPath: this.root,\n workspaceFolders: [{ uri: rootUri, name: 'workspace' }],\n capabilities: {\n textDocument: {\n hover: { contentFormat: ['markdown', 'plaintext'] },\n definition: { linkSupport: true },\n references: {},\n documentSymbol: { hierarchicalDocumentSymbolSupport: true },\n publishDiagnostics: {},\n rename: {\n prepareSupport: true,\n prepareSupportDefaultBehavior: 1,\n honorsChangeAnnotations: true,\n },\n codeAction: {\n codeActionLiteralSupport: {\n codeActionKind: {\n valueSet: [\n 'quickfix',\n 'refactor',\n 'refactor.extract',\n 'refactor.inline',\n 'refactor.rewrite',\n 'source',\n 'source.organizeImports',\n 'source.fixAll',\n ],\n },\n },\n isPreferredSupport: true,\n disabledSupport: true,\n dataSupport: true,\n resolveSupport: { properties: ['edit', 'command'] },\n },\n },\n workspace: {\n symbol: {},\n workspaceFolders: true,\n configuration: true,\n applyEdit: true,\n workspaceEdit: { documentChanges: true },\n },\n },\n ...this.server.initialization,\n })\n\n this.sendNotification('initialized')\n this.sendNotification('workspace/didChangeConfiguration', {\n settings: this.server.settings ?? { json: { validate: { enable: true } } },\n })\n }\n\n private rejectAllPending(error: Error): void {\n for (const [, req] of this.pending) {\n clearTimeout(req.timer)\n req.reject(error)\n }\n this.pending.clear()\n }\n\n async stop(): Promise<void> {\n this.rejectAllPending(new Error('LSP client stopping'))\n\n if (!this.processExited && this.process) {\n try {\n this.sendNotification('shutdown')\n this.sendNotification('exit')\n } catch {}\n\n const proc = this.process\n this.process = null\n\n let timer: ReturnType<typeof setTimeout> | undefined\n const exited = await Promise.race([\n new Promise<boolean>((res) => proc.on('exit', () => res(true))),\n new Promise<boolean>((res) => {\n timer = setTimeout(() => res(false), 5000)\n }),\n ])\n if (timer) clearTimeout(timer)\n\n if (!exited) {\n logger.debug('LSP process did not exit within timeout, sending SIGKILL')\n try {\n proc.kill('SIGKILL')\n } catch {}\n }\n }\n\n this.processExited = true\n this.process = null\n this.diagnosticsStore.clear()\n this.diagnosticsWaiter.clear()\n this.openedFiles.clear()\n this.documentVersions.clear()\n this.lastSyncedText.clear()\n }\n\n isAlive(): boolean {\n return this.process !== null && !this.processExited && this.process.exitCode === null\n }\n\n private processBuffer(chunk: Buffer): void {\n // RFC review P2-0:常见稳态下每条 LSP 消息在其自身到达的 chunk 内已完整(上一条消息\n // 处理完毕后 this.buffer 已清空),此时无需 Buffer.concat([空 buffer, chunk])——\n // 直接复用 chunk 本身即可避免不必要的分配+拷贝。只有真正存在跨 chunk 的残留数据\n // (多 chunk 消息/解析滞后)时才需要 concat,此路径的渐进复杂度不变但发生频率低得多。\n this.buffer = this.buffer.length === 0 ? chunk : Buffer.concat([this.buffer, chunk])\n\n // Safeguard: prevent unbounded buffer growth when receiving data without\n // valid LSP headers (e.g. from a misconfigured server that outputs\n // non-protocol data). Only resets when still looking for the header\n // separator (contentLength === -1) — once a valid header is parsed,\n // the body size is already bounded by MAX_LSP_MESSAGE_BYTES.\n if (this.buffer.length > MAX_LSP_MESSAGE_BYTES && this.contentLength === -1) {\n logger.warn(\n `LSP buffer exceeded ${MAX_LSP_MESSAGE_BYTES} bytes without finding header terminator; resetting`,\n )\n this.buffer = Buffer.alloc(0)\n this.contentLength = -1\n return\n }\n\n while (true) {\n if (this.contentLength === -1) {\n const headerEnd = this.buffer.indexOf('\\r\\n\\r\\n')\n if (headerEnd === -1) {\n break\n }\n\n const header = this.buffer.subarray(0, headerEnd).toString('ascii')\n for (const line of header.split('\\r\\n')) {\n if (line.startsWith(CONTENT_LENGTH)) {\n const parsed = parseInt(line.slice(CONTENT_LENGTH.length), 10)\n if (Number.isInteger(parsed) && parsed >= 0 && parsed <= MAX_LSP_MESSAGE_BYTES) {\n this.contentLength = parsed\n }\n }\n }\n\n if (this.contentLength === -1) {\n this.buffer = this.buffer.subarray(headerEnd + 4)\n continue\n }\n\n this.buffer = this.buffer.subarray(headerEnd + 4)\n }\n\n if (this.buffer.length < this.contentLength) {\n break\n }\n\n const body = this.buffer.subarray(0, this.contentLength).toString('utf-8')\n this.buffer = this.buffer.subarray(this.contentLength)\n this.contentLength = -1\n\n try {\n this.onMessage(JSON.parse(body))\n } catch (err) {\n logger.error(`Failed to parse LSP message: ${err}`)\n }\n }\n }\n\n private onMessage(msg: Record<string, unknown>): void {\n if ('id' in msg && ('result' in msg || 'error' in msg)) {\n const id = msg.id as number\n const pending = this.pending.get(id)\n if (pending) {\n this.pending.delete(id)\n clearTimeout(pending.timer)\n if ('error' in msg) {\n const err = msg.error as { message: string; code?: number }\n pending.reject(new Error(`LSP error ${err.code ?? ''}: ${err.message}`))\n } else {\n pending.resolve(msg.result)\n }\n }\n return\n }\n\n const method = msg.method as string | undefined\n if (!method) {\n return\n }\n\n if (!('id' in msg)) {\n if (method === 'textDocument/publishDiagnostics') {\n const params = msg.params as { uri?: string; diagnostics?: Diagnostic[] }\n if (params.uri) {\n this.diagnosticsStore.set(params.uri, params.diagnostics ?? [])\n this.diagnosticsWaiter.notify(params.uri)\n }\n }\n return\n }\n\n const id = msg.id as number\n if (method === 'workspace/configuration') {\n const params = msg.params as { items?: Array<{ section?: string }> }\n this.respond(id, buildConfigurationResponse(params?.items ?? [], this.server.settings))\n } else if (\n method === 'client/registerCapability' ||\n method === 'window/workDoneProgress/create'\n ) {\n this.respond(id, null)\n } else {\n this.respond(id, null)\n }\n }\n\n private sendRequest<T = unknown>(method: string, params?: unknown): Promise<T> {\n if (!this.process?.stdin?.writable) {\n throw toolError('LSP client not started', 'runtime')\n }\n\n if (this.processExited || (this.process && this.process.exitCode !== null)) {\n const stderr = this.stderrBuffer.slice(-10).join('\\n')\n throw toolError(\n `LSP server already exited (code: ${this.process?.exitCode})` +\n (stderr ? `\\nstderr: ${stderr}` : ''),\n 'runtime',\n )\n }\n\n const id = this.nextId++\n\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pending.delete(id)\n const stderr = this.stderrBuffer.slice(-5).join('\\n')\n reject(\n new Error(\n `LSP request timeout (method: ${method})` +\n (stderr ? `\\nrecent stderr: ${stderr}` : ''),\n ),\n )\n }, this.REQUEST_TIMEOUT)\n\n this.pending.set(id, { resolve: resolve as (v: unknown) => void, reject, timer })\n\n const body = JSON.stringify({ jsonrpc: '2.0', id, method, params })\n this.process!.stdin!.write(encodeMessage(body))\n })\n }\n\n private sendNotification(method: string, params?: unknown): void {\n if (!this.process?.stdin?.writable) {\n return\n }\n if (this.processExited || (this.process && this.process.exitCode !== null)) {\n return\n }\n\n const body = JSON.stringify({ jsonrpc: '2.0', method, params })\n this.process.stdin.write(encodeMessage(body))\n }\n\n private respond(id: number, result: unknown): void {\n if (!this.process?.stdin?.writable) {\n return\n }\n const body = JSON.stringify({ jsonrpc: '2.0', id, result })\n this.process.stdin.write(encodeMessage(body))\n }\n\n async openFile(filePath: string): Promise<void> {\n const absPath = resolve(filePath)\n const uri = pathToFileURL(absPath).href\n const text = await readFile(absPath, 'utf-8')\n\n if (!this.openedFiles.has(absPath)) {\n const ext = extname(absPath)\n const languageId = EXT_TO_LANG[ext] || 'plaintext'\n const version = 1\n\n this.sendNotification('textDocument/didOpen', {\n textDocument: { uri, languageId, version, text },\n })\n\n this.openedFiles.add(absPath)\n this.documentVersions.set(uri, version)\n this.lastSyncedText.set(uri, text)\n await this.diagnosticsWaiter.wait(uri, DIAGNOSTICS_TIMEOUT_MS)\n return\n }\n\n const prevText = this.lastSyncedText.get(uri)\n if (prevText === text) {\n return\n }\n\n const nextVersion = (this.documentVersions.get(uri) ?? 1) + 1\n this.documentVersions.set(uri, nextVersion)\n this.lastSyncedText.set(uri, text)\n\n this.sendNotification('textDocument/didChange', {\n textDocument: { uri, version: nextVersion },\n contentChanges: [{ text }],\n })\n\n this.sendNotification('textDocument/didSave', {\n textDocument: { uri },\n text,\n })\n }\n\n async definition(filePath: string, line: number, character: number): Promise<unknown> {\n const absPath = resolve(filePath)\n await this.openFile(absPath)\n return this.sendRequest('textDocument/definition', {\n textDocument: { uri: pathToFileURL(absPath).href },\n position: { line: line - 1, character },\n })\n }\n\n async references(\n filePath: string,\n line: number,\n character: number,\n includeDeclaration = true,\n ): Promise<unknown> {\n const absPath = resolve(filePath)\n await this.openFile(absPath)\n return this.sendRequest('textDocument/references', {\n textDocument: { uri: pathToFileURL(absPath).href },\n position: { line: line - 1, character },\n context: { includeDeclaration },\n })\n }\n\n async documentSymbols(filePath: string): Promise<unknown> {\n const absPath = resolve(filePath)\n await this.openFile(absPath)\n return this.sendRequest('textDocument/documentSymbol', {\n textDocument: { uri: pathToFileURL(absPath).href },\n })\n }\n\n async workspaceSymbols(query: string): Promise<unknown> {\n return this.sendRequest('workspace/symbol', { query })\n }\n\n async diagnostics(filePath: string): Promise<{ items: Diagnostic[] }> {\n const absPath = resolve(filePath)\n const uri = pathToFileURL(absPath).href\n await this.openFile(absPath)\n\n try {\n const result = await this.sendRequest<{ items?: Diagnostic[] }>('textDocument/diagnostic', {\n textDocument: { uri },\n })\n if (result && typeof result === 'object' && 'items' in result) {\n return result as { items: Diagnostic[] }\n }\n } catch {}\n\n return { items: this.diagnosticsStore.get(uri) ?? [] }\n }\n\n async prepareRename(filePath: string, line: number, character: number): Promise<unknown> {\n const absPath = resolve(filePath)\n await this.openFile(absPath)\n return this.sendRequest('textDocument/prepareRename', {\n textDocument: { uri: pathToFileURL(absPath).href },\n position: { line: line - 1, character },\n })\n }\n\n async rename(\n filePath: string,\n line: number,\n character: number,\n newName: string,\n ): Promise<unknown> {\n const absPath = resolve(filePath)\n await this.openFile(absPath)\n return this.sendRequest('textDocument/rename', {\n textDocument: { uri: pathToFileURL(absPath).href },\n position: { line: line - 1, character },\n newName,\n })\n }\n}\n\nexport function validateCwd(cwd: string): { valid: boolean; error?: string } {\n try {\n if (!existsSync(cwd)) {\n return { valid: false, error: `Working directory does not exist: ${cwd}` }\n }\n const stats = statSync(cwd)\n if (!stats.isDirectory()) {\n return { valid: false, error: `Path is not a directory: ${cwd}` }\n }\n return { valid: true }\n } catch (err) {\n return {\n valid: false,\n error: `Cannot access working directory: ${cwd} (${err instanceof Error ? err.message : String(err)})`,\n }\n }\n}\n\nexport { LSPServerManager, lspManager } from './lsp-server-manager'\n","import { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { getHomeDir, parseJsonc } from '@x-otto/shared'\nimport { OTTO_PROJECT_DIR } from '@x-otto/env'\nimport { BUILTIN_SERVERS, EXT_TO_LANG, LSP_INSTALL_HINTS } from './constants'\nimport type { ResolvedServer, ServerLookupResult } from './types'\n\ninterface LspEntry {\n disabled?: boolean\n command?: string[]\n extensions?: string[]\n priority?: number\n env?: Record<string, string>\n initialization?: Record<string, unknown>\n}\n\ninterface ConfigJson {\n lsp?: Record<string, LspEntry>\n}\n\ntype ConfigSource = 'project' | 'user'\n\ninterface ServerWithSource extends ResolvedServer {\n source: ConfigSource | 'builtin'\n}\n\nexport function getLanguageId(ext: string): string {\n return EXT_TO_LANG[ext] || 'plaintext'\n}\n\nexport function isServerInstalled(command: string[]): boolean {\n if (command.length === 0) {\n return false\n }\n\n const cmd = command[0]!\n\n if (cmd.includes('/') || cmd.includes('\\\\')) {\n if (existsSync(cmd)) {\n return true\n }\n }\n\n const isWindows = process.platform === 'win32'\n\n let exts = ['']\n if (isWindows) {\n const pathExt = process.env.PATHEXT || ''\n if (pathExt) {\n const systemExts = pathExt.split(';').filter(Boolean)\n exts = [...new Set([...exts, ...systemExts, '.exe', '.cmd', '.bat', '.ps1'])]\n } else {\n exts = ['', '.exe', '.cmd', '.bat', '.ps1']\n }\n }\n\n let pathEnv = process.env.PATH || ''\n if (isWindows && !pathEnv) {\n pathEnv = process.env.Path || ''\n }\n\n const pathSeparator = isWindows ? ';' : ':'\n const paths = pathEnv.split(pathSeparator)\n\n for (const p of paths) {\n for (const suffix of exts) {\n if (existsSync(join(p, cmd + suffix))) {\n return true\n }\n }\n }\n\n const homeDir = getHomeDir()\n const additionalBases = [\n join(OTTO_PROJECT_DIR, 'node_modules', '.bin'),\n join(homeDir, 'bin'),\n join(homeDir, 'node_modules', '.bin'),\n ]\n\n for (const base of additionalBases) {\n for (const suffix of exts) {\n if (existsSync(join(base, cmd + suffix))) {\n return true\n }\n }\n }\n\n if (cmd === 'node') {\n return true\n }\n\n return false\n}\n\nfunction loadJsonFile<T>(path: string): T | null {\n if (!existsSync(path)) {\n return null\n }\n try {\n return parseJsonc(readFileSync(path, 'utf-8')) as T\n } catch {\n return null\n }\n}\n\nfunction detectConfigFilePath(base: string): string {\n const jsonc = base + '.jsonc'\n if (existsSync(jsonc)) {\n return jsonc\n }\n const json = base + '.json'\n if (existsSync(json)) {\n return json\n }\n return jsonc\n}\n\nexport function getConfigPaths(): { project: string; user: string } {\n const homeDir = getHomeDir()\n return {\n project: detectConfigFilePath(join(OTTO_PROJECT_DIR, 'otto')),\n user: detectConfigFilePath(join(homeDir, 'otto')),\n }\n}\n\n// RFC-089 M3: 配置只读一遍——大毫秒级 readFileSync 变缓存命中(微秒级)。\nlet _cachedConfigs: Map<ConfigSource, ConfigJson> | undefined\n\nfunction loadAllConfigs(): Map<ConfigSource, ConfigJson> {\n if (_cachedConfigs) return _cachedConfigs\n\n const paths = getConfigPaths()\n const configs = new Map<ConfigSource, ConfigJson>()\n\n const project = loadJsonFile<ConfigJson>(paths.project)\n if (project) {\n configs.set('project', project)\n }\n\n const user = loadJsonFile<ConfigJson>(paths.user)\n if (user) {\n configs.set('user', user)\n }\n\n _cachedConfigs = configs\n return configs\n}\n\nfunction getMergedServers(): ServerWithSource[] {\n const configs = loadAllConfigs()\n const servers: ServerWithSource[] = []\n const disabled = new Set<string>()\n const seen = new Set<string>()\n\n const sources: ConfigSource[] = ['project', 'user']\n\n for (const source of sources) {\n const config = configs.get(source)\n if (!config?.lsp) {\n continue\n }\n\n for (const [id, entry] of Object.entries(config.lsp)) {\n if (entry.disabled) {\n disabled.add(id)\n continue\n }\n if (seen.has(id)) {\n continue\n }\n if (!entry.command || !entry.extensions) {\n continue\n }\n\n servers.push({\n id,\n command: entry.command,\n extensions: entry.extensions,\n priority: entry.priority ?? 0,\n env: entry.env,\n initialization: entry.initialization,\n source,\n })\n seen.add(id)\n }\n }\n\n for (const [id, config] of Object.entries(BUILTIN_SERVERS)) {\n if (disabled.has(id) || seen.has(id)) {\n continue\n }\n servers.push({\n id,\n command: config.command,\n extensions: config.extensions,\n priority: -100,\n source: 'builtin',\n })\n }\n\n return servers.sort((a, b) => {\n if (a.source !== b.source) {\n const order: Record<string, number> = { project: 0, user: 1, builtin: 2 }\n return (order[a.source] ?? 2) - (order[b.source] ?? 2)\n }\n return b.priority - a.priority\n })\n}\n\nexport function findServerForExtension(ext: string): ServerLookupResult {\n const servers = getMergedServers()\n\n for (const server of servers) {\n if (server.extensions.includes(ext) && isServerInstalled(server.command)) {\n return {\n status: 'found',\n server: {\n id: server.id,\n command: server.command,\n extensions: server.extensions,\n priority: server.priority,\n env: server.env,\n initialization: server.initialization,\n },\n }\n }\n }\n\n for (const server of servers) {\n if (server.extensions.includes(ext)) {\n const installHint =\n LSP_INSTALL_HINTS[server.id] ||\n `Install '${server.command[0]}' and ensure it's in your PATH`\n return {\n status: 'not_installed',\n server: {\n id: server.id,\n command: server.command,\n extensions: server.extensions,\n },\n installHint,\n }\n }\n }\n\n const availableServers = [...new Set(servers.map((s) => s.id))]\n return { status: 'not_configured', extension: ext, availableServers }\n}\n\nexport function getAllServers(): Array<{\n id: string\n installed: boolean\n extensions: string[]\n disabled: boolean\n source: string\n priority: number\n}> {\n const configs = loadAllConfigs()\n const servers = getMergedServers()\n const disabled = new Set<string>()\n\n for (const config of configs.values()) {\n if (!config.lsp) {\n continue\n }\n for (const [id, entry] of Object.entries(config.lsp)) {\n if (entry.disabled) {\n disabled.add(id)\n }\n }\n }\n\n const result: Array<{\n id: string\n installed: boolean\n extensions: string[]\n disabled: boolean\n source: string\n priority: number\n }> = []\n\n const seen = new Set<string>()\n\n for (const server of servers) {\n if (seen.has(server.id)) {\n continue\n }\n result.push({\n id: server.id,\n installed: isServerInstalled(server.command),\n extensions: server.extensions,\n disabled: false,\n source: server.source,\n priority: server.priority,\n })\n seen.add(server.id)\n }\n\n for (const id of disabled) {\n if (seen.has(id)) {\n continue\n }\n const builtin = BUILTIN_SERVERS[id]\n result.push({\n id,\n installed: builtin ? isServerInstalled(builtin.command) : false,\n extensions: builtin?.extensions || [],\n disabled: true,\n source: 'disabled',\n priority: 0,\n })\n }\n\n return result\n}\n","import { resolve, extname, dirname, join } from 'node:path'\nimport { existsSync, statSync } from 'node:fs'\nimport { LSPClient, lspManager } from './lsp-client'\nimport { findServerForExtension } from './server-config'\nimport { toolError } from '../tool-error'\nimport type { ServerLookupResult } from './types'\n\nconst WORKSPACE_MARKERS = [\n '.git',\n 'package.json',\n 'pyproject.toml',\n 'Cargo.toml',\n 'go.mod',\n 'pom.xml',\n 'build.gradle',\n]\n\nexport function findWorkspaceRoot(filePath: string): string {\n let dir = resolve(filePath)\n\n if (!existsSync(dir) || !statSync(dir).isDirectory()) {\n dir = dirname(dir)\n }\n\n let prevDir = ''\n while (dir !== prevDir) {\n for (const marker of WORKSPACE_MARKERS) {\n if (existsSync(join(dir, marker))) {\n return dir\n }\n }\n prevDir = dir\n dir = dirname(dir)\n }\n\n return dirname(resolve(filePath))\n}\n\nexport function formatServerLookupError(\n result: Exclude<ServerLookupResult, { status: 'found' }>,\n): string {\n if (result.status === 'not_installed') {\n const { server, installHint } = result\n return [\n `LSP server '${server.id}' is configured but NOT INSTALLED.`,\n '',\n `Command not found: ${server.command[0]}`,\n '',\n 'To install:',\n ` ${installHint}`,\n '',\n `Supported extensions: ${server.extensions.join(', ')}`,\n '',\n 'After installation, the server will be available automatically.',\n ].join('\\n')\n }\n\n return [\n `No LSP server configured for extension: ${result.extension}`,\n '',\n `Available servers: ${result.availableServers.slice(0, 10).join(', ')}${result.availableServers.length > 10 ? '...' : ''}`,\n '',\n \"To add a custom server, configure 'lsp' in otto.jsonc:\",\n ' {',\n ' \"lsp\": {',\n ' \"my-server\": {',\n ' \"command\": [\"my-lsp\", \"--stdio\"],',\n ` \"extensions\": [\"${result.extension}\"]`,\n ' }',\n ' }',\n ' }',\n ].join('\\n')\n}\n\nexport async function withLspClient<T>(\n filePath: string,\n fn: (client: LSPClient) => Promise<T>,\n): Promise<T> {\n const absPath = resolve(filePath)\n const ext = extname(absPath)\n const result = findServerForExtension(ext)\n\n if (result.status !== 'found') {\n throw toolError(formatServerLookupError(result), 'not_found')\n }\n\n const server = result.server\n const root = findWorkspaceRoot(absPath)\n const client = await lspManager.getClient(root, server)\n\n try {\n return await fn(client)\n } catch (e) {\n if (e instanceof Error && e.message.includes('timeout')) {\n const isInitializing = lspManager.isServerInitializing(root, server.id)\n if (isInitializing) {\n throw toolError(\n 'LSP server is still initializing. Please retry in a few seconds. ' +\n `Original error: ${e.message}`,\n 'timeout',\n )\n }\n }\n throw e\n } finally {\n lspManager.releaseClient(root, server.id)\n }\n}\n","import { z } from 'zod'\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport { formatLocation } from './lsp-formatters'\nimport { withLspClient } from './lsp-wrapper'\nimport type { Location, LocationLink } from './types'\n\nconst DefinitionArgsSchema = z.object({\n filePath: z.string().describe('Absolute path to the file'),\n line: z.number().int().min(1).describe('1-based line number'),\n character: z.number().int().min(0).describe('0-based character offset'),\n})\n\ntype DefinitionArgs = z.infer<typeof DefinitionArgsSchema>\n\nexport function createLspDefinition(_projectRoot: string): AgentTool<DefinitionArgs> {\n return {\n name: 'lsp_goto_definition',\n description: 'Jump to symbol definition. Find WHERE something is defined.',\n parameters: DefinitionArgsSchema,\n pathParams: ['filePath'],\n readonly: true,\n\n async execute({ params }): Promise<ToolResult> {\n const result = await withLspClient(params.filePath, async (client) => {\n return (await client.definition(params.filePath, params.line, params.character)) as\n | Location\n | Location[]\n | LocationLink[]\n | null\n })\n\n if (!result) {\n return { content: [{ type: 'text', text: 'No definition found' }] }\n }\n\n const locations = Array.isArray(result) ? result : [result]\n if (locations.length === 0) {\n return { content: [{ type: 'text', text: 'No definition found' }] }\n }\n\n const text = locations.map(formatLocation).join('\\n')\n return { content: [{ type: 'text', text }] }\n },\n }\n}\n","import { z } from 'zod'\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport { DEFAULT_MAX_REFERENCES } from './constants'\nimport { formatLocation } from './lsp-formatters'\nimport { withLspClient } from './lsp-wrapper'\nimport type { Location } from './types'\n\nconst ReferencesArgsSchema = z.object({\n filePath: z.string().describe('Absolute path to the file'),\n line: z.number().int().min(1).describe('1-based line number'),\n character: z.number().int().min(0).describe('0-based character offset'),\n includeDeclaration: z\n .boolean()\n .optional()\n .describe('Include the declaration itself (default true)'),\n})\n\ntype ReferencesArgs = z.infer<typeof ReferencesArgsSchema>\n\nexport function createLspReferences(_projectRoot: string): AgentTool<ReferencesArgs> {\n return {\n name: 'lsp_find_references',\n description: 'Find ALL usages/references of a symbol across the entire workspace.',\n parameters: ReferencesArgsSchema,\n pathParams: ['filePath'],\n readonly: true,\n\n async execute({ params }): Promise<ToolResult> {\n const result = await withLspClient(params.filePath, async (client) => {\n return (await client.references(\n params.filePath,\n params.line,\n params.character,\n params.includeDeclaration ?? true,\n )) as Location[] | null\n })\n\n if (!result || result.length === 0) {\n return { content: [{ type: 'text', text: 'No references found' }] }\n }\n\n const total = result.length\n const truncated = total > DEFAULT_MAX_REFERENCES\n const limited = truncated ? result.slice(0, DEFAULT_MAX_REFERENCES) : result\n const lines = limited.map(formatLocation)\n if (truncated) {\n lines.unshift(`Found ${total} references (showing first ${DEFAULT_MAX_REFERENCES}):`)\n }\n\n return { content: [{ type: 'text', text: lines.join('\\n') }] }\n },\n }\n}\n","import { z } from 'zod'\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport { DEFAULT_MAX_SYMBOLS } from './constants'\nimport { formatDocumentSymbol, formatSymbolInfo } from './lsp-formatters'\nimport { withLspClient } from './lsp-wrapper'\nimport type { DocumentSymbol, SymbolInfo } from './types'\n\nconst SymbolsArgsSchema = z.object({\n filePath: z.string().describe('File path for LSP context'),\n scope: z\n .enum(['document', 'workspace'])\n .default('document')\n .describe(\"'document' for file symbols, 'workspace' for project-wide search\"),\n query: z.string().optional().describe('Symbol name to search (required for workspace scope)'),\n limit: z.number().int().min(1).max(500).optional().describe('Max results (default/max 200)'),\n})\n\ntype SymbolsArgs = z.infer<typeof SymbolsArgsSchema>\n\nexport function createLspSymbols(_projectRoot: string): AgentTool<SymbolsArgs> {\n return {\n name: 'lsp_symbols',\n description:\n \"Get symbols from file (document) or search across workspace. Use scope='document' for file outline, scope='workspace' for project-wide symbol search.\",\n parameters: SymbolsArgsSchema,\n pathParams: ['filePath'],\n readonly: true,\n\n async execute({ params }): Promise<ToolResult> {\n const scope = params.scope ?? 'document'\n\n if (scope === 'workspace') {\n if (!params.query) {\n return {\n content: [{ type: 'text', text: \"Error: 'query' is required for workspace scope\" }],\n isError: true,\n errorKind: 'validation',\n }\n }\n\n const result = await withLspClient(params.filePath, async (client) => {\n return (await client.workspaceSymbols(params.query!)) as SymbolInfo[] | null\n })\n\n if (!result || result.length === 0) {\n return { content: [{ type: 'text', text: 'No symbols found' }] }\n }\n\n const total = result.length\n const limit = Math.min(params.limit ?? DEFAULT_MAX_SYMBOLS, DEFAULT_MAX_SYMBOLS)\n const truncated = total > limit\n const limited = result.slice(0, limit)\n const lines = limited.map(formatSymbolInfo)\n if (truncated) {\n lines.unshift(`Found ${total} symbols (showing first ${limit}):`)\n }\n\n return { content: [{ type: 'text', text: lines.join('\\n') }] }\n }\n\n const result = await withLspClient(params.filePath, async (client) => {\n return (await client.documentSymbols(params.filePath)) as\n | DocumentSymbol[]\n | SymbolInfo[]\n | null\n })\n\n if (!result || result.length === 0) {\n return { content: [{ type: 'text', text: 'No symbols found' }] }\n }\n\n const total = result.length\n const limit = Math.min(params.limit ?? DEFAULT_MAX_SYMBOLS, DEFAULT_MAX_SYMBOLS)\n const truncated = total > limit\n const limited = truncated ? result.slice(0, limit) : result\n\n const lines: string[] = []\n if (truncated) {\n lines.push(`Found ${total} symbols (showing first ${limit}):`)\n }\n\n if (limited[0] && 'range' in limited[0]) {\n lines.push(...(limited as DocumentSymbol[]).map((s) => formatDocumentSymbol(s)))\n } else {\n lines.push(...(limited as SymbolInfo[]).map(formatSymbolInfo))\n }\n\n return { content: [{ type: 'text', text: lines.join('\\n') }] }\n },\n }\n}\n","import { z } from 'zod'\nimport { DEFAULT_MAX_DIAGNOSTICS } from './constants'\nimport { filterDiagnosticsBySeverity, formatDiagnostic } from './lsp-formatters'\nimport { withLspClient } from './lsp-wrapper'\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport type { Diagnostic } from './types'\n\nconst DiagnosticsArgsSchema = z.object({\n filePath: z.string().describe('Absolute path to the file'),\n severity: z\n .enum(['error', 'warning', 'information', 'hint', 'all'])\n .optional()\n .describe('Filter by severity level'),\n})\n\ntype DiagnosticsArgs = z.infer<typeof DiagnosticsArgsSchema>\n\nexport function createLspDiagnostics(_projectRoot: string): AgentTool<DiagnosticsArgs> {\n return {\n name: 'lsp_diagnostics',\n description: 'Get errors, warnings, hints from language server BEFORE running build.',\n parameters: DiagnosticsArgsSchema,\n pathParams: ['filePath'],\n readonly: true,\n\n async execute({ params }): Promise<ToolResult> {\n const result = await withLspClient(params.filePath, async (client) => {\n return (await client.diagnostics(params.filePath)) as\n | { items?: Diagnostic[] }\n | Diagnostic[]\n | null\n })\n\n let diagnostics: Diagnostic[] = []\n if (result) {\n if (Array.isArray(result)) {\n diagnostics = result\n } else if (result.items) {\n diagnostics = result.items\n }\n }\n\n diagnostics = filterDiagnosticsBySeverity(diagnostics, params.severity)\n\n if (diagnostics.length === 0) {\n return { content: [{ type: 'text', text: 'No diagnostics found' }] }\n }\n\n const total = diagnostics.length\n const truncated = total > DEFAULT_MAX_DIAGNOSTICS\n const limited = truncated ? diagnostics.slice(0, DEFAULT_MAX_DIAGNOSTICS) : diagnostics\n const lines = limited.map(formatDiagnostic)\n if (truncated) {\n lines.unshift(`Found ${total} diagnostics (showing first ${DEFAULT_MAX_DIAGNOSTICS}):`)\n }\n\n return { content: [{ type: 'text', text: lines.join('\\n') }] }\n },\n }\n}\n","import { z } from 'zod'\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport { formatApplyResult, formatPrepareRenameResult } from './lsp-formatters'\nimport { withLspClient } from './lsp-wrapper'\nimport { applyWorkspaceEdit } from './workspace-edit'\nimport type { PrepareRenameDefaultBehavior, PrepareRenameResult, WorkspaceEdit } from './types'\n\nconst PrepareRenameArgsSchema = z.object({\n filePath: z.string().describe('Absolute path to the file'),\n line: z.number().int().min(1).describe('1-based line number'),\n character: z.number().int().min(0).describe('0-based character offset'),\n})\n\ntype PrepareRenameArgs = z.infer<typeof PrepareRenameArgsSchema>\n\nexport function createLspPrepareRename(_projectRoot: string): AgentTool<PrepareRenameArgs> {\n return {\n name: 'lsp_prepare_rename',\n description: 'Check if rename is valid. Use BEFORE lsp_rename.',\n parameters: PrepareRenameArgsSchema,\n pathParams: ['filePath'],\n\n async execute({ params }): Promise<ToolResult> {\n const result = await withLspClient(params.filePath, async (client) => {\n return (await client.prepareRename(params.filePath, params.line, params.character)) as\n | PrepareRenameResult\n | PrepareRenameDefaultBehavior\n | null\n })\n\n const text = formatPrepareRenameResult(result)\n return { content: [{ type: 'text', text }] }\n },\n }\n}\n\nconst RenameArgsSchema = z.object({\n filePath: z.string().describe('Absolute path to the file'),\n line: z.number().int().min(1).describe('1-based line number'),\n character: z.number().int().min(0).describe('0-based character offset'),\n newName: z.string().describe('New symbol name'),\n})\n\ntype RenameArgs = z.infer<typeof RenameArgsSchema>\n\nexport function createLspRename(_projectRoot: string): AgentTool<RenameArgs> {\n return {\n name: 'lsp_rename',\n description: 'Rename symbol across entire workspace. APPLIES changes to all files.',\n parameters: RenameArgsSchema,\n pathParams: ['filePath'],\n\n async execute({ params }): Promise<ToolResult> {\n const edit = await withLspClient(params.filePath, async (client) => {\n return (await client.rename(\n params.filePath,\n params.line,\n params.character,\n params.newName,\n )) as WorkspaceEdit | null\n })\n\n const result = await applyWorkspaceEdit(edit)\n const text = formatApplyResult(result)\n return { content: [{ type: 'text', text }] }\n },\n }\n}\n","import { z } from 'zod'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\n\nconst SessionManagerArgsSchema = z.object({\n action: z\n .enum(['list', 'create', 'remove', 'compact'])\n .describe('Session management action to perform'),\n sessionId: z.string().optional().describe('Session ID (required for remove/compact)'),\n title: z.string().optional().describe('New session title (optional when creating)'),\n})\n\ntype SessionManagerArgs = z.infer<typeof SessionManagerArgsSchema>\n\nexport interface SessionManagerTool {\n list: () => Promise<Array<{ id: string; title: string; messageCount: number }>>\n create: (title?: string) => Promise<{ id: string }>\n remove: (id: string) => Promise<boolean>\n compact: (id: string) => Promise<boolean>\n}\n\ninterface SessionManagerToolOptions {\n sessionManager?: SessionManagerTool\n}\n\nexport function createSessionManagerTool(\n options: SessionManagerToolOptions,\n): AgentTool<SessionManagerArgs> {\n const { sessionManager } = options\n\n return {\n name: 'session_manager',\n description: 'Manage conversation sessions: list, create, remove, compact.',\n parameters: SessionManagerArgsSchema,\n\n async execute({ params }): Promise<ToolResult> {\n if (!sessionManager) {\n throw new Error('SessionManager is not provided in options')\n }\n\n return await execute(params.action, sessionManager, params.sessionId, params.title)\n },\n }\n}\n\nasync function execute(\n action: 'list' | 'create' | 'remove' | 'compact',\n sessionManager: SessionManagerTool,\n sessionId?: string,\n title?: string,\n): Promise<ToolResult> {\n switch (action) {\n case 'list': {\n const sessions = await sessionManager.list()\n\n const text =\n sessions.length === 0\n ? 'No sessions found.'\n : sessions.map((s) => `- ${s.id}: ${s.title} (${s.messageCount} messages)`).join('\\n')\n return { content: [{ type: 'text', text }] }\n }\n\n case 'create': {\n const session = await sessionManager.create(title)\n return { content: [{ type: 'text', text: `Session created: ${session.id}` }] }\n }\n\n case 'remove': {\n if (!sessionId) {\n throw new Error('sessionId required for remove')\n }\n\n const removed = await sessionManager.remove(sessionId)\n return {\n content: [\n { type: 'text', text: removed ? `Session ${sessionId} removed` : 'Session not found' },\n ],\n isError: !removed,\n }\n }\n case 'compact': {\n if (!sessionId) {\n throw new Error('sessionId required for compact')\n }\n const compacted = await sessionManager.compact(sessionId)\n\n return {\n content: [\n { type: 'text', text: compacted ? `Session ${sessionId} compacted` : 'Compact failed' },\n ],\n isError: !compacted,\n }\n }\n }\n}\n","import { resolve } from 'node:path'\nimport { z } from 'zod'\nimport { missingCapability } from '../orchestration/missing-capability'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\n\nconst SkillArgsSchema = z.object({\n action: z\n .enum(['load', 'execute'])\n .describe(\"'load' to register a skill; 'execute' to run it (auto-loads if needed)\"),\n path: z\n .string()\n .optional()\n .describe('SKILL.md file path, relative to project root (load action)'),\n name: z\n .string()\n .optional()\n .describe('Skill name (execute action; if path is also given, loads first)'),\n})\n\ntype SkillArgs = z.infer<typeof SkillArgsSchema>\n\nexport type LoadSkill = (path: string) => Promise<{\n success: boolean\n name?: string\n error?: string\n}>\n\nexport type ExecuteSkill = (name: string) => Promise<{\n success: boolean\n content?: string\n error?: string\n}>\n\nexport function createSkill(\n projectRoot: string,\n load: LoadSkill,\n execute?: ExecuteSkill,\n): AgentTool<SkillArgs> {\n return {\n name: 'skill',\n description:\n 'Load or execute a reusable skill (workflow template). Use action=load to register a SKILL.md; use action=execute to run a loaded skill (auto-loads via path if not yet registered).',\n parameters: SkillArgsSchema,\n pathParams: ['path'],\n // RFC-074 R-ERRKIND:path 经 projectRoot 解析(与 fs 工具一致),免裸相对路径穿越。\n resolvePath: (raw) => resolve(projectRoot, raw),\n\n async execute({ params }): Promise<ToolResult> {\n switch (params.action) {\n case 'load': {\n if (!params.path) {\n return {\n content: [{ type: 'text', text: 'path is required for load action' }],\n isError: true,\n errorKind: 'validation',\n }\n }\n const result = await load(params.path)\n if (result.success) {\n return {\n content: [\n { type: 'text', text: `Skill \"${result.name}\" loaded from ${params.path}` },\n ],\n }\n }\n return {\n content: [{ type: 'text', text: `Failed to load skill: ${result.error}` }],\n isError: true,\n errorKind: 'runtime',\n }\n }\n\n case 'execute': {\n if (!execute) {\n return missingCapability('skill(execute)')\n }\n if (params.path) {\n const loadResult = await load(params.path)\n if (!loadResult.success) {\n return {\n content: [{ type: 'text', text: `Failed to load skill: ${loadResult.error}` }],\n isError: true,\n errorKind: 'runtime',\n }\n }\n const execName = loadResult.name ?? params.name\n if (!execName) {\n return {\n content: [\n {\n type: 'text',\n text: 'Cannot execute: no skill name resolved from path or argument',\n },\n ],\n isError: true,\n errorKind: 'validation',\n }\n }\n const result = await execute(execName)\n return formatExecuteResult(result)\n }\n if (!params.name) {\n return {\n content: [{ type: 'text', text: 'name or path is required for execute action' }],\n isError: true,\n errorKind: 'validation',\n }\n }\n const result = await execute(params.name)\n return formatExecuteResult(result)\n }\n }\n },\n }\n}\n\nfunction formatExecuteResult(result: {\n success: boolean\n content?: string\n error?: string\n}): ToolResult {\n if (result.success) {\n const text = result.content ?? 'Skill executed successfully'\n return { content: [{ type: 'text', text }] }\n }\n return { content: [{ type: 'text', text: `Skill failed: ${result.error}` }], isError: true, errorKind: 'runtime' }\n}\n","import { z } from 'zod'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\n\nconst MemoryRecordArgsSchema = z.object({\n name: z.string().describe('Short, stable memory title (used as the lookup key)'),\n description: z.string().describe('One-line hook shown in the always-visible memory index'),\n content: z.string().describe('The full fact to remember across sessions'),\n scope: z\n .enum(['workspace', 'local', 'project-shared'])\n .optional()\n .default('workspace')\n .describe(\n \"Memory scope (default 'workspace'). Use 'workspace' for durable structured facts stored in the \" +\n \"AutoMemory index+entry store, portable across machines (preferences, gotchas, decisions). Use \" +\n \"'local' for machine-specific facts that should never sync to other devices (local absolute paths, \" +\n \"installed plugins/extensions, environment quirks, OS-specific gotchas). Use 'project-shared' for \" +\n 'team-visible project conventions that belong in AGENTS.md (build/test commands, code style, ' +\n 'architecture decisions all contributors should see when opening this project) — written directly ' +\n 'to the project AGENTS.md file, never edit/write it directly.',\n ),\n})\n\ntype MemoryRecordArgs = z.infer<typeof MemoryRecordArgsSchema>\n\nexport type RecordMemory = (entry: {\n name: string\n description: string\n content: string\n scope?: 'workspace' | 'local' | 'project-shared'\n}) => Promise<void>\n\nexport function createMemoryRecord(record?: RecordMemory): AgentTool<MemoryRecordArgs> {\n return {\n name: 'memory_record',\n description:\n 'Persist a fact across sessions (auto-memory). The memory index is injected into future sessions; use memory_read to retrieve full entries.',\n parameters: MemoryRecordArgsSchema,\n\n async execute({ params }): Promise<ToolResult> {\n if (!record) {\n return {\n content: [\n {\n type: 'text',\n text: 'Auto-memory is not configured. Set storage.memory in createApp() options.',\n },\n ],\n isError: true,\n errorKind: 'runtime',\n }\n }\n\n await record(params)\n return {\n content: [{ type: 'text', text: `Memory \"${params.name}\" recorded.` }],\n }\n },\n }\n}\n","import { z } from 'zod'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\n\nconst MemoryReadArgsSchema = z.object({\n name: z.string().describe('The memory title as shown in the memory index'),\n})\n\ntype MemoryReadArgs = z.infer<typeof MemoryReadArgsSchema>\n\nexport type ReadMemory = (name: string) => Promise<string | null>\n\nexport function createMemoryRead(read?: ReadMemory): AgentTool<MemoryReadArgs> {\n return {\n name: 'memory_read',\n description:\n 'Read the full content of a persisted memory entry by its name (see the memory index in the system prompt).',\n parameters: MemoryReadArgsSchema,\n readonly: true,\n\n async execute({ params }): Promise<ToolResult> {\n if (!read) {\n return {\n content: [\n {\n type: 'text',\n text: 'Auto-memory is not configured. Set storage.memory in createApp() options.',\n },\n ],\n isError: true,\n errorKind: 'runtime',\n }\n }\n\n const content = await read(params.name)\n if (content === null) {\n return {\n content: [{ type: 'text', text: `Memory \"${params.name}\" not found.` }],\n isError: true,\n errorKind: 'not_found',\n }\n }\n return { content: [{ type: 'text', text: content }] }\n },\n }\n}\n","import { z } from 'zod'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\n\n/**\n * memory-archive.ts — RFC-340 M2:会话归档召回工具。\n *\n * 背景:压缩(compaction)把旧历史折叠成摘要后,原始消息仍完整保留在会话树里\n * (RFC-159 红线保证 `session_entries` 永不删行),`MemoryManager` 也早已实现了\n * 按 compaction 边界重建归档全文的能力(`readArchiveFromEntries`)——但这套能力\n * **从未暴露给模型**(终局复审 R3 实证:零工具、零生产调用方)。\n *\n * 后果是 RFC-340 M1 的低保真标注只完成了半环:标注告诉模型\"有 N% 历史没进摘要\",\n * 模型却没有任何手段找回,标注沦为焦虑提示而非可行动信息。本文件补上下半环。\n */\n\n/** 归档条目的工具层视图——不依赖 `@x-otto/memory` 的 `ArchiveEntry`(保持 tools 包分层纯净)。 */\nexport interface ArchiveEntryView {\n path: string\n timestamp: number\n messageCount: number\n summary: string\n}\n\nexport type ReadArchive = (archivePath: string) => Promise<string>\nexport type ListArchives = (sessionId: string) => Promise<ArchiveEntryView[]>\n\n/**\n * `archive:<sessionId>:<idx>` 的解析。\n *\n * sessionId 自身可含冒号(会话 id 无字符集约束),故用**贪婪匹配 + 末段为序号**的\n * 形状,与 `MemoryManager.readArchiveFromEntries` 的正则保持一致,避免两处解析口径\n * 分裂导致\"工具认为合法、后端认为非法\"的静默不一致。\n */\nconst ARCHIVE_PATH_RE = /^archive:(.+):(\\d+)$/\n\nexport function parseArchivePath(archivePath: string): { sessionId: string; index: number } | null {\n const m = ARCHIVE_PATH_RE.exec(archivePath)\n if (!m) return null\n return { sessionId: m[1]!, index: Number.parseInt(m[2]!, 10) }\n}\n\nconst ArchiveReadArgsSchema = z.object({\n archivePath: z\n .string()\n .describe(\n 'The archive id shown in a compaction summary, in the form \"archive:<sessionId>:<n>\".',\n ),\n})\n\ntype ArchiveReadArgs = z.infer<typeof ArchiveReadArgsSchema>\n\nconst ArchiveListArgsSchema = z.object({})\ntype ArchiveListArgs = z.infer<typeof ArchiveListArgsSchema>\n\nconst NOT_CONFIGURED =\n 'Conversation archive recall is not available in this session (no session-entry backed storage).'\n\n/**\n * RFC-340 M2 D2-B-2:**会话隔离是硬约束**。\n *\n * `archive:<sessionId>:<n>` 里的 sessionId 是模型可见、可自行拼装的字符串——若不校验,\n * 模型能通过伪造 sessionId 读到**其他会话**的历史全文,是跨会话信息泄漏。\n *\n * 故在工具层用**权威来源** `ToolCallContext.sessionId`(由引擎注入,模型无法影响)\n * 与入参解析出的 target 比对,不等即拒绝且不下发任何内容。判定方向为 fail-closed:\n * 拿不到当前会话 id(`ctx.sessionId` 缺省)时同样拒绝,而非放行——无法确认归属时\n * 默认不给,与 RFC-287 R2 的双层 origin 强制同源。\n */\nfunction assertSameSession(\n archivePath: string,\n currentSessionId: string | undefined,\n): ToolResult | null {\n const parsed = parseArchivePath(archivePath)\n if (!parsed) {\n return {\n content: [\n {\n type: 'text',\n text: `Invalid archive id \"${archivePath}\". Expected the form \"archive:<sessionId>:<n>\" exactly as shown in the compaction summary.`,\n },\n ],\n isError: true,\n errorKind: 'validation',\n }\n }\n if (!currentSessionId || parsed.sessionId !== currentSessionId) {\n return {\n content: [\n {\n type: 'text',\n text: 'This archive belongs to a different conversation and cannot be read from here.',\n },\n ],\n isError: true,\n errorKind: 'permission',\n }\n }\n return null\n}\n\n/**\n * 读取一段被压缩折叠的历史全文。\n *\n * 只读工具——结果走引擎既有的 `clampWithSpill` 体量钳制,本工具**不另做裁剪**\n * (RFC-340 M2 D2-B-3:在没有统一预算账本前不新增独立裁剪层,见终局复审 R1)。\n */\nexport function createMemoryArchiveRead(read?: ReadArchive): AgentTool<ArchiveReadArgs> {\n return {\n name: 'memory_archive_read',\n description:\n 'Read the full original messages behind a compaction summary. Use this when a summary is marked low-fidelity, or when you need details the summary does not cover. Pass the archive id shown in the summary.',\n parameters: ArchiveReadArgsSchema,\n readonly: true,\n\n async execute({ params, sessionId }): Promise<ToolResult> {\n if (!read) {\n return {\n content: [{ type: 'text', text: NOT_CONFIGURED }],\n isError: true,\n errorKind: 'runtime',\n }\n }\n\n const denied = assertSameSession(params.archivePath, sessionId)\n if (denied) return denied\n\n const content = await read(params.archivePath)\n if (!content) {\n return {\n content: [\n {\n type: 'text',\n text: `Archive \"${params.archivePath}\" not found in this conversation.`,\n },\n ],\n isError: true,\n errorKind: 'not_found',\n }\n }\n return { content: [{ type: 'text', text: content }] }\n },\n }\n}\n\n/** 列举当前会话已发生的压缩归档(供模型自行定位要读哪一段)。 */\nexport function createMemoryArchiveList(list?: ListArchives): AgentTool<ArchiveListArgs> {\n return {\n name: 'memory_archive_list',\n description:\n 'List the compaction archives of the current conversation (each entry = one folded chunk of earlier history, with its archive id and a summary preview).',\n parameters: ArchiveListArgsSchema,\n readonly: true,\n\n async execute({ sessionId }): Promise<ToolResult> {\n if (!list) {\n return {\n content: [{ type: 'text', text: NOT_CONFIGURED }],\n isError: true,\n errorKind: 'runtime',\n }\n }\n // 会话归属同样 fail-closed:无权威 sessionId 时不列举(不猜、不默认全量)。\n if (!sessionId) {\n return {\n content: [{ type: 'text', text: NOT_CONFIGURED }],\n isError: true,\n errorKind: 'runtime',\n }\n }\n\n const entries = await list(sessionId)\n if (entries.length === 0) {\n return {\n content: [{ type: 'text', text: 'No compaction archives yet in this conversation.' }],\n }\n }\n\n const lines = entries.map(\n (e) =>\n `- ${e.path} — ${e.messageCount} messages, ${new Date(e.timestamp).toISOString()}\\n ${e.summary}`,\n )\n return { content: [{ type: 'text', text: lines.join('\\n') }] }\n },\n }\n}\n","import { z } from 'zod'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\n\nconst CronCreateArgs = z.object({\n cron: z.string().describe('5-field cron expression (minute hour dom month dow). Examples: \"0 9 * * 1-5\" = weekdays 9am, \"*/30 * * * *\" = every 30 min'),\n prompt: z.string().describe('Natural language instruction for the agent to execute at the scheduled time'),\n recurring: z.boolean().describe('true = run on schedule repeatedly, false = one-shot (delete after first fire)'),\n name: z.string().optional().describe('Short label for the task (shown in schedule list)'),\n // review I2: default 5min timeout silently killed long-running tasks with no way to\n // self-tune. Exposed as optional so the model can raise it for tasks it expects to take\n // longer (e.g. \"audit the whole monorepo for security issues\").\n maxDurationMs: z.number().int().positive().optional().describe(\n 'Timeout in milliseconds for a single fire attempt (default 300000 = 5 minutes). Raise this for tasks expected to take longer.',\n ),\n})\n\ntype CronCreateArgs = z.infer<typeof CronCreateArgs>\n\ninterface CronCreateResult {\n taskId: string\n nextFireAt: number\n name: string\n}\n\nexport type ScheduleCreateCallback = (input: CronCreateArgs) => CronCreateResult\n\nexport function createScheduleCreate(cb?: ScheduleCreateCallback): AgentTool<CronCreateArgs> {\n return {\n name: 'schedule_create',\n description: 'Schedule a recurring or one-shot scheduled task. The task will fire automatically and execute the prompt via a background agent job.',\n parameters: CronCreateArgs,\n execute: async ({ params }): Promise<ToolResult> => {\n if (!cb) {\n return {\n content: [{ type: 'text', text: 'Schedule scheduling is not available in this session.' }],\n isError: true,\n }\n }\n const result = cb(params)\n return {\n content: [{\n type: 'text',\n text: `Schedule created: ${result.taskId} — \"${result.name}\"\\nNext fire: ${new Date(result.nextFireAt).toLocaleString()}\\nUse schedule_list to view all schedules, schedule_delete to remove.`,\n }],\n }\n },\n }\n}\n","import { z } from 'zod'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\n\ninterface CronTask {\n id: string\n name: string\n cronExpression: string\n enabled: boolean\n recurring: boolean\n nextFireAt: number\n lastFiredAt?: number\n}\n\nexport type ScheduleListCallback = () => CronTask[]\n\nexport function createScheduleList(cb?: ScheduleListCallback): AgentTool<Record<string, never>> {\n return {\n name: 'schedule_list',\n description: 'List all scheduled tasks with their IDs, cron schedules, and next fire times.',\n parameters: z.object({}),\n execute: async (): Promise<ToolResult> => {\n if (!cb) {\n return { content: [{ type: 'text', text: 'Cron scheduling is not available.' }], isError: true }\n }\n const tasks = cb()\n if (tasks.length === 0) {\n return { content: [{ type: 'text', text: 'No scheduled tasks.' }] }\n }\n const lines = tasks.map(t => {\n const status = t.enabled ? '\\u25C9' : '\\u25CB'\n const type = t.recurring ? t.cronExpression : 'one-shot'\n const next = new Date(t.nextFireAt).toLocaleString()\n const last = t.lastFiredAt ? ` last: ${new Date(t.lastFiredAt).toLocaleString()}` : ''\n return `${status} ${t.id} ${t.name}\\n ${type} next: ${next}${last}`\n })\n return { content: [{ type: 'text', text: lines.join('\\n') }] }\n },\n }\n}\n","import { z } from 'zod'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\n\nconst CronDeleteArgs = z.object({\n task_id: z.string().describe('ID of the scheduled task to delete'),\n})\n\ntype CronDeleteArgs = z.infer<typeof CronDeleteArgs>\n\nexport type ScheduleDeleteCallback = (taskId: string) => boolean\n\nexport function createScheduleDelete(cb?: ScheduleDeleteCallback): AgentTool<CronDeleteArgs> {\n return {\n name: 'schedule_delete',\n description: 'Delete a scheduled task by ID. Irreversible.',\n parameters: CronDeleteArgs,\n execute: async ({ params }): Promise<ToolResult> => {\n if (!cb) {\n return { content: [{ type: 'text', text: 'Cron scheduling is not available.' }], isError: true }\n }\n const ok = cb(params.task_id)\n return {\n content: [{\n type: 'text',\n text: ok ? `Schedule \"${params.task_id}\" deleted.` : `Schedule \"${params.task_id}\" not found.`,\n }],\n isError: !ok,\n }\n },\n }\n}\n","import { z } from 'zod'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\n\nconst CronRunArgs = z.object({\n task_id: z.string().describe('ID of the scheduled task to run immediately'),\n})\n\ntype CronRunArgs = z.infer<typeof CronRunArgs>\n\nexport type ScheduleRunCallback = (taskId: string) => void\n\nexport function createScheduleRun(cb?: ScheduleRunCallback): AgentTool<CronRunArgs> {\n return {\n name: 'schedule_run',\n description: 'Run a scheduled task immediately (does not affect its regular schedule).',\n parameters: CronRunArgs,\n execute: async ({ params }): Promise<ToolResult> => {\n if (!cb) {\n return { content: [{ type: 'text', text: 'Cron scheduling is not available.' }], isError: true }\n }\n try {\n cb(params.task_id)\n return { content: [{ type: 'text', text: `Schedule \"${params.task_id}\" triggered.` }] }\n } catch (err) {\n return {\n content: [{ type: 'text', text: `Failed to run schedule \"${params.task_id}\": ${err instanceof Error ? err.message : String(err)}` }],\n isError: true,\n }\n }\n },\n }\n}\n","import { z } from 'zod'\n\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\n\nconst ScheduleCancelArgs = z.object({\n task_id: z.string().describe('ID of the scheduled task whose currently running job should be canceled'),\n})\n\ntype ScheduleCancelArgs = z.infer<typeof ScheduleCancelArgs>\n\nexport interface ScheduleCancelResult {\n ok: boolean\n reason?: string\n}\n\nexport type ScheduleCancelCallback = (taskId: string) => ScheduleCancelResult\n\nexport function createScheduleCancel(cb?: ScheduleCancelCallback): AgentTool<ScheduleCancelArgs> {\n return {\n name: 'schedule_cancel',\n description:\n 'Cancel the currently running job of a scheduled task. The schedule definition is not affected — the task will fire again at its next cron-triggered time. Only works on jobs that are still running or waiting for input; an already-completed or ready (diff-produced) job cannot be canceled.',\n parameters: ScheduleCancelArgs,\n execute: async ({ params }): Promise<ToolResult> => {\n if (!cb) {\n return {\n content: [{ type: 'text', text: 'Schedule cancel is not available in this session.' }],\n isError: true,\n }\n }\n const result = cb(params.task_id)\n if (result.ok) {\n return {\n content: [{\n type: 'text',\n text: `Schedule task \"${params.task_id}\" canceled. The task will fire again at its next scheduled time.`,\n }],\n }\n }\n return {\n content: [{\n type: 'text',\n text: `Cannot cancel schedule \"${params.task_id}\": ${result.reason ?? 'unknown reason'}.`,\n }],\n isError: true,\n }\n },\n }\n}\n","import { createBash } from './shell/bash'\nimport { createBashOutput } from './shell/bash-output'\nimport { createKillShell } from './shell/kill-shell'\nimport { createFind } from './search/find'\nimport { createGrep } from './search/grep'\nimport { createRead } from './fs/read'\nimport { createWrite } from './fs/write'\nimport { createEdit } from './fs/edit'\n\nimport { createWebFetch } from './web/fetch'\nimport { createWebSearch } from './web/search'\n\nimport { createTaskDelegate } from './orchestration/dispatched-task'\nimport { createDelegateJob } from './orchestration/delegate-job'\nimport { createCapabilityGap } from './orchestration/capability-gap'\nimport { createTaskInspect } from './orchestration/task-inspect'\nimport { createTaskControl } from './orchestration/task-control'\nimport { createListModels } from './orchestration/list-models'\nimport { createWriteTodos } from './orchestration/write-todos'\nimport { createCaptureFileState } from './orchestration/capture-file-state'\nimport { createLearn } from './orchestration/learn'\nimport { createGrillMe } from './hitl/grill-me'\nimport { createToolSearch } from './orchestration/tool-search'\n\nimport { createLspDefinition } from './lsp/definition'\nimport { createLspReferences } from './lsp/references'\nimport { createLspSymbols } from './lsp/symbols'\nimport { createLspDiagnostics } from './lsp/diagnostics'\nimport { createLspPrepareRename, createLspRename } from './lsp/rename'\nimport { lspManager } from './lsp/lsp-client'\n\nimport { createSessionManagerTool } from './session/session-manager'\n\nimport { createSkill } from './skill/skill'\nimport { createMemoryRecord } from './memory/memory-record'\nimport { createMemoryRead } from './memory/memory-read'\nimport { createMemoryArchiveRead, createMemoryArchiveList } from './memory/memory-archive'\n\nimport { createScheduleCreate, createScheduleList, createScheduleDelete, createScheduleRun, createScheduleCancel } from './scheduling'\nimport type { ScheduleCreateCallback, ScheduleListCallback, ScheduleDeleteCallback, ScheduleRunCallback, ScheduleCancelCallback } from './scheduling'\n\nimport type { AgentTool } from '@x-otto/interchange'\nimport type { ProcessTracker } from '@x-otto/interchange'\nimport type { ToolPreset } from '@x-otto/setting'\nimport type { ToolRegistry } from './tool-registry'\nimport type { LoadSkill, ExecuteSkill } from './skill/skill'\nimport type {\n AgentsCapability,\n FeaturesCapability,\n ForkCapability,\n MemoryCapability,\n OrchestrationCapability,\n SessionCapability,\n TeamCapability,\n} from './register-builtin'\n\n/**\n * 工具装配上下文。**least-privilege**——只暴露工具装配真需的能力子集,\n * 不再挂整包 `RegisterBuiltinOptions`(宿主编排全套)。每个能力按需注入命名字段,工具工厂无法\n * 越权触达无关宿主编排细节(规则 10:能力层不泄漏宿主知识)。\n */\nexport interface ToolNodeContext {\n readonly projectRoot: string\n readonly registry: ToolRegistry\n readonly orchestration: OrchestrationCapability\n readonly agents: AgentsCapability\n readonly features: FeaturesCapability\n /** 统一降级:skills 缺组时已解析的占位实现(文案单源在 register-builtin)。 */\n readonly loadSkill: LoadSkill\n readonly executeSkill: ExecuteSkill\n /** 委派 fork runner(task_delegate fork mode)。缺省 → 该路径优雅降级。 */\n readonly fork?: ForkCapability\n /** 多代理 team runner(task_delegate team mode)。 */\n readonly team?: TeamCapability\n /** 跨会话 memory 读写(memory_record/memory_read)。 */\n readonly memory?: MemoryCapability\n /** 会话管理工具(session_manager)。缺省 → 整组跳过。 */\n readonly session?: SessionCapability\n /** task_delegate 描述所需的 declared agent profile 列表。 */\n readonly agentProfiles?: Array<{ name: string; description: string; preferredModelTier?: string }>\n /**\n * RFC-287 M4:自迭代回路的生态检索能力。缺省 → `capability_gap` 工具不注册\n * (`when` 门),模型看不到该工具。\n */\n readonly capabilityGap?: { readonly search: import('./orchestration/capability-gap').SearchEcosystem }\n readonly schedule?: {\n readonly create: ScheduleCreateCallback\n readonly list: ScheduleListCallback\n readonly delete: ScheduleDeleteCallback\n readonly run: ScheduleRunCallback\n readonly cancel: ScheduleCancelCallback\n }\n /** RFC-095:进程追踪器(组合根注入)。 */\n readonly processTracker?: ProcessTracker\n}\n\nexport interface ToolNode {\n /**\n * 本节点产出的 canonical 工具名(**必填**)。register-builtin 无条件断言\n * `create` 实产名集合 === names——漏声明或 create 漂移在注册时 fail-fast,不再静默绕过。\n */\n readonly names: readonly string[]\n readonly preset: ToolPreset\n readonly category: string\n /** 行为指南(注入 system prompt;同 category 同串在 buildToolGuidance 渲染时去重)。 */\n readonly guidance?: string\n /** opt-in / 能力闸:缺省恒注册;返回 false → 整组跳过。 */\n readonly when?: (ctx: ToolNodeContext) => boolean\n /** 工具构造(单一来源);返回该节点产出的一组工具。 */\n readonly create: (ctx: ToolNodeContext) => AgentTool[]\n}\n\n/**\n * 异构泛型工具收敛为 `AgentTool[]`:`AgentTool` 在 Args 上不变(执行入参逆变 + schema 协变),\n * 无统一泛型上界,故工厂的 `AgentTool<具体>` 在收集边界一次性收敛——\n * 运行期工具按名分派、参数由各自 zod schema 校验,类型安全不依赖此处的编译期 Args。\n */\nconst asTools = (...items: unknown[]): AgentTool[] => items as AgentTool[]\n\nconst FS_GUIDANCE = [\n 'Use read to understand existing code before making changes. Read targeted ranges instead of entire large files.',\n 'Prefer edit over write for modifying existing files — edit replaces exact text and is safer against accidental overwrites.',\n 'Use write only for creating new files or when the entire file content needs replacement. Prefer editing an existing file over creating a new one.',\n 'Always verify file existence with read before editing to avoid operating on stale assumptions.',\n 'For edit: provide enough surrounding context to avoid ambiguous matches.',\n].join('\\n')\n\nconst SHELL_GUIDANCE = [\n 'Use bash for running tests, installing dependencies, build commands, git operations, and system tasks that have no dedicated tool.',\n 'For long-lived service processes (dev servers, watchers, `http.server`), pass run_in_background:true instead of a blocking call or a trailing `&`. It returns immediately with a background id.',\n 'Then use bash_output to read its accumulated output and kill_shell to stop it. Background processes are auto-killed when the session ends — no manual cleanup of `&` jobs.',\n 'Avoid programs requiring interactive input.',\n 'Prefer dedicated tools (read, write, edit, grep, find) over shell equivalents (cat, sed, grep) for file operations.',\n 'Avoid destructive commands (rm -rf, git reset --hard, DROP TABLE) without explicit user approval.',\n 'Set reasonable timeouts for long-running commands. Kill stale processes rather than waiting indefinitely.',\n 'Always check the exit code; on failure, inspect stdout/stderr before deciding next steps.',\n 'Prefer non-interactive flags, e.g. git --no-pager, --non-interactive.',\n].join('\\n')\n\nconst SEARCH_GUIDANCE = [\n 'Use find to locate files by name or glob pattern; use grep to search file contents by text or regex.',\n 'Start broad (find) then narrow (grep → read) to efficiently navigate unfamiliar codebases. There is no dedicated listing tool — for ad-hoc directory listing run `ls` via bash.',\n 'Combine grep results with read to understand full context around matches.',\n 'grep should use precise patterns; prefer regex alternation for multiple candidates rather than many separate searches.',\n].join('\\n')\n\nconst WEB_GUIDANCE = [\n 'Use web_fetch to read specific URLs when you know the page address.',\n 'Prefer web_search → web_fetch workflow: search first, then fetch specific results. (See the Factual Verification rule in the base instructions for when verification is required.)',\n 'web_fetch cannot render JavaScript-heavy pages (SPAs). Use for documentation, articles, APIs.',\n].join('\\n')\n\nconst ORCH_STANDARD_GUIDANCE = [\n 'Use task_delegate to route tasks to more suitable sub-agents by category — useful for tasks requiring specialization or lifecycle management. Provide clear, complete context — sub-agents start with a blank slate.',\n 'Use write_todos for complex tasks to build and maintain a step list first, then work it to completion. Set a meaningful \"title\" (e.g. \"Bug fixes\", \"Feature implementation\"); defaults to \"Task\" if omitted.',\n 'Todo discipline: keep exactly ONE item in_progress at a time; mark an item done the moment you finish it (do not batch). Do NOT end your turn while any item is still pending or in_progress — either finish the remaining work, or explicitly mark items skipped/failed with a reason. A turn that stops with unfinished todos is reported to the user as incomplete, not done.',\n 'Break complex tasks into small, independently verifiable subtasks (2-5 minutes each) before delegating.',\n 'Do not delegate tasks that require the current conversation context or interactive clarification.',\n].join('\\n')\n\nconst ORCH_FULL_GUIDANCE = [\n 'Use task_delegate for delegation — tracked:false for lightweight synchronous agent opinions, default (tracked) for background or stateful execution with retries.',\n 'Use task_inspect to check task status and output before acting; use task_control to cancel or retry tasks.',\n 'Use grill_me to ask the human ONE key decision (with detailed background + a required recommendation) when a single fork genuinely needs a human steer. Advisory only — it never authorizes side effects.',\n 'Use capture_file_state when conversation is long and you need to refresh understanding of workspace changes.',\n 'Use learn to record reusable insights (patterns, mistakes, strategies) — not routine progress notes.',\n].join('\\n')\n\nconst SESSION_GUIDANCE = [\n 'Use session management to organize separate conversation threads per task or topic.',\n 'Compact sessions proactively when conversation history grows long to avoid context window exhaustion.',\n 'Do not create excessive sessions — reuse existing ones when the topic is the same.',\n].join('\\n')\n\nconst SKILL_GUIDANCE = [\n 'Use skill to load or execute reusable workflow templates. Skills are auto-loaded on first execute if a path is given.',\n 'Prefer invoking a matching skill over ad-hoc multi-step workflows when one exists.',\n 'Skills encapsulate best practices — follow their structure rather than shortcutting steps.',\n].join('\\n')\n\nconst MEMORY_GUIDANCE = [\n 'Use memory_record to persist durable facts (user preferences, environment quirks, project constraints) across sessions.',\n 'The memory index is always visible in the system prompt; fetch full entries on demand with memory_read.',\n \"Choose scope: 'workspace' (default, portable structured facts), 'local' (machine-specific, never synced), \" +\n \"or 'project-shared' (team-visible conventions written to AGENTS.md — never edit/write it directly, .otto state is protected).\",\n].join('\\n')\n\nconst ARCHIVE_GUIDANCE = [\n 'When a conversation summary is marked low-fidelity (only part of the history made it into the summary), use memory_archive_read to pull back the original messages instead of guessing or asking the user to repeat themselves.',\n 'Use memory_archive_list to see which chunks of earlier history are available and pick the right archive id.',\n 'Archives are scoped to the current conversation — you cannot read another session\\'s history.',\n].join('\\n')\n\nexport const TOOL_NODES: readonly ToolNode[] = [\n {\n names: ['read', 'write', 'edit'],\n preset: 'minimal',\n category: 'fs',\n guidance: FS_GUIDANCE,\n create: (c) =>\n asTools(createRead(c.projectRoot), createWrite(c.projectRoot), createEdit(c.projectRoot)),\n },\n {\n names: ['bash'],\n preset: 'minimal',\n category: 'shell',\n guidance: SHELL_GUIDANCE,\n create: (c) =>\n asTools(createBash(c.projectRoot, undefined, c.features.sandbox, c.features.processRegistry)),\n },\n {\n names: ['bash_output', 'kill_shell'],\n preset: 'standard',\n category: 'shell',\n when: (c) => Boolean(c.features.processRegistry),\n create: (c) =>\n asTools(\n createBashOutput(c.features.processRegistry!),\n createKillShell(c.features.processRegistry!),\n ),\n },\n\n {\n names: ['find', 'grep'],\n preset: 'standard',\n category: 'search',\n guidance: SEARCH_GUIDANCE,\n create: (c) =>\n asTools(\n createFind(c.projectRoot, { binaryExecutorRegistry: c.registry.getBinaryToolExecutors() }),\n createGrep(c.projectRoot, {\n binaryToolExecutorRegistry: c.registry.getBinaryToolExecutors(),\n processTracker: c.processTracker,\n }),\n ),\n },\n {\n names: ['web_fetch', 'web_search'],\n preset: 'standard',\n category: 'web',\n guidance: WEB_GUIDANCE,\n create: (c) => asTools(createWebFetch(c.projectRoot), createWebSearch(c.projectRoot)),\n },\n {\n names: ['tool_search'],\n preset: 'standard',\n category: 'orchestration',\n guidance:\n 'Use tool_search to discover tools not shown in the default list (e.g. MCP server tools). Search by name or keyword to load the full parameter schema.',\n create: (c) => asTools(createToolSearch(() => c.registry.project('full').deferred)),\n },\n {\n names: ['task_delegate', 'delegate_job', 'write_todos'],\n preset: 'standard',\n category: 'orchestration',\n guidance: ORCH_STANDARD_GUIDANCE,\n create: (c) =>\n asTools(\n createTaskDelegate(\n c.projectRoot,\n c.orchestration.dispatchTask,\n c.orchestration.writeTodos,\n c.fork?.fork,\n c.team?.runTeam,\n undefined,\n c.agents.callAgent,\n c.agentProfiles ?? [],\n ),\n createDelegateJob(c.orchestration.startJob),\n createWriteTodos(c.orchestration.writeTodos),\n ),\n },\n {\n names: [\n 'lsp_goto_definition',\n 'lsp_find_references',\n 'lsp_symbols',\n 'lsp_diagnostics',\n 'lsp_prepare_rename',\n 'lsp_rename',\n ],\n preset: 'standard',\n category: 'lsp',\n when: (c) => Boolean(c.features.enableLsp),\n create: (c) => {\n // RFC-095: LSP singleton 独立于工具创建——在此注入进程追踪器。只设一次(幂等)。\n if (c.processTracker) lspManager.setProcessTracker(c.processTracker)\n return asTools(\n createLspDefinition(c.projectRoot),\n createLspReferences(c.projectRoot),\n createLspSymbols(c.projectRoot),\n createLspDiagnostics(c.projectRoot),\n createLspPrepareRename(c.projectRoot),\n createLspRename(c.projectRoot),\n )\n },\n },\n\n {\n names: ['list_models', 'task_inspect', 'task_control', 'grill_me', 'capture_file_state', 'learn'],\n preset: 'full',\n category: 'orchestration',\n guidance: ORCH_FULL_GUIDANCE,\n create: (c) =>\n asTools(\n createListModels(c.features.listModels, c.features.sessionId),\n createTaskInspect(c.projectRoot, {\n get: c.orchestration.getTask,\n list: c.orchestration.listTasks,\n output: c.orchestration.getBackgroundOutput,\n }),\n createTaskControl(c.projectRoot, {\n update: c.orchestration.updateTask,\n cancelBackground: c.orchestration.cancelBackground,\n }),\n createGrillMe(c.projectRoot, c.features.grill),\n createCaptureFileState(c.features.captureFileState),\n createLearn(c.features.sessionId ?? '', c.features.learn),\n ),\n },\n {\n names: ['session_manager'],\n preset: 'full',\n category: 'session',\n guidance: SESSION_GUIDANCE,\n when: (c) => Boolean(c.session),\n create: (c) => {\n const session = c.session\n if (!session) {\n return asTools()\n }\n return asTools(\n createSessionManagerTool({ sessionManager: session.manager }),\n )\n },\n },\n {\n names: ['skill'],\n preset: 'full',\n category: 'skill',\n guidance: SKILL_GUIDANCE,\n create: (c) => asTools(createSkill(c.projectRoot, c.loadSkill, c.executeSkill)),\n },\n {\n names: ['memory_record', 'memory_read'],\n preset: 'full',\n category: 'memory',\n guidance: MEMORY_GUIDANCE,\n create: (c) =>\n asTools(\n createMemoryRecord(c.memory?.recordMemory),\n createMemoryRead(c.memory?.readMemory),\n ),\n },\n {\n /**\n * RFC-340 M2:会话归档召回。独立节点而非并入上面的 memory 组——两者能力来源不同\n * (auto-memory 存储 vs 会话树重建),且本组有 `when` 门:归档重建能力未注入时\n * 整组不注册,模型看不到工具,而非看到一个恒失败的工具。\n */\n names: ['memory_archive_read', 'memory_archive_list'],\n preset: 'full',\n category: 'memory',\n when: (c) => Boolean(c.memory?.archive),\n guidance: ARCHIVE_GUIDANCE,\n create: (c) =>\n asTools(\n createMemoryArchiveRead(c.memory?.archive?.read),\n createMemoryArchiveList(c.memory?.archive?.list),\n ),\n },\n {\n names: ['schedule_create', 'schedule_list', 'schedule_delete', 'schedule_run', 'schedule_cancel'],\n preset: 'full',\n category: 'schedule',\n guidance:\n 'Schedule tools — create/list/delete/run/cancel timer tasks that fire agent jobs on a schedule. ' +\n 'Use schedule_create to schedule recurring or one-shot tasks (5-field cron). ' +\n 'Use schedule_list to view all timers with next fire times. ' +\n 'Use schedule_delete to remove a schedule permanently (irreversible). ' +\n 'Use schedule_run to trigger a schedule immediately without affecting its regular schedule. ' +\n 'Use schedule_cancel to cancel the currently running job of a scheduled task.',\n when: (c) => Boolean(c.schedule),\n create: (c) =>\n asTools(\n createScheduleCreate(c.schedule!.create),\n createScheduleList(c.schedule!.list),\n createScheduleDelete(c.schedule!.delete),\n createScheduleRun(c.schedule!.run),\n createScheduleCancel(c.schedule!.cancel),\n ),\n },\n {\n names: ['capability_gap'],\n preset: 'full',\n category: 'orchestration',\n guidance:\n 'Use capability_gap when the user asks for something you genuinely cannot do with your current tools. ' +\n 'It searches the plugin ecosystem for an existing capability and, if none exists, returns a blueprint for ' +\n 'building one (scaffold + delegate_job). It only returns guidance — it never installs anything; ' +\n 'installation and any high-risk capability grant always require explicit user approval.',\n when: (c) => Boolean(c.capabilityGap),\n create: (c) => asTools(createCapabilityGap(c.capabilityGap!.search)),\n },\n]\n","import type { SandboxProvider, SandboxStrategy } from './shell/sandbox/types'\nimport type { BackgroundProcessPort } from './shell/background-types'\n\nimport type { CaptureFileState } from './orchestration/capture-file-state'\nimport type { LearnCallback } from './orchestration/learn'\nimport type { SessionGrill } from './hitl/grill-me'\nimport type { ModelCatalogQuery } from './orchestration/list-models'\nimport type { SessionManagerTool } from './session/session-manager'\nimport type { LoadSkill, ExecuteSkill } from './skill/skill'\nimport type { RecordMemory } from './memory/memory-record'\nimport type { ReadMemory } from './memory/memory-read'\nimport type { ForkRunner, RunTeam } from './orchestration/delegation-runners'\n\nimport type { ToolRegistry } from './tool-registry'\nimport { TOOL_NODES, type ToolNodeContext } from './tool-nodes'\nimport type {\n CallAgent,\n CancelBackground,\n GetBackgroundOutput,\n GetTask,\n ListTasks,\n TaskDispatch,\n UpdateTask,\n WriteTodos,\n} from '@x-otto/orchestration-contracts'\n\nexport interface OrchestrationCapability {\n dispatchTask: TaskDispatch\n getTask?: GetTask\n listTasks?: ListTasks\n updateTask?: UpdateTask\n getBackgroundOutput?: GetBackgroundOutput\n cancelBackground?: CancelBackground\n writeTodos?: WriteTodos\n /** 起一个隔离可变作业(origin:'main')。缺省 → delegate_job 回 missingCapability。 */\n startJob?: import('./orchestration/delegate-job').StartJob\n}\n\nexport interface AgentsCapability {\n callAgent: CallAgent\n}\n\nexport interface ForkCapability {\n fork: ForkRunner\n}\n\nexport interface SkillsCapability {\n loadSkill: LoadSkill\n executeSkill: ExecuteSkill\n}\n\nexport interface MemoryCapability {\n recordMemory: RecordMemory\n readMemory: ReadMemory\n /**\n * RFC-340 M2:会话归档召回(压缩折叠掉的历史全文)。\n *\n * **可选**——归档重建依赖 `MemoryManager` 的 `loadSessionEntries` 注入,V 形态/\n * 无持久化会话拿不到。缺省时归档工具整组**不注册**(`tool-nodes` 的 `when` 门),\n * 模型看不到它,而不是注册一个恒失败的工具(对齐 `capabilityGap` 既有纪律)。\n */\n archive?: {\n read: import('./memory/memory-archive').ReadArchive\n list: import('./memory/memory-archive').ListArchives\n }\n}\n\nexport interface TeamCapability {\n runTeam: RunTeam\n}\n\nexport interface SessionCapability {\n manager: SessionManagerTool\n}\n\nexport interface FeaturesCapability {\n captureFileState?: CaptureFileState\n learn?: LearnCallback\n sessionId?: string\n /** LSP 工具 opt-in(语言服务器需本机安装;缺省不注册)。 */\n enableLsp?: boolean\n /**\n * bash OS 级沙箱策略(缺省 = 无沙箱,保持兼容)。\n * provider 形态支持晚绑定:settings.sandbox 在 app.start() 才 load 且可热更新。\n */\n sandbox?: SandboxStrategy | SandboxProvider\n /** HITL grill 能力(按 sessionId 路由到会话闸)。缺省 → grill_me 兜底 fail-soft。 */\n grill?: SessionGrill\n /** 列出当前会话可用模型 + 能力优势(供 list_models 工具 → subagent 选型)。 */\n listModels?: ModelCatalogQuery\n /**\n * 后台进程注册表 port。注入 → bash 支持 run_in_background +\n * 注册 bash_output/kill_shell;缺省 → run_in_background 优雅降级为前台。\n */\n processRegistry?: BackgroundProcessPort\n}\n\nexport interface RegisterBuiltinOptions {\n orchestration: OrchestrationCapability\n agents: AgentsCapability\n fork?: ForkCapability\n skills?: SkillsCapability\n memory?: MemoryCapability\n team?: TeamCapability\n session?: SessionCapability\n features?: FeaturesCapability\n /** task_delegate 描述所需的 declared agent profile 列表 */\n agentProfiles?: Array<{ name: string; description: string; preferredModelTier?: string }>\n /** Schedule callbacks (schedule_create/list/delete/run/cancel tools). */\n schedule?: {\n readonly create: import('./scheduling').ScheduleCreateCallback\n readonly list: import('./scheduling').ScheduleListCallback\n readonly delete: import('./scheduling').ScheduleDeleteCallback\n readonly run: import('./scheduling').ScheduleRunCallback\n readonly cancel: import('./scheduling').ScheduleCancelCallback\n }\n /** RFC-287 M4:自迭代回路的生态检索能力(缺省 → capability_gap 工具不注册)。 */\n capabilityGap?: { readonly search: import('./orchestration/capability-gap').SearchEcosystem }\n /** RFC-095:进程追踪器(组合根注入,替代 globalProcessRuntime)。 */\n processTracker?: import('@x-otto/interchange').ProcessTracker\n}\n\nconst SKILL_NOT_CONFIGURED = 'Skill provider not configured. Pass skillProvider to createApp().'\n\n/**\n * 反漂移硬约束:node.create 实产工具名集合必须逐项 === node.names。\n * names 现为必填(ToolNode 类型层强制)+ 本断言**无条件**运行(删原 `if (node.names)` 软门)——\n * 任何新增节点漏声明、或 create 产名与声明漂移,都在注册时 fail-fast,而非静默绕过守卫。\n * 抽成导出纯函数:守卫本身可独立单测(证明它真会 fire),不再是内联未测代码。\n */\nexport function assertNodeNamesMatch(\n node: { readonly names: readonly string[]; readonly category: string },\n tools: readonly { readonly name: string }[],\n): void {\n const actual = tools.map((t) => t.name).sort()\n const expected = [...node.names].sort()\n if (actual.length !== expected.length || actual.some((n, i) => n !== expected[i])) {\n throw new Error(\n `TOOL_NODES drift in category \"${node.category}\": declared [${expected.join(', ')}] ` +\n `but factory produced [${actual.join(', ')}]`,\n )\n }\n}\n\nexport function registerBuiltinTools(\n registry: ToolRegistry,\n projectRoot: string,\n options: RegisterBuiltinOptions,\n): void {\n const { orchestration, agents, features = {} } = options\n\n const loadSkill: LoadSkill =\n options.skills?.loadSkill ?? (async () => ({ success: false, error: SKILL_NOT_CONFIGURED }))\n const executeSkill: ExecuteSkill =\n options.skills?.executeSkill ?? (async () => ({ success: false, error: SKILL_NOT_CONFIGURED }))\n\n // least-privilege——只投影工具装配真需的能力,不挂整包 options(宿主编排全套)。\n const ctx: ToolNodeContext = {\n projectRoot,\n registry,\n orchestration,\n agents,\n features,\n loadSkill,\n executeSkill,\n fork: options.fork,\n team: options.team,\n memory: options.memory,\n session: options.session,\n agentProfiles: options.agentProfiles,\n schedule: options.schedule,\n capabilityGap: options.capabilityGap,\n processTracker: options.processTracker,\n }\n\n for (const node of TOOL_NODES) {\n if (node.when && !node.when(ctx)) {\n continue\n }\n\n const tools = node.create(ctx)\n\n assertNodeNamesMatch(node, tools)\n\n registry.register(tools, {\n preset: node.preset,\n category: node.category,\n builtin: true,\n guideline: node.guidance,\n })\n }\n}\n","import {\n createBinaryToolExecutorRegistry,\n type BinaryToolExecutorRegistry,\n} from './binary/binary-executor-registry'\nimport { registerBuiltinTools, type RegisterBuiltinOptions } from './register-builtin'\nimport type { AgentTool } from '@x-otto/interchange'\nimport type { ToolPreset } from '@x-otto/setting'\nimport type { RegisteredTool, ToolMetadata, ToolRegistrationOptions } from './types'\n\nconst PRESET_INCLUDES: Record<ToolPreset, Set<ToolPreset>> = {\n minimal: new Set(['minimal']),\n standard: new Set(['minimal', 'standard']),\n full: new Set(['minimal', 'standard', 'full']),\n}\n\nfunction isAgentTool(value: unknown): value is AgentTool<unknown> {\n if (!value || typeof value !== 'object') {\n return false\n }\n\n const candidate = value as Partial<AgentTool<unknown>>\n const desc = candidate.description as unknown\n return (\n typeof candidate.name === 'string' &&\n (typeof desc === 'string' || typeof desc === 'function') &&\n typeof candidate.execute === 'function' &&\n candidate.parameters !== undefined\n )\n}\n\nexport class ToolRegistry {\n private readonly tools = new Map<string, RegisteredTool>()\n private binaryToolExecutors: BinaryToolExecutorRegistry | null = null\n private readonly disposers: Array<() => void | Promise<void>> = []\n /**\n * RFC-303 D4:工具面变更通知(register/unregister/unregisterBySource 后调用)。\n * 由宿主(App)注入,桥接为 monitor 事件 `tool.registered`/`tool.unregistered`。\n * 可选——不注入则工具增减不对外广播(不影响注册/注销本身)。\n */\n onMutation?: (delta: { added?: string[]; removed?: string[]; source?: string }) => void\n\n get size(): number {\n return this.tools.size\n }\n\n setBinaryToolExecutors(registry: BinaryToolExecutorRegistry): void {\n this.binaryToolExecutors = registry\n }\n\n getBinaryToolExecutors(): BinaryToolExecutorRegistry {\n if (!this.binaryToolExecutors) {\n throw new Error('BinaryToolExecutorRegistry is not set in ToolRegistry')\n }\n return this.binaryToolExecutors\n }\n\n /** 注册随 registry dispose(app stop)一并释放的清理函数(如 browser session)。best-effort。 */\n registerDisposer(disposer: () => void | Promise<void>): void {\n this.disposers.push(disposer)\n }\n\n register(tool: unknown, options?: ToolRegistrationOptions): void\n register(tools: unknown[], options?: ToolRegistrationOptions): void\n register(tools: unknown | unknown[], options: ToolRegistrationOptions = {}): void {\n const metadata: ToolMetadata = {\n preset: options.preset ?? 'full',\n category: options.category,\n builtin: options.builtin ?? false,\n snippet: options.snippet,\n guideline: options.guideline,\n source: options.source,\n }\n\n const toolList = Array.isArray(tools) ? tools : [tools]\n for (const tool of toolList) {\n if (!isAgentTool(tool)) {\n throw new Error('Invalid tool registration input')\n }\n\n const typedTool = tool\n const desc =\n typeof typedTool.description === 'function'\n ? typedTool.description({ projectRoot: options.projectRoot ?? '' })\n : typedTool.description\n const registered: RegisteredTool = { ...typedTool, description: desc, metadata }\n this.tools.set(typedTool.name, registered)\n }\n // RFC-303 D4:注册完成 → 通知宿主(tool.registered 事件桥接)。\n this.onMutation?.({ added: toolList.map((t) => (t as { name: string }).name), source: options.source })\n }\n\n unregister(name: string): boolean\n unregister(name: string[]): boolean\n unregister(name: string | string[]): boolean {\n const removed: string[] = []\n if (Array.isArray(name)) {\n let allDeleted = true\n for (const n of name) {\n const deleted = this.tools.delete(n)\n if (!deleted) {\n allDeleted = false\n } else {\n removed.push(n)\n }\n }\n // RFC-303 D4:注销完成 → 通知宿主(tool.unregistered 事件桥接)。\n if (removed.length > 0) this.onMutation?.({ removed })\n return allDeleted\n }\n\n const deleted = this.tools.delete(name)\n if (deleted) {\n // RFC-303 D4:同上。\n this.onMutation?.({ removed: [name] })\n }\n return deleted\n }\n\n /**\n * 注销某来源注册的全部工具(RFC-105 D4/R2:插件卸载/reload 批量清理)。\n * 只清理 `metadata.source === source` 的条目——不影响内置工具(source 未设置)或\n * 其他来源。返回注销数量。\n */\n unregisterBySource(source: string): number {\n const names = [...this.tools.values()]\n .filter((t) => t.metadata.source === source)\n .map((t) => t.name)\n for (const name of names) this.tools.delete(name)\n // RFC-303 D4:批量注销完成 → 通知宿主(tool.unregistered 事件桥接,带 source)。\n if (names.length > 0) this.onMutation?.({ removed: names, source })\n return names.length\n }\n\n get(name: string): RegisteredTool | undefined {\n return this.tools.get(name)\n }\n\n has(name: string): boolean {\n return this.tools.has(name)\n }\n\n getAll(): RegisteredTool[] {\n return [...this.tools.values()].sort((a, b) => a.name.localeCompare(b.name))\n }\n\n getAvailable(preset: ToolPreset = 'standard'): RegisteredTool[] {\n const includes = PRESET_INCLUDES[preset] ?? PRESET_INCLUDES.standard\n return this.getAll().filter((tool) => includes.has(tool.metadata.preset))\n }\n\n /** M33-02:按 preset 投影并三分流——direct 进模型列表 / deferred 进 tool_search / hidden 仅 dispatch。 */\n project(preset: ToolPreset = 'standard'): {\n direct: RegisteredTool[]\n deferred: RegisteredTool[]\n } {\n const includes = PRESET_INCLUDES[preset] ?? PRESET_INCLUDES.standard\n const direct: RegisteredTool[] = []\n const deferred: RegisteredTool[] = []\n\n for (const tool of this.getAll()) {\n if (!includes.has(tool.metadata.preset)) continue\n if (tool.exposure === 'deferred') {\n deferred.push(tool)\n } else if (tool.exposure !== 'hidden') {\n direct.push(tool)\n }\n }\n\n return { direct, deferred }\n }\n\n /**\n * Tool Reference 段的默认总预算(tokens)。同 `@x-otto/memory` 的预算门同一治理动机——\n * MCP/插件工具经 `register()` 的 `guideline`/`snippet` 选项可贡献任意长度文本,\n * 且无 per-tool 上限;已安装插件数量增长时这段会无界累积(同 skill catalog 此前的\n * 问题)。本仓 full preset 实测 ~1.7k tokens,取 3 倍留余量,同时挡住\"装了一堆长\n * guideline 插件\"的极端场景。\n */\n private static readonly TOOL_GUIDANCE_BUDGET_TOKENS = 5_000\n\n private static estimatedTokens(text: string): number {\n let cjk = 0\n for (const ch of text) {\n if (/[\\u3000-\\u9fff\\uff00-\\uffef]/.test(ch)) cjk++\n }\n return Math.ceil(cjk + (text.length - cjk) / 4)\n }\n\n /**\n * RFC-303 D6:exposure 泄漏修复——此前用 `getAvailable(preset)` 遍历全集,deferred\n * (MCP 工具默认态)与 hidden 工具的完整 description 会被渐进披露契约以外的方式\n * 泄漏进 system prompt(M33 的核心设计是\"deferred 工具只能经 tool_search 按需发现\",\n * 但指引段一直在无条件展开它们的正文)。改用 `project(preset)` 统一投影:direct 集合\n * 走原有分组渲染,deferred 集合只输出一行聚合提示(不展开 description,省 token 且不\n * 破坏渐进披露),hidden 集合完全不出现(tool_search 也搜不到,仅供内部派发)。\n */\n buildToolGuidance(preset: ToolPreset = 'standard'): string {\n const { direct, deferred } = this.project(preset)\n if (direct.length === 0 && deferred.length === 0) {\n return ''\n }\n\n const groups = new Map<string, { guideline?: string; entries: string[] }>()\n\n for (const tool of direct) {\n const { snippet, guideline, category } = tool.metadata\n const key = guideline ? `${category ?? ''}\\u0000${guideline}` : `\\u0000tool:${tool.name}`\n\n let group = groups.get(key)\n if (!group) {\n group = { guideline, entries: [] }\n groups.set(key, group)\n }\n\n const entry: string[] = [`#### ${tool.name}`]\n if (!guideline && !snippet) {\n entry.push(tool.description as string)\n }\n if (snippet) {\n entry.push(`Example:\\n\\`\\`\\`\\n${snippet}\\n\\`\\`\\``)\n }\n group.entries.push(entry.join('\\n'))\n }\n\n const blocks: string[] = []\n for (const group of groups.values()) {\n const block: string[] = []\n if (group.guideline) {\n block.push(group.guideline)\n }\n block.push(...group.entries)\n blocks.push(block.join('\\n\\n'))\n }\n\n // 预算门:按 block(每 block = 一个 category/guideline 分组)整体取舍——不在\n // block 内部截断,那样会切碎单个工具的用法说明,价值不如\"完整保留高优先分组、\n // 舍弃超出预算的分组\"。保留顺序 = 注册顺序(与既有行为一致,不重排优先级)。\n const budget = ToolRegistry.TOOL_GUIDANCE_BUDGET_TOKENS\n let usedTokens = 0\n const kept: string[] = []\n let omittedCount = 0\n for (const block of blocks) {\n const blockTokens = ToolRegistry.estimatedTokens(block)\n if (usedTokens + blockTokens <= budget) {\n kept.push(block)\n usedTokens += blockTokens\n } else {\n omittedCount++\n }\n }\n if (omittedCount > 0) {\n kept.push(\n `> ⚠ [${omittedCount} additional tool guidance section(s) omitted — guidance budget (${budget} tokens) reached; use tool_search or the tool's own description]`,\n )\n }\n\n // RFC-303 D6:deferred 工具(MCP 默认态)只报数量与发现方式,不展开 description——\n // 展开等于击穿 M33 的渐进披露契约(模型应经 tool_search 按需发现,而非在指引段\n // 提前看到全文)。hidden 工具不在此处理——它们不进 project() 的 direct/deferred\n // 任一桶,本函数结构上不会触碰。\n if (deferred.length > 0) {\n kept.push(\n `> ${deferred.length} additional tool(s) discoverable via tool_search (not shown here to keep this section focused; use tool_search to look them up by name or capability).`,\n )\n }\n\n if (kept.length === 0) {\n return ''\n }\n\n return `### Tool Reference (per category)\\n\\n${kept.join('\\n\\n')}`\n }\n\n clear(): void {\n if (this.tools.size > 0) {\n this.tools.clear()\n }\n }\n\n dispose(): void {\n this.clear()\n if (this.binaryToolExecutors) {\n this.binaryToolExecutors.dispose()\n this.binaryToolExecutors = null\n }\n for (const disposer of this.disposers.splice(0)) {\n void Promise.resolve()\n .then(disposer)\n .catch(() => undefined)\n }\n }\n}\n\nexport const createToolRegistry = (\n workspaceDir: string,\n options: RegisterBuiltinOptions,\n): ToolRegistry => {\n const registry = new ToolRegistry()\n\n const binaryRegistry = createBinaryToolExecutorRegistry(workspaceDir, options.processTracker)\n registry.setBinaryToolExecutors(binaryRegistry)\n\n registerBuiltinTools(registry, workspaceDir, options)\n\n return registry\n}\n","/**\n * 直接派生子代理的工具名(**子代理生成器 denylist**)——`allowSubagents=false` 时由\n * session-config-resolver 据此剔除,防锁定 agent 递归起子代理。\n *\n * RFC-074 D9:这是 orchestration `category` 的**有意子集**(category 还含 write_todos/list_models/\n * task_inspect 等管理类,非生成器,不应被剔),故不从 category 派生。新增\"会起子代理\"的工具时\n * 必须在此登记;`tool-nodes.test` 的漂移守卫断言本集合 ⊆ 真实工具名(堵 team_run/fork_call 类幻影)。\n */\nexport const ORCHESTRATION_TOOL_NAMES = new Set([\n 'task_delegate',\n])\n","import type { SandboxStrategy, SandboxWrapInput, SandboxWrapResult } from './types'\n\n/**\n * 无操作沙箱策略。\n *\n * 完全透传参数,行为与不启用沙箱时一致 —— 零开销,保持向后兼容。\n * 这是 createBash() 的默认策略;spawn() 未注入 sandbox 时走同一条路径(options.sandbox === undefined)。\n */\nexport class NoopSandbox implements SandboxStrategy {\n readonly name = 'noop'\n\n wrap(input: SandboxWrapInput): SandboxWrapResult {\n return {\n command: input.command,\n args: input.args,\n options: input.options,\n }\n }\n\n async dispose(): Promise<void> {}\n}\n\n/**\n * 单例 noop 策略 —— 多次注入共享同一实例,零分配。\n */\nconst noopInstance = new NoopSandbox()\n\nexport function createNoopSandbox(): SandboxStrategy {\n return noopInstance\n}\n","import { existsSync, realpathSync, writeFileSync } from 'node:fs'\nimport { unlink } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { resolve } from 'node:path'\nimport { randomUUID } from 'node:crypto'\n\nimport type { SandboxStrategy, SandboxWrapInput, SandboxWrapResult } from './types'\n\n/**\n * macOS Seatbelt 沙箱选项。\n */\nexport interface MacOsSeatbeltOptions {\n /**\n * 网络访问策略。默认 'allow'。\n * 'deny' 时禁止沙箱内进程发起网络连接。\n */\n network?: 'allow' | 'deny'\n /**\n * 额外可写路径(绝对路径)。\n * 项目根目录和临时目录已默认允许;额外路径按需添加。\n */\n writablePaths?: string[]\n /**\n * 凭证读隔离(opt-in,endgame review 余 backlog #1 的真正落点):拒绝读取这些子树。\n * seatbelt 默认 `(allow file-read* /)` 整盘可读 → 凭证目录(~/.ssh 等)默认可被读出外泄;\n * 此项在 profile 末尾追加 `(deny file-read* (subpath ...))`(SBPL 末匹配胜,deny 盖过前面的\n * allow,包括可写根内的子树)。仅拦读、不拦写(写由 file-guard/protected-paths 另管)。\n * 注意:deny 后沙箱内 git-over-ssh / aws-cli / gpg 会因读不到密钥而失败——故为 opt-in。\n */\n denyReadPaths?: string[]\n}\n\n/**\n * macOS Seatbelt(sandbox-exec)沙箱策略。\n *\n * 用 `/usr/bin/sandbox-exec -f <profile>` 包装\n * 命令执行。动态生成 .sb 文件,执行后清理。\n *\n * 约束:\n * - 文件系统:只读访问系统路径,可写仅限 projectRoot + /tmp + 指定额外路径\n * - 网络:按 options.network 控制\n * - 进程:允许 fork(bash 命令常需 fork 子进程)\n *\n * 注意:仅在 macOS 平台可用(sandbox-exec 是 macOS 特有工具)。\n */\nexport class MacOsSeatbeltSandbox implements SandboxStrategy {\n readonly name = 'macos-seatbelt'\n\n private readonly options: Required<MacOsSeatbeltOptions>\n private readonly profilePathByContent = new Map<string, string>()\n\n constructor(options: MacOsSeatbeltOptions = {}) {\n this.options = {\n network: options.network ?? 'allow',\n writablePaths: options.writablePaths ?? [],\n denyReadPaths: options.denyReadPaths ?? [],\n }\n }\n\n wrap(input: SandboxWrapInput): SandboxWrapResult {\n const profile = this.buildProfile(input.projectRoot)\n const profilePath = this.ensureProfileSync(profile)\n\n const sandboxArgs = ['-f', profilePath, input.command, ...input.args]\n\n return {\n command: '/usr/bin/sandbox-exec',\n args: sandboxArgs,\n options: {\n ...input.options,\n },\n }\n }\n\n async dispose(): Promise<void> {\n const paths = [...this.profilePathByContent.values()]\n this.profilePathByContent.clear()\n await Promise.allSettled(paths.map((p) => unlink(p).catch(() => undefined)))\n }\n\n /** 生成 Seatbelt profile 文本 */\n private buildProfile(projectRoot: string): string {\n const absRoot = resolve(projectRoot)\n const tmp = tmpdir()\n\n const writableDirs = new Set<string>()\n for (const dir of [absRoot, tmp, ...this.options.writablePaths.map((p) => resolve(p))]) {\n writableDirs.add(dir)\n try {\n writableDirs.add(realpathSync(dir))\n } catch {}\n }\n const escapeSb = (s: string): string => s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')\n const writableClauses = [...writableDirs]\n .map((d) => `(subpath \"${escapeSb(d)}\")`)\n .join('\\n ')\n\n const networkClause =\n this.options.network === 'deny' ? '(deny network*)\\n ' : '(allow network*)\\n '\n\n const denyReadDirs = new Set<string>()\n for (const p of this.options.denyReadPaths) {\n const abs = resolve(p)\n denyReadDirs.add(abs)\n try {\n denyReadDirs.add(realpathSync(abs))\n } catch {}\n }\n const denyReadClauses =\n denyReadDirs.size > 0\n ? `\\n; credential read isolation (opt-in) — last match wins, denies even within writable roots\\n` +\n [...denyReadDirs].map((d) => `(deny file-read* (subpath \"${escapeSb(d)}\"))`).join('\\n') +\n '\\n'\n : ''\n\n return `(version 1)\n(allow default)\n(deny file-write*)\n(allow file-read* file-write* (literal \"/dev/null\"))\n(allow file-read* file-write* (literal \"/dev/zero\"))\n(allow file-read* file-write* (literal \"/dev/tty\"))\n${networkClause}(allow process-fork)\n(allow process-exec)\n(allow signal)\n(allow sysctl-read)\n(allow file-read*\n (subpath \"/\")\n)\n(allow file-read* file-write*\n ${writableClauses}\n)\n${denyReadClauses}`\n }\n\n /**\n * 同步确保 profile 临时文件存在(spawn 调用点同步执行,需要文件立即可读)。\n * 同内容复用缓存路径;文件被外部清理(如系统 tmp 清扫)时重写。\n */\n private ensureProfileSync(profile: string): string {\n const cached = this.profilePathByContent.get(profile)\n if (cached && existsSync(cached)) {\n return cached\n }\n const path = `${tmpdir()}/otto-sandbox-${randomUUID()}.sb`\n writeFileSync(path, profile, 'utf-8')\n this.profilePathByContent.set(profile, path)\n return path\n }\n}\n\nexport function createMacOsSeatbeltSandbox(options?: MacOsSeatbeltOptions): MacOsSeatbeltSandbox {\n return new MacOsSeatbeltSandbox(options)\n}\n","import { resolve } from 'node:path'\nimport { existsSync } from 'node:fs'\n\nimport type { SandboxStrategy, SandboxWrapInput, SandboxWrapResult } from './types'\n\n/**\n * Linux Bubblewrap 沙箱选项。\n */\nexport interface BubblewrapOptions {\n /**\n * 网络访问策略。默认 'allow'。\n * 'deny' 时不创建网络命名空间(去 --share-net),进程无网络访问。\n */\n network?: 'allow' | 'deny'\n /**\n * 额外可写路径(绝对路径)。\n * 项目根目录通过 --bind 已可写;系统路径(/usr /lib /bin /etc)通过 --ro-bind 只读挂载。\n * 临时目录(/tmp)由 bwrap 自动创建 tmpfs 隔离。\n */\n writablePaths?: string[]\n /**\n * 凭证读隔离(opt-in):拒绝读取这些子树。\n * bwrap 是 allowlist-bind 模型——未绑定的路径在沙箱内**根本不可见**,故 `~/.ssh` 在项目目录\n * 外时本就读不到(ENOENT),无需处理。此项只覆盖边角:凭证目录**落在已绑定区内**(如项目根\n * 恰为 $HOME → `--bind $HOME` 把 ~/.ssh 也暴露了)时,用 `--tmpfs <dir>` 以空 tmpfs 遮蔽。\n */\n denyReadPaths?: string[]\n}\n\n/**\n * Linux Bubblewrap (bwrap) 沙箱策略。\n *\n * 使用 bwrap 子进程包装(用户态,无需内核模块或 root 权限)。\n * bwrap 比 Firejail 更轻量,是 Flatpak/snap 使用的底层沙箱技术。\n *\n * 约束:\n * - 文件系统:系统路径只读绑定(--ro-bind),项目目录可写(--bind),/tmp 隔离(--tmpfs)\n * - 网络:按 options.network 控制(--share-net 显式保留 / --unshare-net 隔离)\n * - 进程/命名空间:--unshare-all 全命名空间隔离 + --cap-drop ALL 去特权\n *\n * 威胁模型(M16a-R1,与 seatbelt 对称):写保护 + 网络开关,**不承诺** env/文件读隔离\n * (seatbelt 同样允许 file-read* /)。env 透传宿主:--clearenv 会丢 PATH/HOME,\n * node/git 等真实命令直接不可用,且读隔离在 file-read* 全放行下本就不成立。\n *\n * 前置要求:系统中需安装 bwrap(`which bwrap`,常见于 flatpak 或 bubblewrap 包)。\n * 未安装时 spawn 会收到 ENOENT 错误(子进程 spawn 失败,由调用方的错误传递链处理)。\n */\nexport class BubblewrapSandbox implements SandboxStrategy {\n readonly name = 'bubblewrap'\n\n private readonly options: Required<BubblewrapOptions>\n\n constructor(options: BubblewrapOptions = {}) {\n this.options = {\n network: options.network ?? 'allow',\n writablePaths: options.writablePaths ?? [],\n denyReadPaths: options.denyReadPaths ?? [],\n }\n }\n\n wrap(input: SandboxWrapInput): SandboxWrapResult {\n const absRoot = resolve(input.projectRoot)\n const cwd = resolve(input.options.cwd ?? absRoot)\n\n // --die-with-parent(bwrap 0.4+,终局架构 review 建议优化项):sandbox-exec 的\n // 会话生命周期天然绑定父进程(macOS 无对应逃逸风险),bwrap 子进程若父进程异常终止\n // (kill -9/崩溃)而未显式 --die-with-parent,沙箱子进程可能成为孤儿继续运行——\n // 不构成沙箱逃逸(子进程仍受同一组隔离约束),但会造成资源泄漏(孤儿沙箱进程常驻)。\n const bwrapArgs: string[] = ['--unshare-all', '--cap-drop', 'ALL', '--die-with-parent']\n\n // 存在性缺陷修复(2026-07-17,Docker/Linux arm64 真实环境验证发现):SYSTEM_RO_PATHS\n // 硬编码 /lib64 是 x86_64 惯例路径,arm64 Linux(含日益常见的 Graviton/树莓派/Apple\n // Silicon 跑 Linux 容器场景)通常不存在该路径——`--ro-bind <src> <dst>` 对不存在的\n // 源路径会直接报错退出(\"Can't find source path\"),导致沙箱在 arm64 上完全无法\n // 启动(非降级,是硬失败)。改为存在性检查后再加入绑定列表,不存在的路径静默跳过\n // (该路径本就不存在,跳过 --ro-bind 不产生任何额外攻击面)。\n for (const sysPath of SYSTEM_RO_PATHS) {\n if (existsSync(sysPath)) {\n bwrapArgs.push('--ro-bind', sysPath, sysPath)\n }\n }\n\n // 挂载顺序缺陷修复(2026-07-17,Docker/Linux 真实环境验证发现):bwrap 的挂载是\n // **顺序生效**的——若 `--bind <projectRoot> <projectRoot>` 在 `--tmpfs /tmp` 之前,\n // 而 projectRoot(或任一 writablePaths)恰好落在 /tmp 子树下(真实场景:CI 临时\n // 工作区、容器化部署、用户手动 `cd /tmp/repo` 场景均完全合理),后续的\n // `--tmpfs /tmp` 会把刚绑定的子路径重新遮蔽为空 tmpfs——不是权限降级,是\n // `--chdir` 直接 ENOENT 崩溃,整个沙箱功能失效(bwrap: Can't chdir to ...:\n // No such file or directory,本机 Docker 容器复现实测)。\n // 修复:`--tmpfs /tmp` 移到 projectRoot/writablePaths 的 --bind 之前,让可写路径的\n // 绑定作为\"最后生效的挂载\"覆盖回 tmpfs 的空状态(bwrap 顺序生效语义下天然成立,\n // 无需特殊 case 判断路径是否在 /tmp 下)。\n bwrapArgs.push('--tmpfs', '/tmp')\n\n bwrapArgs.push('--bind', absRoot, absRoot)\n\n const boundRoots = [absRoot]\n for (const p of this.options.writablePaths) {\n const abs = resolve(p)\n boundRoots.push(abs)\n bwrapArgs.push('--bind', abs, abs)\n }\n\n for (const p of this.options.denyReadPaths) {\n const abs = resolve(p)\n if (boundRoots.some((root) => abs === root || abs.startsWith(`${root}/`))) {\n bwrapArgs.push('--tmpfs', abs)\n }\n }\n\n bwrapArgs.push('--proc', '/proc')\n\n if (this.options.network === 'deny') {\n bwrapArgs.push('--unshare-net')\n } else {\n bwrapArgs.push('--share-net')\n }\n\n bwrapArgs.push('--chdir', cwd)\n\n bwrapArgs.push(input.command, ...input.args)\n\n return {\n command: 'bwrap',\n args: bwrapArgs,\n options: {\n ...input.options,\n },\n }\n }\n\n async dispose(): Promise<void> {}\n}\n\n/**\n * 系统只读挂载路径(bwrap --ro-bind)。\n * 覆盖基础 OS 路径以支持常见 bash 工具(node / git / curl 等)。\n */\nconst SYSTEM_RO_PATHS = ['/usr', '/bin', '/lib', '/lib64', '/etc', '/dev', '/sys']\n\nexport function createBubblewrapSandbox(options?: BubblewrapOptions): BubblewrapSandbox {\n return new BubblewrapSandbox(options)\n}\n","import { existsSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, join } from 'node:path'\n\nimport { createNoopSandbox } from './noop'\nimport { createMacOsSeatbeltSandbox } from './macos-seatbelt'\nimport { createBubblewrapSandbox } from './bubblewrap'\n\nimport type { SandboxStrategy } from './types'\nimport type { MacOsSeatbeltOptions } from './macos-seatbelt'\nimport type { BubblewrapOptions } from './bubblewrap'\n\n/**\n * 平台沙箱选项(联合 macOS Seatbelt 与 Linux Bubblewrap 的可配字段)。\n */\nexport interface PlatformSandboxOptions {\n /** 网络访问策略。默认 'allow'。 */\n network?: 'allow' | 'deny'\n /**\n * 额外可写路径(绝对路径)。\n * 项目根目录和 /tmp 已默认允许,无需在此列出。\n */\n writablePaths?: string[]\n /**\n * 凭证读隔离(opt-in):拒绝读取这些子树(绝对路径)。\n * 标准凭证集见 {@link credentialDenyReadPaths}。seatbelt 末尾追加 deny;bwrap 仅遮蔽落在绑定区内者。\n */\n denyReadPaths?: string[]\n /**\n * 强制指定平台策略名,跳过 process.platform 自动检测。\n * 用于测试或强制使用特定后端。\n */\n force?: 'seatbelt' | 'bubblewrap' | 'noop'\n}\n\n/**\n * 标准凭证读隔离路径集(opt-in `protectCredentials` 展开为此):\n * `~/.ssh`、`~/.aws`、`~/.gnupg` + otto auth store 目录(默认 `~/.config/otto`,\n * 经 OTTO_AUTH_PATH 覆盖时取其所在目录)。\n */\nexport function credentialDenyReadPaths(): string[] {\n const home = homedir()\n const authPath = process.env['OTTO_AUTH_PATH']\n return [\n join(home, '.ssh'),\n join(home, '.aws'),\n join(home, '.gnupg'),\n authPath ? dirname(authPath) : join(home, '.config', 'otto'),\n ]\n}\n\n/**\n * 按平台自动选择沙箱策略:\n * - darwin → macOS Seatbelt (sandbox-exec)\n * - linux → Bubblewrap (bwrap)\n * - 其他 → Noop(Windows 等回退)\n *\n * 每个平台使用最优 OS 级沙箱技术,无需调用方判断平台。\n */\nexport function createPlatformSandbox(options: PlatformSandboxOptions = {}): SandboxStrategy {\n const strategy = options.force ?? platformDefault()\n\n switch (strategy) {\n case 'seatbelt': {\n const seatbeltOpts: MacOsSeatbeltOptions = {\n network: options.network,\n writablePaths: options.writablePaths,\n denyReadPaths: options.denyReadPaths,\n }\n return createMacOsSeatbeltSandbox(seatbeltOpts)\n }\n case 'bubblewrap': {\n const bwrapOpts: BubblewrapOptions = {\n network: options.network,\n writablePaths: options.writablePaths,\n denyReadPaths: options.denyReadPaths,\n }\n return createBubblewrapSandbox(bwrapOpts)\n }\n case 'noop':\n default:\n return createNoopSandbox()\n }\n}\n\nfunction platformDefault(): 'seatbelt' | 'bubblewrap' | 'noop' {\n if (process.platform === 'darwin') {\n return 'seatbelt'\n }\n if (process.platform === 'linux') {\n return 'bubblewrap'\n }\n return 'noop'\n}\n\n/**\n * M4 Phase 2 preflight:当前平台的 OS 沙箱是否**实际可用**。\n * - darwin:`sandbox-exec` 是 macOS 内置 → true。\n * - linux:需安装 `bwrap`(bubblewrap)→ 探测 PATH/常见路径;缺失 → false。\n * - 其它平台:无沙箱 → false。\n *\n * 默认开(Phase 2)必须先过此关:缺 bwrap 的 Linux 主机若强行套 bwrap,spawn ENOENT\n * 会让**每条命令失败**(fail-closed 但用户体验=全挂)。preflight 让默认开在不可用时\n * 优雅降级为「无沙箱 + 一次告警」,而非破坏。\n */\nexport function isPlatformSandboxAvailable(): boolean {\n const platform = platformDefault()\n if (platform === 'seatbelt') {\n return existsSync('/usr/bin/sandbox-exec')\n }\n if (platform === 'bubblewrap') {\n const candidates = ['/usr/bin/bwrap', '/usr/local/bin/bwrap', '/bin/bwrap']\n if (candidates.some((p) => existsSync(p))) {\n return true\n }\n const pathDirs = (process.env.PATH ?? '').split(':').filter(Boolean)\n return pathDirs.some((d) => existsSync(`${d}/bwrap`))\n }\n return false\n}\n"],"mappings":"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"}
|