JavaScript/TypeScript 模式实战:用 ctx_execute 在沙箱中处理 API、JSON 与测试输出
【免费下载链接】context-modeContext window optimization for AI coding agents. Sandboxes tool output (98% reduction), persists session memory, and enforces routing across 17 platforms via MCP + hooks.项目地址: https://gitcode.com/GitHub_Trending/cl/context-mode
在 AI 编码 Agent 的会话中,上下文窗口是最宝贵的资源。context-mode 项目通过ctx_execute沙箱化工具输出(项目描述中标注可将工具输出缩减约 98%),让 Agent 用代码而非上下文来处理数据。本文以 patterns-javascript.md 为骨架,完整继承其全部 8 个 JavaScript/TypeScript 实战模式,并结合 src/server.ts 的实现与 SKILL.md 的决策规则做源码级扩充。读完你将掌握:如何用原生 fetch 分析 API 响应、解析大型 JSON 配置、审计依赖、解析测试输出,以及如何让每一份输出都以"分析结果"而非"原始字节"的形式进入上下文。
前置条件与运行环境
本文所有示例均面向ctx_execute的language: "javascript"运行时:
- Node.js 运行时:所有示例依赖原生
fetch,需要Node 18+; - CommonJS 模块环境:
fs、child_process通过require引入,JSON 可直接用require('./x.json')读取; - 沙箱执行:代码运行在独立子进程中,
console.log的输出是唯一进入上下文的通道(详见下文"Think-in-Code")。
context-mode 的ctx_execute工具在 src/server.ts 中注册,支持 12 种运行时:javascript、typescript、python、shell、ruby、go、rust、php、perl、r、elixir、csharp。各语言向 stdout 输出的方式不同——JS/TS 用console.log,Python 用print,Shell 用echo——但原则一致:只打印结论,不倾倒原始数据。
核心心智模型:Think-in-Code
ctx_execute的工具描述中明确阐述了本项目的第一性原则(见 src/server.ts):
Think-in-Code — the core philosophy: the bytes your code processes never enter your conversation memory; only what you console.log() does. Reading a 700 KB log directly means 700 KB of your remaining reasoning capacity gets spent on raw bytes. Running code over that same log in this sandbox and printing a 3 KB summary leaves you with 697 KB of capacity for the actual work.
(代码处理的字节永远不会进入对话记忆,只有console.log的输出会。直接读 700 KB 日志意味着你 700 KB 的推理容量被原始字节消耗;在沙箱中跑代码处理同一份日志、打印 3 KB 摘要,你就能省下 697 KB 容量用于真正的工作。)
这解释了本文件所有模式的共同形态:读取 → 分析 → 只打印发现。例如分析 47 个源文件时:
ctx_execute(language: "javascript", code: ` const fs = require('fs'); const files = fs.readdirSync('src').filter(f => f.endsWith('.ts')); files.forEach(f => { const lines = fs.readFileSync('src/'+f,'utf8').split('\\n').length; console.log(f + ': ' + lines + ' lines'); }); `) // 47 files analyzed, 15,314 LoC summarized — output ~3.6 KB instead of 47 Read() calls = ~700 KB.实现层面,src/server.ts 会对 JS/TS 代码做闭包包装,拦截http/https请求以追踪沙箱内的网络字节消耗(bytesSandboxed,见 src/server.ts),这些字节永不进入上下文。
ctx_execute 参数速查
理解以下参数(定义见 src/server.ts),才能把模式用到实处:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
language | 枚举(12 种) | 必填 | 运行时语言,JS/TS 示例见本文 |
code | string | 必填 | 要执行的源码;只打印摘要到上下文 |
timeout | number (ms) | 由宿主 RPC 超时决定 | 省略时不启动服务端计时器,由 MCP 宿主 RPC 超时兜底;长任务(Gradle/Maven/SBT 构建)需显式传入 |
background | boolean | false | 超时后不杀进程(用于 dev server / daemon),返回部分输出 |
cwd | string | 沙箱临时目录 | Shell 命令的工作目录;非 Shell 语言仍从沙箱临时目录执行 |
intent | string | 无 | 你想从输出中找什么;输出超过约 5KB 时自动索引进知识库,只返回分节标题与预览,随后用ctx_search(queries: [...])检索具体段落 |
summary_prompt 约定
patterns 文件中每个示例末尾都附有> summary_prompt注释(部分还有> timeout_ms)。这是本文件的元数据约定,含义是:这段代码执行后,希望 LLM 如何总结输出。写得好坏直接决定上下文质量——anti-patterns.md 给出了具体建议:
- 具体指出需要哪些数据点(数量、指标,而非笼统描述);
- 请求可行动的洞察("suggest fixes"、"identify patterns");
- 指明输出格式("list as bullet points"、"group by category")。
例如本文件中的> summary_prompt: "Report overall health, list any degraded services, and highlight errors"就比 "Summarize this" 有效得多。
模式一:API 响应处理
1.1 Fetch 并汇总 REST API
// execute: Analyze API health endpoint const resp = await fetch('https://api.example.com/health'); const data = await resp.json(); console.log('=== Service Health ==='); console.log(`Status: ${data.status}`); console.log(`Uptime: ${data.uptime}`); console.log(`Timestamp: ${data.timestamp}`); if (data.services) { console.log('\n=== Service Components ==='); for (const [name, info] of Object.entries(data.services)) { console.log(` ${name}: ${info.status} (latency: ${info.latency_ms}ms)`); } } if (data.errors && data.errors.length > 0) { console.log('\n=== Recent Errors ==='); data.errors.slice(0, 10).forEach(e => { console.log(` [${e.timestamp}] ${e.code}: ${e.message}`); }); }summary_prompt: "Report overall health, list any degraded services, and highlight errors"
要点拆解:
- 直接调用
fetch:这是 JS 模式优于 Shell 的核心场景。用 Bashcurl会把整份响应(可能 50 KB+)灌进上下文(见 SKILL.md 的 Anti-Patterns 一节); - 先分析再打印:不是
console.log(JSON.stringify(data)),而是按维度(健康状态、组件延迟、近期错误)组织输出; - 主动截断:
slice(0, 10)限制错误数量,避免海量错误列表反噬上下文。
1.2 分页 API 收集
// execute: Fetch all open issues from GitHub API const owner = 'org'; const repo = 'project'; let page = 1; let allIssues = []; while (true) { const resp = await fetch( `https://api.github.com/repos/${owner}/${repo}/issues?state=open&per_page=100&page=${page}`, { headers: { 'Accept': 'application/vnd.github.v3+json' } } ); const issues = await resp.json(); if (issues.length === 0) break; allIssues.push(...issues); page++; } console.log(`Total open issues: ${allIssues.length}\n`); // Group by labels const byLabel = {}; allIssues.forEach(issue => { issue.labels.forEach(label => { byLabel[label.name] = (byLabel[label.name] || 0) + 1; }); }); console.log('=== Issues by Label ==='); Object.entries(byLabel) .sort((a, b) => b[1] - a[1]) .forEach(([label, count]) => console.log(` ${label}: ${count}`)); // Oldest issues console.log('\n=== 10 Oldest Issues ==='); allIssues .sort((a, b) => new Date(a.created_at) - new Date(b.created_at)) .slice(0, 10) .forEach(i => console.log(` #${i.number} (${i.created_at.slice(0,10)}): ${i.title}`));summary_prompt: "Summarize issue distribution by label, highlight stale issues, suggest priorities" timeout_ms: 30000
这个模式展示了沙箱的完整价值:分页可能拉取数百条 issue(数十 KB 原始数据),但经过标签聚合、时间排序后,进入上下文的只有三个精炼视图(总量、标签分布、最旧十条)。注意两点:
> timeout_ms: 30000:分页网络请求需要更宽裕的超时。参见 anti-patterns.md 的推荐值——单次 API 请求 15000–30000ms,分页调用 30000–60000ms;- 数据留在沙箱:
allIssues数组在沙箱内完成计算,不会逐条进入对话。
模式二:JSON 数据分析
2.1 分析大型 JSON 配置文件
const fs = require('fs'); const data = JSON.parse(fs.readFileSync('tsconfig.json', 'utf8')); console.log('=== TSConfig Analysis ==='); console.log(`Target: ${data.compilerOptions?.target}`); console.log(`Module: ${data.compilerOptions?.module}`); console.log(`Strict: ${data.compilerOptions?.strict}`); console.log(`Paths aliases: ${Object.keys(data.compilerOptions?.paths || {}).length}`); if (data.compilerOptions?.paths) { console.log('\n=== Path Aliases ==='); for (const [alias, targets] of Object.entries(data.compilerOptions.paths)) { console.log(` ${alias} -> ${targets.join(', ')}`); } } if (data.include) console.log(`\nInclude: ${data.include.join(', ')}`); if (data.exclude) console.log(`Exclude: ${data.exclude.join(', ')}`); if (data.references) { console.log(`\nProject References: ${data.references.length}`); data.references.forEach(r => console.log(` ${r.path}`)); }summary_prompt: "Report compiler strictness, module system, and any unusual configuration"
技巧:可选链?.与|| {}兜底。配置文件字段未必齐全,data.compilerOptions?.paths与Object.keys(... || {}).length让脚本对缺失字段健壮,避免未捕获异常导致 stderr 泄漏进上下文(src/server.ts 的 RETURNS 说明警告过:未捕获错误会进入 stderr,可能泄漏超出预期的内容)。
2.2 对比两个 JSON 文件
const fs = require('fs'); const a = JSON.parse(fs.readFileSync('config.prod.json', 'utf8')); const b = JSON.parse(fs.readFileSync('config.staging.json', 'utf8')); function diffObjects(obj1, obj2, path = '') { const allKeys = new Set([...Object.keys(obj1 || {}), ...Object.keys(obj2 || {})]); for (const key of allKeys) { const fullPath = path ? `${path}.${key}` : key; if (!(key in (obj1 || {}))) { console.log(`+ ${fullPath}: ${JSON.stringify(obj2[key])}`); } else if (!(key in (obj2 || {}))) { console.log(`- ${fullPath}: ${JSON.stringify(obj1[key])}`); } else if (typeof obj1[key] === 'object' && typeof obj2[key] === 'object') { diffObjects(obj1[key], obj2[key], fullPath); } else if (JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])) { console.log(`~ ${fullPath}: ${JSON.stringify(obj1[key])} -> ${JSON.stringify(obj2[key])}`); } } } console.log('=== Config Diff: prod vs staging ==='); diffObjects(a, b);summary_prompt: "List all configuration differences between prod and staging environments"
递归对比的要点:
- 三类变更符号:
+新增键、-删除键、~值变化(旧值 → 新值); - 递归深度优先:嵌套对象进入
diffObjects递归,fullPath用.拼接出可读的键路径; - 值比较用
JSON.stringify:避免对象引用比较的陷阱。
模式三:package.json / 锁文件分析
3.1 依赖审计
const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); const deps = Object.entries(pkg.dependencies || {}); const devDeps = Object.entries(pkg.devDependencies || {}); console.log(`Package: ${pkg.name}@${pkg.version}`); console.log(`Dependencies: ${deps.length}`); console.log(`DevDependencies: ${devDeps.length}`); // Find non-pinned versions console.log('\n=== Non-Pinned Dependencies ==='); [...deps, ...devDeps].forEach(([name, version]) => { if (version.startsWith('^') || version.startsWith('~') || version === '*') { console.log(` ${name}: ${version}`); } }); // Find duplicated categories console.log('\n=== Scripts ==='); Object.entries(pkg.scripts || {}).forEach(([name, cmd]) => { console.log(` ${name}: ${cmd}`); }); // Workspace detection if (pkg.workspaces) { console.log('\n=== Monorepo Workspaces ==='); const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages || []; ws.forEach(w => console.log(` ${w}`)); }summary_prompt: "Report dependency health: unpinned versions, total count, any security concerns from package names"
这里的"非锁定版本"检测(^、~、*开头)是典型的在沙箱内做判断、只打印发现:原始package.json可能只有几 KB,但叠加package-lock.json后体积陡增(anti-patterns.md 提到 20,000 行的 lock 文件案例),逐行 Read 会浪费整个上下文。
3.2 锁文件漂移检测
const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); let lockExists = { npm: false, yarn: false, pnpm: false }; try { fs.accessSync('package-lock.json'); lockExists.npm = true; } catch {} try { fs.accessSync('yarn.lock'); lockExists.yarn = true; } catch {} try { fs.accessSync('pnpm-lock.yaml'); lockExists.pnpm = true; } catch {} console.log('=== Lock File Status ==='); Object.entries(lockExists).forEach(([mgr, exists]) => { console.log(` ${mgr}: ${exists ? 'PRESENT' : 'missing'}`); }); const activeLocks = Object.entries(lockExists).filter(([, v]) => v); if (activeLocks.length > 1) { console.log('\nWARNING: Multiple lock files detected! This causes inconsistent installs.'); } if (activeLocks.length === 0) { console.log('\nWARNING: No lock file found! Dependencies are not reproducible.'); } // Check engines if (pkg.engines) { console.log('\n=== Required Engines ==='); Object.entries(pkg.engines).forEach(([e, v]) => console.log(` ${e}: ${v}`)); }summary_prompt: "Report lock file health and any warnings about package management"
要点:
fs.accessSync+ try/catch 探测文件存在性:避免用 Read 工具逐个读锁文件(yarn.lock 动辄上万行);- 状态机式告警:多个锁文件并存(安装不一致)或无锁文件(不可复现构建)是两种明确的风险状态,脚本直接输出 WARNING,让 LLM 的总结聚焦在风险而非文件内容上。
模式四:文件内容解析——解析并汇总大型 Markdown
const fs = require('fs'); const content = fs.readFileSync('CHANGELOG.md', 'utf8'); const lines = content.split('\n'); const sections = []; let currentSection = null; for (const line of lines) { if (line.startsWith('## ')) { if (currentSection) sections.push(currentSection); currentSection = { title: line.replace('## ', ''), items: 0, breaking: 0 }; } else if (currentSection && line.startsWith('- ')) { currentSection.items++; if (line.toLowerCase().includes('breaking') || line.toLowerCase().includes('BREAKING')) { currentSection.breaking++; } } } if (currentSection) sections.push(currentSection); console.log(`Total versions: ${sections.length}\n`); console.log('=== Recent Versions ==='); sections.slice(0, 10).forEach(s => { const warn = s.breaking > 0 ? ` [${s.breaking} BREAKING]` : ''; console.log(` ${s.title}: ${s.items} changes${warn}`); }); const totalBreaking = sections.reduce((sum, s) => sum + s.breaking, 0); if (totalBreaking > 0) { console.log(`\nTotal breaking changes across all versions: ${totalBreaking}`); }summary_prompt: "Summarize recent releases, highlight breaking changes, report release cadence"
解析策略:按##划分版本章节,统计每个版本的条目数与 breaking 变更数,最终输出总量、最近 10 个版本、breaking 总数。这与 patterns-python.md 中的文档结构提取思路一致——都是"结构扫描 + 摘要输出"。若目标是长期查询而非一次性总结,可改用ctx_execute_file(path, ...):文件内容以FILE_CONTENT预加载进沙箱(SKILL.md 的决策树:读取文件用于分析/汇总时用ctx_execute_file,文件只进入沙箱、不进入上下文)。
模式五:测试输出解析——运行测试并提取失败信息
const { execSync } = require('child_process'); let output; try { output = execSync('npx jest --json 2>/dev/null', { encoding: 'utf8', maxBuffer: 50 * 1024 * 1024 }); } catch (e) { output = e.stdout || ''; } try { const results = JSON.parse(output); console.log(`=== Test Results ===`); console.log(`Suites: ${results.numPassedTestSuites} passed, ${results.numFailedTestSuites} failed`); console.log(`Tests: ${results.numPassedTests} passed, ${results.numFailedTests} failed`); console.log(`Time: ${(results.testResults || []).reduce((s, t) => s + (t.endTime - t.startTime), 0)}ms`); const failures = (results.testResults || []).filter(t => t.status === 'failed'); if (failures.length > 0) { console.log('\n=== Failed Tests ==='); failures.forEach(suite => { console.log(`\nSuite: ${suite.name}`); (suite.assertionResults || []) .filter(a => a.status === 'failed') .forEach(a => { console.log(` FAIL: ${a.ancestorTitles.join(' > ')} > ${a.title}`); console.log(` ${(a.failureMessages || []).join('\n ').slice(0, 200)}`); }); }); } } catch { console.log('Could not parse JSON output. Raw output:'); console.log(output.slice(0, 5000)); }summary_prompt: "Report test pass/fail counts, list each failing test with its error message" timeout_ms: 60000
这个模式是"沙箱内运行测试"的标准姿势,要点:
npx jest --json输出结构化结果:相比直接npm test把完整测试输出灌进上下文(SKILL.md 明确列为反模式),JSON 模式让脚本精确提取数量与失败详情;execSync抛错时读取e.stdout:测试失败时 jest 可能非零退出,此时 stdout 依然携带 JSON 结果;maxBuffer: 50 * 1024 * 1024:为超大测试输出预留缓冲;- 错误消息截断
slice(0, 200):每条失败只保留前 200 字符,防止巨型堆栈反噬上下文; - 兜底分支:JSON 解析失败时只打印前 5000 字符原始输出;
> timeout_ms: 60000:完整测试套件的推荐超时为 120000–300000ms(见 anti-patterns.md),60 秒是单套件场景的保守下限。
语言选择:什么时候用 JavaScript
SKILL.md 的语言选择表给出了明确依据:
| 场景 | 语言 | 理由 |
|---|---|---|
| HTTP/API 调用、JSON | javascript | 原生 fetch、JSON.parse、async/await |
| 数据分析、CSV、统计 | python | csv、statistics、collections、re |
| 管道式 Shell 命令 | shell | grep、awk、jq 原生工具 |
| 文件模式匹配 | shell | find、wc、sort、uniq |
与 Python 模式的边界(见 patterns-python.md):JSON/API 场景优先 JS,数据统计优先 Python。若你的 Shell 脚本里出现了内联python3 -c、node -e、超过 3 个管道链或复杂jq,就应改用 JS/Python(anti-patterns.md)。
结合检索策略:intent + ctx_search
当ctx_execute的输出可能超过约 5KB 且你只想"按主题事后回忆"时,传入intent参数(见 src/server.ts):输出被自动索引进知识库,工具只返回分节标题与预览,随后用ctx_search(queries: [...])检索具体段落。ctx_search的入参契约定义在 src/search/ctx-search-schema.ts:
queries数组:一次性批量传入所有检索问题,绝不多次单独调用(BM25 使用 OR 语义,命中词越多的结果排名越高,单次查询 2–4 个具体技术词最佳);source参数:多文档索引时务必使用(部分匹配即可,如source: "Node"匹配"Node.js v22 CHANGELOG"),避免跨源污染;limit:每查询默认 3 条结果;sort:relevance(默认,BM25 排序,仅当前会话)或timeline(跨当前会话、历史会话与 auto-memory 的时序检索)。
反模式清单:五个必守纪律
结合 anti-patterns.md 与 SKILL.md 的 Critical Rules,使用 JS 模式时须遵守:
- 必须打印结论:
execute捕获 stdout,脚本不输出则总结为空。计算完务必console.log结果; - 结构化序列化:对象/数组用
JSON.stringify(data, null, 2)或表格化输出,直接console.log(obj)会得到[object Object]; - 别在上下文里处理大文件:超过约 200 行的文件且只需特定数据时,用
ctx_execute/ctx_execute_file提取,而非 Read 全文(anti-patterns.md); - 别用
ctx_index(content: 大段数据):该参数会把数据作为工具参数再次送进上下文(翻倍消耗)。始终用ctx_index(path: ...)服务端读文件(SKILL.md 的 Critical Rules #6); - 超时与任务匹配:文件解析 5–10s、单次 API 15–30s、分页 30–60s、构建/完整测试 120s+。
关联文档
- JavaScript/TypeScript Patterns(本文底稿)
- Python Patterns
- Shell Patterns
- Anti-Patterns & Common Mistakes
- context-mode 主技能文档
【免费下载链接】context-modeContext window optimization for AI coding agents. Sandboxes tool output (98% reduction), persists session memory, and enforces routing across 17 platforms via MCP + hooks.项目地址: https://gitcode.com/GitHub_Trending/cl/context-mode
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考