@huicai/cli 2026.1021.0 → 2026.1022.59980
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/bin/huicai.js +45 -10
- package/package.json +2 -2
- package/src/account-scope.js +1 -0
- package/src/agent.js +1 -1
- package/src/assets-import.js +1 -1
- package/src/automation-sync.js +1 -0
- package/src/codeindex.js +1 -1
- package/src/commands.js +1 -1
- package/src/config.js +1 -1
- package/src/context.js +1 -1
- package/src/daemon.js +1 -1
- package/src/decisions.js +1 -1
- package/src/doccache.js +1 -1
- package/src/experts-store.js +1 -1
- package/src/experts.js +1 -1
- package/src/input.js +1 -1
- package/src/lsp.js +1 -1
- package/src/mcp.js +1 -1
- package/src/memory.js +1 -1
- package/src/plan-limits.js +1 -1
- package/src/privatedir.js +1 -1
- package/src/repl.js +1 -1
- package/src/schedule.js +1 -1
- package/src/session.js +1 -1
- package/src/sessionindex.js +1 -1
- package/src/skills.js +1 -1
- package/src/squad.js +1 -1
- package/src/subagent.js +1 -1
- package/src/toolpipe.js +1 -1
- package/src/uninstall.js +1 -1
- package/src/workflow.js +1 -1
- package/src/worklog.js +1 -1
package/bin/huicai.js
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
// huicai —— 终端智能体入口:解析参数并分发(交互 / 一次性 / login / models / help)。
|
|
3
3
|
|
|
4
4
|
import readline from 'node:readline';
|
|
5
|
+
import { accountSnapshot, assertAccountPath } from '../src/account-scope.js';
|
|
5
6
|
import path from 'node:path';
|
|
6
7
|
import { createRequire } from 'node:module';
|
|
7
8
|
import { c, setColorMode, setAccent } from '../src/colors.js';
|
|
8
|
-
import { loadConfig, saveConfig, CONFIG_PATH, DEFAULT_MODEL } from '../src/config.js';
|
|
9
|
-
import { listModels, getAgentPersona, fetchUsage, listPlatformSkills, getPlatformSkill, pushPlatformSkill } from '../src/api.js';
|
|
9
|
+
import { loadConfig, saveConfig, setRuntimeCredentials, CONFIG_PATH, DEFAULT_MODEL } from '../src/config.js';
|
|
10
|
+
import { getPlatformConfig, listModels, getAgentPersona, fetchUsage, listPlatformSkills, getPlatformSkill, pushPlatformSkill } from '../src/api.js';
|
|
10
11
|
import { deviceLogin } from '../src/device.js';
|
|
11
12
|
import { startRepl, runOnce } from '../src/repl.js';
|
|
12
13
|
import { buildSystemPrompt } from '../src/context.js';
|
|
@@ -211,7 +212,10 @@ async function login(flags = {}) {
|
|
|
211
212
|
// 不适合 CLI 的本机工具循环——用 `huicai --agent <ref>` 才是采用智能体的正确方式)。
|
|
212
213
|
let model = cur.model;
|
|
213
214
|
let tokenOk = true;
|
|
215
|
+
let user = null;
|
|
214
216
|
try {
|
|
217
|
+
const platform = await getPlatformConfig(normBase, key);
|
|
218
|
+
user = platform?.user || null;
|
|
215
219
|
const models = await listModels(normBase, key);
|
|
216
220
|
const raw = models.map((m) => m.id).filter((id) => !/^(agent|workflow|datahuman|squad)-/.test(id));
|
|
217
221
|
if (models.length === 0) {
|
|
@@ -227,7 +231,7 @@ async function login(flags = {}) {
|
|
|
227
231
|
process.stdout.write(c.red('⚠ 令牌校验失败:' + e.message) + '\n');
|
|
228
232
|
}
|
|
229
233
|
|
|
230
|
-
saveConfig({ baseUrl: normBase, apiKey: key, model });
|
|
234
|
+
saveConfig({ baseUrl: normBase, apiKey: key, model, user });
|
|
231
235
|
if (tokenOk) {
|
|
232
236
|
process.stdout.write(c.green('登录成功') + (model ? c.gray(` · 默认模型 ${model}`) : '') + '\n');
|
|
233
237
|
process.stdout.write(c.gray(`已保存到 ${CONFIG_PATH},直接运行huicai开始。`) + '\n');
|
|
@@ -617,17 +621,40 @@ async function main() {
|
|
|
617
621
|
return;
|
|
618
622
|
}
|
|
619
623
|
|
|
624
|
+
setRuntimeCredentials({ ...(flags.base ? { baseUrl: flags.base } : {}), ...(flags.key ? { apiKey: flags.key } : {}) });
|
|
620
625
|
const config = loadConfig();
|
|
626
|
+
if (config.apiKey && !config.user && !['help', 'version'].includes(sub)) {
|
|
627
|
+
const { syncPlanUser } = await import('../src/plan-limits.js');
|
|
628
|
+
await syncPlanUser(config);
|
|
629
|
+
Object.assign(config, loadConfig());
|
|
630
|
+
}
|
|
621
631
|
if (flags.base) config.baseUrl = flags.base.replace(/\/+$/, '');
|
|
622
632
|
if (flags.key) config.apiKey = flags.key;
|
|
623
633
|
if (flags.model) config.model = flags.model;
|
|
624
634
|
if (Number.isFinite(flags.maxTurns) && flags.maxTurns > 0) config.maxTurns = flags.maxTurns;
|
|
625
635
|
setColorMode(config.theme); // /theme:无色/彩色
|
|
626
636
|
setAccent(config.accentColor); // /color:强调色
|
|
627
|
-
|
|
637
|
+
let cwd = flags.cwd ? path.resolve(flags.cwd) : process.cwd();
|
|
638
|
+
let scheduledTask = null;
|
|
639
|
+
if (sub === 'run-scheduled') {
|
|
640
|
+
const identity = accountSnapshot();
|
|
641
|
+
if (!identity.current() || identity.scope !== positional[2]) throw new Error('任务不属于当前登录用户');
|
|
642
|
+
const { syncAutomations } = await import('../src/automation-sync.js');
|
|
643
|
+
const schedule = await import('../src/schedule.js');
|
|
644
|
+
await syncAutomations();
|
|
645
|
+
const task = schedule.getTask(positional[1]);
|
|
646
|
+
if (!task || task.enabled === false || !identity.current()) return;
|
|
647
|
+
const { isDue } = await import('../src/schedule-freq.js');
|
|
648
|
+
if (!isDue(task, new Date())) return;
|
|
649
|
+
scheduledTask = task;
|
|
650
|
+
cwd = assertAccountPath(task.cwd || process.cwd());
|
|
651
|
+
flags.prompt = task.prompt; flags.print = true; flags.yolo = task.permission === 'full';
|
|
652
|
+
if (task.model && task.model !== 'auto') config.model = task.model;
|
|
653
|
+
}
|
|
628
654
|
|
|
629
655
|
// 会话列表:纯本地,无需鉴权
|
|
630
656
|
if (sub === 'sessions') {
|
|
657
|
+
if (!config.apiKey || !config.user) { process.stdout.write('请先登录并完成用户身份校验。\n'); return; }
|
|
631
658
|
cmdSessions(cwd, flags.all || positional.includes('--all') || positional.includes('-a'));
|
|
632
659
|
return;
|
|
633
660
|
}
|
|
@@ -837,12 +864,20 @@ async function main() {
|
|
|
837
864
|
if (piped) {
|
|
838
865
|
prompt = prompt ? `${prompt}\n\n【以下为管道输入(stdin)】\n${piped}` : piped;
|
|
839
866
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
867
|
+
const onceOptions = { config, cwd, yolo: flags.yolo, prompt, session, persist: resuming,
|
|
868
|
+
outputFormat: flags.outputFormat || 'text', maxTurns: config.maxTurns || undefined };
|
|
869
|
+
if (scheduledTask) {
|
|
870
|
+
const { runClaimedAutomation } = await import('../src/automation-sync.js');
|
|
871
|
+
await runClaimedAutomation(scheduledTask, async (task, signal) => {
|
|
872
|
+
// 以领取时的权威定义运行,不能使用同步到领取之间已被控制台修改的旧指令。
|
|
873
|
+
const runCwd = assertAccountPath(task.cwd || process.cwd());
|
|
874
|
+
const runConfig = { ...config, model: task.model && task.model !== 'auto' ? task.model : config.model };
|
|
875
|
+
const runSession = sessions.createSession({ cwd: runCwd, model: runConfig.model,
|
|
876
|
+
messages: [{ role: 'system', content: buildSystemPrompt(runCwd, { config: runConfig }) }] });
|
|
877
|
+
return runOnce({ ...onceOptions, config: runConfig, cwd: runCwd, session: runSession,
|
|
878
|
+
prompt: task.prompt, yolo: task.permission === 'full', signal, throwOnError: true });
|
|
879
|
+
});
|
|
880
|
+
} else await runOnce(onceOptions);
|
|
846
881
|
return;
|
|
847
882
|
}
|
|
848
883
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huicai/cli",
|
|
3
|
-
"version": "2026.
|
|
4
|
-
"versionDisplay": "2026.10.
|
|
3
|
+
"version": "2026.1022.59980",
|
|
4
|
+
"versionDisplay": "2026.10.22.59980",
|
|
5
5
|
"description": "在终端运行的智能体CLI:以HuiCai_AI平台的OpenAI兼容 /v1 为模型后端,在用户本机执行文件/命令工具。",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import t from"node:crypto";import r from"node:fs";import o from"node:path";import{AsyncLocalStorage as n}from"node:async_hooks";import{CONFIG_DIR as e,loadConfig as c}from"./config.js";const a=new n;export function accountScope(r=c()){const o=r.user?.id??r.user?.userId??r.user?.username;return r.apiKey&&r.baseUrl&&null!=o&&""!==String(o)?t.createHash("sha256").update(`${r.baseUrl.replace(/\/+$/,"")}\n${o}`).digest("hex"):""}export function accountDataDir(){return assertAccountOperation(),o.join(e,"accounts",accountScope()||"_signed-out")}export function assertAccountOperation(){const t=a.getStore();if(t&&!t.current())throw new Error("登录状态已改变,请重试")}export function withAccountOperation(t,r){return a.run(t,r)}export function requireAccount(){assertAccountOperation();const t=accountScope();if(!t)throw new Error("请先登录并完成用户身份校验");return t}export function accountSnapshot(){const t=c(),r=accountScope(t);return{config:t,scope:r,current:()=>{const o=c();return!!r&&accountScope(o)===r&&o.apiKey===t.apiKey}}}export function assertAccountPath(t){if(assertAccountOperation(),!t)return t;const n=t=>{const e=o.resolve(t);try{return r.realpathSync(e)}catch{const t=o.dirname(e);return t===e?e:o.join(n(t),o.basename(e))}},c=n(t),a=n(e),s=n(accountDataDir());if((c===a||c.startsWith(a+o.sep))&&c!==s&&!c.startsWith(s+o.sep))throw new Error("无权访问其它账号或共享凭据目录");return t}
|
package/src/agent.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"node:fs";import t from"node:os";import n from"node:path";import{streamChat as o,getPlatformConfig as s}from"./api.js";import{classifyError as r}from"./errkind.js";import{MarkupFilter as i,stripMarkup as a}from"./markup-filter.js";import{TOOL_SPECS as l,READONLY_TOOLS as c}from"./tools.js";import{toolLabel as u,indent as d,renderFileDiff as m}from"./render.js";import{compactIfNeeded as f,buildModelMessages as g,contextGauge as p}from"./compaction.js";import{catalogTokenBudget as h,noteMemberLimitFromError as b}from"./context-window.js";import{syncSkillCatalog as y,catalogDigestOf as w}from"./skill-catalog.js";import{makeTodoTool as $,formatTodos as S,TODO_TOOL_NAME as x}from"./todo.js";import{makeDecisionTool as k,DECISION_TOOL_NAME as _,recordDecision as M,extractDecisionFromTurn as j}from"./decisions.js";import{makeMemoryTool as I}from"./memory.js";import{loadHooks as T,runHooks as E}from"./hooks.js";import{capToolResult as C,capOrSpill as v,MAX_TOOL_RESULT_CHARS as A}from"./spill.js";import{runToolCall as O,isBinaryFile as R}from"./toolpipe.js";import{inspectForTurn as P,isCodeFile as N}from"./code-tools.js";import{EngineeringRun as U}from"./engineering.js";import{inspectWebForTurn as J,isWebFile as D,turnCorrection as z}from"./web-inspect.js";import{inspectDocsForTurn as K,docTurnCorrection as F}from"./docx-tools.js";import{isFileWriteTool as W}from"./permissions.js";import{decideUpgrade as B}from"./routing.js";import{renderSdk as q,CODE_MODE_HIDDEN as L}from"./codemode.js";import*as Q from"./ide.js";import{c as G}from"./colors.js";export{C as capToolResult,v as capOrSpill,A as MAX_TOOL_RESULT_CHARS};export function toolConcurrency(e){const n=Number(e?.toolConcurrency);if(Number.isFinite(n)&&n>0)return Math.min(24,Math.floor(n));let o=4;try{o=t.cpus()?.length||4}catch{}return Math.max(8,Math.min(24,o-2))}const H=/context[_\s-]?length|maximum context|context window|too many tokens|reduce the length|prompt is too long|上下文.*(超|过长|超出|溢出)|输入.*过长|token.*(超出|超过).*(上限|限制)/i;export function isContextOverflowError(e){return H.test(String(e||""))}const V=32768,X=new Map;function Y(e,t){e&&(X.delete(e),X.set(e,t),X.size>200&&X.delete(X.keys().next().value))}function Z(e,t){let n="";try{n=JSON.stringify(t??{},(e,t)=>t&&"object"==typeof t&&!Array.isArray(t)?Object.fromEntries(Object.keys(t).sort().map(e=>[e,t[e]])):t)}catch{n=String(t)}return e+"|"+n.slice(0,4e3)}const ee=/(现在|接下来|下面|这就|马上|我来|让我|我要|我先|我这就|咱们|让我们|稍等|待我)/,te=/(做|进行|生成|编写|写(一|个|完|出)?|制作|计算|统计|核对|汇总|整理|分析|梳理|输出|列出|准备|运行|执行|开始)/,ne=/\b(let me|now let me|i.?ll|i will|let.?s|next,? i)\b[^.!?\n]*\b(write|create|generat|calculat|comput|do|produc|compil|analyz|summar|prepar|run|make|list)\b/i;export function jsonErrorContext(e,t){const n=String(e||""),o=String(t?.message||"").match(/position (\d+)/);if(!o)return`:${n.slice(0,200)}…`;const s=Number(o[1]);return`,出错位置附近:…${n.slice(Math.max(0,s-60),s)+"⟦此处⟧"+n.slice(s,s+30)}…${/[^\\]"[^,:}\]\s]/.test(n.slice(Math.max(0,s-60),s+1))?'看起来是字符串里夹了没转义的英文双引号(中文里引述请用「」或“”,或写成 \\")。':""}`}export function truncatedArgs(e,t,n){if("length"===n)return!0;const o=String(t?.message||"");if(!/Unterminated string|Unexpected end of JSON|Expected .* after .* in JSON at position/.test(o))return!1;const s=o.match(/position (\d+)/),r=s?Number(s[1]):String(e).length;return String(e).length>40&&r>=String(e).length-2}function oe(e){const t=String(e||"").trim().split(/\n+/).map(e=>e.trim()).filter(Boolean),n=t[t.length-1]||"";return!(!n||n.length>120)&&(!!ne.test(n)||ee.test(n)&&te.test(n))}function se(e){const t=String(e);return t.startsWith("工具执行失败")||t.startsWith("命令启动失败")||t.startsWith("【工具超时】")||t.includes("不是合法JSON")||t.includes("不是合法 JSON")||t.includes("未找到要替换的内容")||t.includes("路径超出项目目录")||t.includes("是目录而非文件")}function re(e){return"\n\n# 当前目标(用户设定)\n"+String(e).trim()+"\n在本回合结束、停止作答前,检查该目标是否已达成:若未达成,应继续推进(补充调研/改动/验证),不要过早停止;达成后用一句话说明已达成。"}const ie="【用户中断】",ae="\n\n# 上一轮被用户中断(重要)\n上一回合被用户主动中断:会话里标注「"+ie+"」的工具调用并未真正执行、没有任何结果。请勿臆测这些未完成工具的输出,也不要假设被中断的工作已完成或推断其结论;一切以用户最新一条消息为准——如与被中断的工作无关,就按新指令重新开始,需要时先与用户确认再继续。";function le(e){if(!Array.isArray(e))return!1;let t=-1;for(let n=e.length-1;n>=0;n--)if("user"===e[n].role){t=n;break}if(t<=0)return!1;for(let n=t-1;n>=0;n--){const t=e[n];if("user"===t.role)break;if("tool"===t.role&&"string"==typeof t.content&&t.content.includes(ie))return!0}return!1}function ce(e){return"string"==typeof e?e:Array.isArray(e)?e.filter(e=>"text"===e?.type).map(e=>e.text||"").join("\n"):""}async function ue(e,t,n){const o=new Array(e.length);let s=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(t,e.length))},async()=>{for(;s<e.length;){const t=s++;o[t]=await n(e[t],t)}})),o}export function isPlatformModel(e){return/^(agent|workflow|datahuman|squad)-/.test(String(e||""))}export function buildPlatformMessages(e){return e.filter(e=>("user"===e.role||"assistant"===e.role)&&!w(e)&&("string"==typeof e.content?e.content.trim().length>0:Array.isArray(e.content)&&e.content.length>0)).map(e=>({role:e.role,content:e.content}))}function de(e,t=200){const n=String(e).split("\n")[0];return n.length>t?n.slice(0,t)+"…":n}export async function runTurn({config:t,messages:w,permissions:C,cwd:v,ask:A,signal:H,extraTools:ee=[],depth:te=0,io:ne,events:me,todoState:fe,compaction:ge,pullQueued:pe,maxIters:he,fileJournal:be}){const ye=ne&&ne.write||(e=>process.stdout.write(e)),we=me||null,$e=e=>{we?we.onInfo?.(e):ye(G.gray(" · "+e)+"\n")};let Se=0,xe=0,ke=0,_e=null;const Me=Number(he||t.maxTurns)||120,je=isPlatformModel(t.model),Ie={low:24e4,medium:36e4,high:6e5}[t.reasoningEffort];let Te=!1,Ee=String(t.fallbackModel||"").trim(),Ce=0,ve=t.model,Ae=null;if(0===te&&t.autoModelStrong&&!je){const e=[...w].reverse().find(e=>"user"===e.role),n=ce(e?.content);Ae=B({text:n,planMode:t.planMode,attachmentCount:t.attachmentCount},{threshold:t.autoModelThreshold}),Ae.upgrade&&t.autoModelStrong!==t.model&&(ve=t.autoModelStrong),we?.onRouteDecision?.({...Ae,model:ve,upgraded:ve!==t.model}),t.debug&&$e(`[automodel] ${Ae.reason}`+(ve!==t.model?` → 升配 ${ve}`:" → 维持"))}const Oe=ge||{},Re=[...ee],Pe=!je&&Re.some(e=>"engineering_plan"===e.spec?.function?.name)?new U(v):null;let Ne=0;if(0===te&&!je&&!Re.some(e=>e.spec?.function?.name===x)){const e=fe||{list:[]};Re.push($({state:e,emit:e=>{we?we.onTodo?we.onTodo(e.map(e=>({...e}))):we.onInfo?.("任务清单:\n"+S(e)):ye(G.gray(d(S(e)," "))+"\n")}}))}if(0!==te||je||!1===t.memoryEnabled||Re.some(e=>"memory_write"===e.spec?.function?.name)||Re.push(I({cwd:v,emit:e=>{we?we.onMemory?we.onMemory(e):we.onInfo?.("已记入长期记忆:"+de(e,80)):ye(G.gray(" ✎ 已记入长期记忆:"+de(e,80))+"\n")}})),0===te&&!je&&!1!==t.decisionsEnabled&&!Re.some(e=>e.spec?.function?.name===_)){const e=w.filter(e=>"user"===e.role).length;Re.push(k({cwd:v,sessionId:t.sessionId||"",turn:e,emit:e=>{ct=!0,we?we.onDecision?we.onDecision(e):we.onInfo?.("已记录决策:"+de(e.title,80)):ye(G.gray(" ◆ 已记录决策:"+de(e.title,80))+"\n")}}))}const Ue=new Map;let Je=null,De=[],ze=new Set,Ke=0,Fe=null,We=new Set,Be=null;const qe=e=>{if("function"!=typeof e?.enabled)return!1;try{return!1===e.enabled(t)}catch{return!1}},Le=()=>{if(Array.isArray(ee)&&ee.length>Ke){for(const e of ee.slice(Ke)){const t=e?.spec?.function?.name;t&&!Ue.has(t)&&Re.push(e)}Ke=ee.length,Je=null}Fe!==t.toolMode&&(Fe=t.toolMode,Je=null);const e=je?[]:Re.filter(qe).map(e=>e.spec.function.name),n=e.join(",");if(Be!==n&&(Be=n,We=new Set(e),Je=null),null===Je){Ue.clear();for(const e of Re)Ue.set(e.spec.function.name,e);const e="both"===t.toolMode||"code"===t.toolMode;De=je?[]:[...l,...Re.filter(e=>!We.has(e.spec.function.name)).map(e=>e.spec)].filter(t=>e||"run_program"!==t.function.name),ze=new Set(De.map(e=>e.function.name));const n="code"===t.toolMode&&ze.has("run_program");Je=je?void 0:n?De.filter(e=>!L.has(e.function.name)):De}};Ke=Array.isArray(ee)?ee.length:0,Le();let Qe=0,Ge=!1,He=0,Ve="",Xe=0,Ye=0,Ze=null;const et=new Set;let tt=0;const nt=new Set;let ot=0;const st=new Set;let rt=0,it=0,at=0,lt=!1,ct=!1;const ut=new Set;let dt="";const mt=t.maxTokens||8192;let ft=Math.max(mt,X.get(t.sessionId)||0),gt=0,pt=!1,ht=!1;const bt=0!==te||je?null:T(v),yt=!(!bt||!bt.all.length);if(yt){const e=[];if(w.filter(e=>"user"===e.role).length<=1){const n=E("SessionStart",{cwd:v,sessionId:t.sessionId||"",model:t.model||""},bt);n.injectContext&&e.push(n.injectContext);for(const t of n.outputs)e.push(t.stdout)}const n=[...w].reverse().find(e=>"user"===e.role),o=E("UserPromptSubmit",{cwd:v,prompt:ce(n?.content),sessionId:t.sessionId||"",model:t.model||"",turn:w.filter(e=>"user"===e.role).length},bt);if(o.block)return $e("提交被UserPromptSubmit钩子阻断:"+o.reason),{error:"提交被钩子阻断:"+o.reason,usage:{prompt:Se,completion:xe},lastPromptTokens:ke,lastUsage:_e};o.injectContext&&e.push(o.injectContext);for(const t of o.outputs)e.push(t.stdout);e.length&&(Ze="【钩子提供的附加上下文】\n"+e.join("\n"))}for(let l=0;l<Me;l++){Le();const d=l===Me-1;!d||je||Ge||Ze||(Ze="已接近本回合的工具调用轮数上限。请不要再调用任何工具,基于目前已获得的信息,直接用中文输出面向用户的完整最终答复。");const $=je?0:await f(w,t,{state:Oe,lastPromptTokens:ke});$>0&&t.debug&&$e(`[debug] 已压缩上下文(${$} 条较早消息已摘要)`);let S=!1,x=!1;const k=new i,_=e=>{e&&(S||!x||we||ye("\n"),S=!0,we?we.onDelta?.(e):ye(e))};if(0===te&&!je&&!1!==t.skillsEnabled){const e=g(w,Oe),n=y(w,e,v,{allowInsert:!(Oe.upTo>0),maxTokens:h(t)});"none"!==n.action&&t.debug&&$e(`[skills] 目录${{initial:"首投",replace:"已刷新",restore:"压缩后重投"}[n.action]}(${n.count} 条,指纹 ${n.digest})`),"none"!==n.action&&we?.onSkillCatalog?.({...n})}const I=je?buildPlatformMessages(w):g(w,Oe);let T=I;if(!je&&I.length&&"system"===I[0]?.role){let e="";t.planMode&&(e+="\n\n# 计划模式(当前开启)\n你现在处于计划模式:只能用只读工具(read_file/glob/grep/list_files/fetch_url)调研,不得做任何改动——write_file/edit_file/run_bash等写或执行操作都会被拒绝,不要反复尝试。请先充分理解需求与现状,然后产出一份清晰、分步的实施方案:涉及哪些文件、每步改动要点、如何验证、风险点,用todo_write列出步骤,随后停下等待用户确认。用户关闭计划模式后你再执行。"),t.sandbox&&(e+="\n\n# 沙箱模式(当前开启)\n禁止执行shell命令(run_bash)、联网(fetch_url)与环境/应用自动化工具(install_*/setup_environment/open_*),调用会被拒绝,不要反复尝试。只做本地文件读写与分析。"),t.goal&&String(t.goal).trim()&&(e+=re(t.goal)),le(w)&&(e+=ae),ze.has("run_program")&&(e+="\n\n# 用程序编排工具(run_program)\n"+q(De)),e&&(T=[{...I[0],content:String(I[0].content||"")+e},...I.slice(1)])}const A=[];dt?A.push({role:"assistant",content:dt},{role:"user",content:"你上一条回复因达到单轮输出长度上限而被截断,用户尚未看到剩余部分。请从截断处直接继续输出剩余内容:无缝衔接上文(若截断处在句子、代码块或表格中间,从中断字符处接着写),不要重复任何已输出的内容,不要道歉或解释,不要重新开头或总结前文。"}):Ze&&A.push({role:"user",content:Ze});const U=Date.now();let B,L=!1,ee="";try{B=await o({baseUrl:t.baseUrl,apiKey:t.apiKey,model:ve,supportsImages:t.imageCapabilities?.[ve],messages:A.length?[...T,...A]:T,tools:Ge||d?void 0:Je,maxTokens:ft,extraBody:pt?{enable_thinking:!1}:void 0,reasoningEffort:t.reasoningEffort,stallTimeoutMs:Ie,client:t.client,sessionId:t.sessionId,aux:t.aux||void 0,continuation:!!dt,tailCount:A.length},{onContent:e=>{L=!0,ee+=e,_(k.feed(e))},onReasoning:e=>{!1!==t.showReasoning&&(dt||(we?we.onReasoning?.(e):(x||ye(G.gray("\n[思考] ")),ye(G.gray(e))),x=!0))},onReasoningEnd:()=>we?.onReasoningEnd?.(),onRetry:(e,n,o,s)=>{s?.network?$e(`${o.replace(/[::].*$/,"")},${Math.round(n/1e3)} 秒后自动重试(第 ${e}/${s.max} 次)`):t.debug&&$e(`[retry] 模型响应异常,${Math.round(n/1e3)}s后自动重试(第 ${e} 次):${o}`)}},H)}catch(e){if("AbortError"===e.name)return we?we.onInfo?.("已中断"):ye(G.gray("\n[已中断]\n")),{aborted:!0,usage:{prompt:Se,completion:xe},lastPromptTokens:ke,lastUsage:_e};if(!e.nonRetryable&&!e.partial&&ft>mt&&!ht&&!H?.aborted){ht=!0,gt=2,ft=mt,X.delete(t.sessionId),t.debug&&$e(`[debug] 抬高输出预算后请求失败(${e.message}),已撤回到 ${mt} 重试`);continue}if(!e.nonRetryable&&!e.partial&&!je&&!H?.aborted&&isContextOverflowError(e.message)&&Ce<3){Ce++,b(t,e.message);const n=await f(w,t,{state:Oe,force:!0});if(n>0){L&&ee&&(dt+=ee),t.debug&&$e(`[overflow] 上下文超出模型窗口,已强制压缩 ${n} 条较早消息后重试`);continue}t.debug&&$e("[overflow] 上下文超限但无可压缩内容,转兜底模型")}const n=r(e);if(!(n||e.nonRetryable||e.partial||e.network||je||Te||H?.aborted)){if(!Ee)try{const e=await s(t.baseUrl,t.apiKey);Ee=String(e&&e.fallback_model||"").trim()}catch{}if(Ee&&Ee!==ve){Te=!0,ve=Ee,L&&ee&&(dt+=ee),t.debug&&$e(`[fallback] 主模型失败(${e.message}),静默切兜底模型 ${Ee}${L?"(续写)":""}`);continue}}return we?we.onInfo?.("✗ 模型调用失败:"+e.message):ye("\n"+G.red("✗ 模型调用失败:")+e.message+"\n"),{error:e.message,errorKind:n,usage:{prompt:Se,completion:xe},lastPromptTokens:ke,lastUsage:_e}}_(k.flush()),B.usage&&(Se+=B.usage.prompt_tokens||0,xe+=B.usage.completion_tokens||0,ke=B.usage.prompt_tokens||ke,_e=B.usage,0===te&&we&&we.onUsage?.({lastPromptTokens:ke,prompt:Se,completion:xe,ctx:p({messages:w,compaction:Oe,config:t,lastPromptTokens:ke})})),t.debug&&$e(`[debug] 轮${l+1} · ${t.model} · in ${B.usage?.prompt_tokens||0} / out ${B.usage?.completion_tokens||0} tok · ${Date.now()-U}ms · ${(B.toolCalls||[]).length} 工具调用`);const{text:ne,sawMarkup:ce}=a(B.content),me=ce||k.sawMarkup;Ze=null;const fe=je?[]:B.toolCalls;if(!fe.length){if(me&&!ne.trim()&&!je){if(Qe<2){Qe++,2===Qe&&(Ge=!0),Ze=1===Qe?"你刚才的输出把工具调用写成了文本标记,工具并未被执行,该内容也不会展示给用户。请重新处理:若需调用工具,必须使用规范的函数调用(tool_calls)通道发起,绝不要以任何文本形式书写工具调用标记;若无需工具,请直接用中文输出面向用户的最终答复。":"请停止调用任何工具。基于目前已获得的全部信息,直接用中文输出面向用户的完整最终答复,不要输出任何工具调用或协议标记。",$e(`已拦截模型的文本化工具调用标记(工具未被执行),纠正重试中(第 ${Qe} 次)`);continue}$e("模型持续输出无效的工具调用标记,已全部拦截;本轮无有效答复(可换个说法重试或更换模型)")}if(!je&&"length"===B.finishReason&&ne&&it<3){it++,dt+=ne,t.debug&&$e(`[debug] 输出达单轮长度上限,自动续写(第 ${it} 段)`);continue}if(!je&&"length"===B.finishReason&&!ne.trim()&&!dt){if(gt<2&&ft<V){gt++,ft=Math.min(3*ft,V),Y(t.sessionId,ft),t.debug&&$e(`[debug] 思考吃光输出预算(正文为空),抬高单轮上限到 ${ft} 重试(第 ${gt} 次)`);continue}if(!pt){pt=!0,Ze="你上一条把全部输出预算用在了思考上,没有输出任何正文,用户什么都没看到。请立刻直接输出面向用户的最终答复(结论、表格、结果放最前面),思考尽量精简。",t.debug&&$e("[debug] 抬高预算后仍无正文,改为要求先出正文(并best-effort关思考)");continue}}S&&!we&&ye("\n");let e=dt?dt+ne:ne;dt="",e&&w.push({role:"assistant",content:e});const o=!e.trim();if(lt&&!o&&oe(e)&&"length"!==B.finishReason&&!Ge&&at<2){at++,t.debug&&$e(`[debug] 检测到「宣告未落地」预告式收尾,注入续跑纠偏(第 ${at} 次)`),w.push({role:"user",content:'你上一条只是说明"接下来要做什么",但既没有发起工具调用、也没有给出结果——用户还没拿到任何最终产物。请立即执行你刚才所说的步骤(需要就用run_python/工具,不需要就直接算),并直接输出面向用户的完整最终结果(例如完整的按月汇总表与逐月核对表、差异结论),不要再只描述你打算做什么。'});continue}if(o&&!je&&"length"!==B.finishReason&&!Ge&&at<2){at++,t.debug&&$e(`[debug] 本回合正文为空,注入续跑纠偏(第 ${at} 次)`),Ze="你上一条回复没有输出任何面向用户的正文(内容可能全部留在了思考里),用户一个字都没拿到。请立刻用中文直接输出完整的最终答复,不要只在思考里推演。";continue}if(!je&&0===te&&!1!==t.codeSelfCheck&&et.size&&tt<1&&!Ge&&!o&&"length"!==B.finishReason){tt++;const e=await P(v,[...et],t);if(e&&e.issues.length){const t=e.issues.slice(0,12),o=t.map(e=>{const t=n.relative(v,e.path)||e.path;return` [${e.rule}] ${t}:${e.line}${e.unit?` 〔${e.unit}〕`:""}\n 问题:${e.msg}\n 改法:${e.hint||"按上面的说明处理"}`}).join("\n"),s=e.issues.length>t.length?`\n …… 另有 ${e.issues.length-t.length} 条,用code_inspect看全量。`:"";$e(`代码自检:本回合改动的 ${et.size} 个源码文件有 ${e.issues.length} 处必修问题,已要求修复`),Ze="【代码自检】你本回合改动的源码经体检发现以下**必修**问题,请逐条改完再结束:\n"+o+s+"\n\n改完用code_inspect(path) 复检确认必修清零。\n若确认某一条是误报,在那一行上方加 `// code-inspect-ignore: 规则名 简短理由`,并在回复里说明为什么判定它是误报——不要默默跳过,也不要在没修完时就宣布完成。";continue}}if(!je&&0===te&&!1!==t.webSelfCheck&&nt.size&&ot<1&&!Ge&&!o&&"length"!==B.finishReason){ot++;const e=await J(v,[...nt],t).catch(()=>null);if(e&&e.errors.length){$e(`网页自检:本回合改动的页面(${e.entries.join("、")})在浏览器里有 ${e.errors.length} 处必修问题,已要求修复`),Ze=z(e);continue}}if(!je&&0===te&&!1!==t.docSelfCheck&&st.size&&rt<1&&!Ge&&!o&&"length"!==B.finishReason){rt++;const e=await K(v,[...st],t).catch(()=>null);if(e&&e.issues.length){$e(`文档自检:本回合生成的 ${st.size} 份文档有 ${e.issues.length} 处必修问题,已要求修复`),Ze=F(e,v);continue}}if(o&&!me){const e=gt>0||pt?",自动抬高上限并重试后仍未产出":"";$e("length"===B.finishReason?`模型把本回合的输出预算(单轮上限 ${ft})全用在了思考上,没有产出正文${e}。建议把任务拆小一点分步做,或换个模型再试。`:`模型本回合没有返回任何正文${at>0?"(已自动重试)":""}。可以直接重发,或换个说法/换个模型再试。`)}const s=Pe?.pendingNote();if(s){if(!Ge&&Ne<2&&!H?.aborted){Ne++,Ze=s,$e(s);continue}e+="\n\n【未完成验证】"+s,"assistant"===w.at(-1)?.role?w.at(-1).content=e:w.push({role:"assistant",content:e}),$e("【未完成验证】"+s)}we&&we.onTurnEnd?.(e);const r=pe?.();if(r){w.push({role:"user",content:r});continue}if(yt){const n=E("Stop",{cwd:v,result:e,sessionId:t.sessionId||"",model:t.model||"",turn:w.filter(e=>"user"===e.role).length},bt);for(const e of n.outputs)$e("钩子·Stop:"+de(e.stdout,160))}if(!je&&t.decisionAutoExtract&&!1!==t.decisionsEnabled&<&&!ct&&e&&e.trim())try{const n=[...w].reverse().find(e=>"user"===e.role),o=n&&"string"==typeof n.content?n.content:"",s=await j(t,{userText:o,assistantText:e,toolNames:[...ut]}),r="number"==typeof t.decisionExtractMinConfidence?t.decisionExtractMinConfidence:.55;if(s&&(s.confidence??0)>=r){const e=w.filter(e=>"user"===e.role).length,n=M(v,{...s,title:s.what,status:"draft",origin:"agent",confidence:Math.min(s.confidence??.5,.7),sessionId:t.sessionId||"",turn:e},{origin:"agent"});we?we.onDecision?we.onDecision(n):we.onInfo?.("已抽取决策草稿(待确认):"+de(n.title,80)):ye(G.gray(" ◇ 已抽取决策草稿(待确认):"+de(n.title,80))+"\n")}}catch{}return{usage:{prompt:Se,completion:xe},lastPromptTokens:ke,lastUsage:_e}}lt=!0,S&&!we&&ye("\n"),we&&ne&&we.onAssistantText?.(ne),w.push({role:"assistant",content:dt?dt+ne:ne,tool_calls:fe,...B.nativeState?{huicai_anthropic:B.nativeState}:{}}),dt="";const ge=fe.map((e,n)=>{const o=e.function?.name||"";let s={},r=null,i=!1;const a=e.function?.arguments||"";if(a)try{s=JSON.parse(a)}catch(e){i=truncatedArgs(a,e,B.finishReason),r=i?`工具参数在第 ${String(a).length} 个字符处被截断:这次回复的输出触到了单次上限(约 ${ft} token),参数没写完——不是字符或转义的问题,原样重试还会在同一处断。常见成因是一段内容太长,或一条回复里并行发了好几个大参数的调用(上限是整条回复共用的)。请拆小:大参数的调用一次少发几个;write_file每段约 3000 字以内,后续段落用append=true追加;很长的JSON(测算假设、图表spec)先write_file落盘,再用对应的 *_path / spec_path参数传入。`:`工具参数不是合法JSON(${e.message})${jsonErrorContext(a,e)}请修正参数后重新调用。`}const l=Ue.get(o);if(l&&We.has(o)&&!r){const e="function"==typeof l.disabledMessage?l.disabledMessage(t):l.disabledMessage;r=String(e||`工具 ${o} 当前已停用(开关已关闭),未执行。`)}const u=Q.isActive()&&("write_file"===o||"edit_file"===o);return{tc:e,idx:n,name:o,args:s,argsError:r,argsTruncated:i,extra:l,readOnly:l?!!l.readOnly:c.has(o),deferToIde:u}});!je&&ge.some(e=>e.argsTruncated)&><2&&ft<V&&(gt++,ft=Math.min(3*ft,V),Y(t.sessionId,ft),t.debug&&$e(`[debug] 工具参数被单轮输出上限截断,抬高到 ${ft}`));const he=e=>{for(let t=e;t<ge.length;t++)w.push({role:"tool",tool_call_id:ge[t].tc.id||`call_${t}`,content:ie+" 该工具未执行,无任何结果,请勿臆测其输出。"});return we?we.onInfo?.("已中断"):ye(G.gray("\n[已中断]\n")),{aborted:!0,usage:{prompt:Se,completion:xe},lastPromptTokens:ke,lastUsage:_e}},Ae={cwd:v,config:t,signal:H,permissions:C,hooks:bt,hooksActive:yt,fileJournal:be,info:$e,shortLine:de,engineering:Pe,turn:w.filter(e=>"user"===e.role).length,onStart:e=>e.nested?$e("程序内调用:"+u(e.name,e.args)):Ke(e),listTools:()=>De,resolveTool:e=>{const t=Ue.get(e);return t&&We.has(e)?null:t?{readOnly:!!t.readOnly,extra:t}:ze.has(e)?{readOnly:c.has(e),extra:null}:null}},Re=e=>O({name:e.name,args:e.args,readOnly:e.readOnly,extra:e.extra,callId:e.tc?.id,deferToIde:e.deferToIde,argsError:e.argsError,item:e},Ae),Ke=e=>{ut.add(e.name),we?we.onToolStart?.(e.name,e.args,{callId:e.callId||e.tc?.id}):ye(G.cyan(" ⏺ ")+u(e.name,e.args)+"\n")},Fe=(e,t)=>{const n=e.item||e;se(t)&&He++;const o=Z(e.name,e.args);o===Ve?Xe++:(Ve=o,Xe=1,Ye=0),we?we.onToolResult?.(e.name,String(t),{callId:n.tc?.id,failed:se(t)}):ye(G.gray(" "+de(t))+"\n"),w.push({role:"tool",tool_call_id:n.tc.id,content:String(t)})},Be=(e,t)=>{const n=e.item||e;we?we.onSkip?.(e.name,e.args,{callId:n.tc?.id,reason:t}):ye(G.gray(` ⊘ ${u(e.name,e.args)} — 已跳过\n`)),w.push({role:"tool",tool_call_id:n.tc.id,content:t||"未授权,未执行。"})},qe=e=>"render_diagram"!==e,wt=t=>{try{return e.readFileSync(t,"utf8")}catch{return""}},$t=(e,t)=>{const o=n.resolve(v,String(e.args.path));if(R(o))return;const s=m(t,wt(o),{path:String(e.args.path)});s&&(N(o)&&et.add(o),D(o)&&nt.add(o),we?we.onFileDiff?.(s):ye(s+"\n"))};let St=0;for(;St<ge.length;){if(H?.aborted)return he(St);const o=ge[St];if(o.readOnly&&!o.argsError){let e=St;for(;e<ge.length&&ge[e].readOnly&&!ge[e].argsError;)e++;const n=ge.slice(St,e),o=await ue(n,toolConcurrency(t),e=>Re(e));for(const e of o)null!=e.denied?Be(e,e.denied):Fe(e,e.result);if(St=e,H?.aborted)return he(St);continue}const s=await Re(o);if(H?.aborted&&null==s.denied&&!s.settled)return he(St);if(null==s.denied)if(s.settled)Ke(s),Fe(s,s.result),St++;else{if(Fe(s,s.result),null!=s.meta.before&&qe(s.name)&&$t(s,s.meta.before),W(s.name)&&/\.docx$/i.test(String(s.args?.path||""))){const t=n.resolve(v,String(s.args.path));e.existsSync(t)&&st.add(t)}St++}else Be(s,s.denied),St++}He>=3&&!Ge&&!Ze&&(Ze="你刚才连续多次工具调用失败。请停下来重新审视:是不是文件路径/内容/前提假设错了?先用read_file / grep / list_files核实现状,再换一种更可靠的方法,绝不要重复同样会失败的调用。",He=0,$e("检测到连续工具失败,已提示智能体换一种更可靠的方法")),Ge||Ze||(Xe>=5&&Ye<2?(Ze="停止重复调用。你已经用完全相同的参数调用同一工具达 5 次,这不会产生任何新信息,只是在空转。现在必须二选一:① 基于已有结果直接给出结论/继续下一步;② 明确说明你缺少什么、为什么现有结果不够,然后换一种**明显不同**的方法去拿。",Ye=2,$e(`检测到同一调用连续重复 ${Xe} 次,已要求智能体停止空转`)):Xe>=3&&Ye<1&&(Ze="注意:你刚刚用**完全相同的参数**连续调用了同一个工具多次,结果不会因为再调一次而改变。请先仔细重读上一次的返回内容——答案很可能已经在里面;若确实需要更多信息,请换不同的参数或换一个工具,不要重复同一次调用。",Ye=1,$e(`检测到同一调用连续重复 ${Xe} 次,已提示智能体重读上次结果`)));const xt=pe?.();xt&&w.push({role:"user",content:xt})}return dt&&(w.push({role:"assistant",content:dt}),we&&we.onTurnEnd?.(dt)),we?we.onInfo?.("已达最大工具轮数,停止"):ye(G.yellow("\n[已达最大工具轮数,停止]\n")),{usage:{prompt:Se,completion:xe},lastPromptTokens:ke,lastUsage:_e}}
|
|
1
|
+
import{accountSnapshot as e,withAccountOperation as t,assertAccountOperation as n}from"./account-scope.js";import o from"node:fs";import s from"node:os";import r from"node:path";import{streamChat as i,getPlatformConfig as a}from"./api.js";import{classifyError as l}from"./errkind.js";import{MarkupFilter as c,stripMarkup as u}from"./markup-filter.js";import{TOOL_SPECS as d,READONLY_TOOLS as m}from"./tools.js";import{toolLabel as f,indent as g,renderFileDiff as p}from"./render.js";import{compactIfNeeded as h,buildModelMessages as b,contextGauge as y}from"./compaction.js";import{catalogTokenBudget as w,noteMemberLimitFromError as $}from"./context-window.js";import{syncSkillCatalog as S,catalogDigestOf as x}from"./skill-catalog.js";import{makeTodoTool as k,formatTodos as _,TODO_TOOL_NAME as M}from"./todo.js";import{makeDecisionTool as j,DECISION_TOOL_NAME as I,recordDecision as T,extractDecisionFromTurn as E}from"./decisions.js";import{makeMemoryTool as v}from"./memory.js";import{loadHooks as C,runHooks as A}from"./hooks.js";import{capToolResult as O,capOrSpill as R,MAX_TOOL_RESULT_CHARS as P}from"./spill.js";import{runToolCall as N,isBinaryFile as U}from"./toolpipe.js";import{inspectForTurn as J,isCodeFile as D}from"./code-tools.js";import{EngineeringRun as z}from"./engineering.js";import{inspectWebForTurn as K,isWebFile as F,turnCorrection as L}from"./web-inspect.js";import{inspectDocsForTurn as W,docTurnCorrection as B}from"./docx-tools.js";import{isFileWriteTool as q}from"./permissions.js";import{decideUpgrade as Q}from"./routing.js";import{renderSdk as G,CODE_MODE_HIDDEN as H}from"./codemode.js";import*as V from"./ide.js";import{c as X}from"./colors.js";export{O as capToolResult,R as capOrSpill,P as MAX_TOOL_RESULT_CHARS};export function toolConcurrency(e){const t=Number(e?.toolConcurrency);if(Number.isFinite(t)&&t>0)return Math.min(24,Math.floor(t));let n=4;try{n=s.cpus()?.length||4}catch{}return Math.max(8,Math.min(24,n-2))}const Y=/context[_\s-]?length|maximum context|context window|too many tokens|reduce the length|prompt is too long|上下文.*(超|过长|超出|溢出)|输入.*过长|token.*(超出|超过).*(上限|限制)/i;export function isContextOverflowError(e){return Y.test(String(e||""))}const Z=32768,ee=new Map;function te(e,t){e&&(ee.delete(e),ee.set(e,t),ee.size>200&&ee.delete(ee.keys().next().value))}function ne(e,t){let n="";try{n=JSON.stringify(t??{},(e,t)=>t&&"object"==typeof t&&!Array.isArray(t)?Object.fromEntries(Object.keys(t).sort().map(e=>[e,t[e]])):t)}catch{n=String(t)}return e+"|"+n.slice(0,4e3)}const oe=/(现在|接下来|下面|这就|马上|我来|让我|我要|我先|我这就|咱们|让我们|稍等|待我)/,se=/(做|进行|生成|编写|写(一|个|完|出)?|制作|计算|统计|核对|汇总|整理|分析|梳理|输出|列出|准备|运行|执行|开始)/,re=/\b(let me|now let me|i.?ll|i will|let.?s|next,? i)\b[^.!?\n]*\b(write|create|generat|calculat|comput|do|produc|compil|analyz|summar|prepar|run|make|list)\b/i;export function jsonErrorContext(e,t){const n=String(e||""),o=String(t?.message||"").match(/position (\d+)/);if(!o)return`:${n.slice(0,200)}…`;const s=Number(o[1]);return`,出错位置附近:…${n.slice(Math.max(0,s-60),s)+"⟦此处⟧"+n.slice(s,s+30)}…${/[^\\]"[^,:}\]\s]/.test(n.slice(Math.max(0,s-60),s+1))?'看起来是字符串里夹了没转义的英文双引号(中文里引述请用「」或“”,或写成 \\")。':""}`}export function truncatedArgs(e,t,n){if("length"===n)return!0;const o=String(t?.message||"");if(!/Unterminated string|Unexpected end of JSON|Expected .* after .* in JSON at position/.test(o))return!1;const s=o.match(/position (\d+)/),r=s?Number(s[1]):String(e).length;return String(e).length>40&&r>=String(e).length-2}function ie(e){const t=String(e||"").trim().split(/\n+/).map(e=>e.trim()).filter(Boolean),n=t[t.length-1]||"";return!(!n||n.length>120)&&(!!re.test(n)||oe.test(n)&&se.test(n))}function ae(e){const t=String(e);return t.startsWith("工具执行失败")||t.startsWith("命令启动失败")||t.startsWith("【工具超时】")||t.includes("不是合法JSON")||t.includes("不是合法 JSON")||t.includes("未找到要替换的内容")||t.includes("路径超出项目目录")||t.includes("是目录而非文件")}function le(e){return"\n\n# 当前目标(用户设定)\n"+String(e).trim()+"\n在本回合结束、停止作答前,检查该目标是否已达成:若未达成,应继续推进(补充调研/改动/验证),不要过早停止;达成后用一句话说明已达成。"}const ce="【用户中断】",ue="\n\n# 上一轮被用户中断(重要)\n上一回合被用户主动中断:会话里标注「"+ce+"」的工具调用并未真正执行、没有任何结果。请勿臆测这些未完成工具的输出,也不要假设被中断的工作已完成或推断其结论;一切以用户最新一条消息为准——如与被中断的工作无关,就按新指令重新开始,需要时先与用户确认再继续。";function de(e){if(!Array.isArray(e))return!1;let t=-1;for(let n=e.length-1;n>=0;n--)if("user"===e[n].role){t=n;break}if(t<=0)return!1;for(let n=t-1;n>=0;n--){const t=e[n];if("user"===t.role)break;if("tool"===t.role&&"string"==typeof t.content&&t.content.includes(ce))return!0}return!1}function me(e){return"string"==typeof e?e:Array.isArray(e)?e.filter(e=>"text"===e?.type).map(e=>e.text||"").join("\n"):""}async function fe(e,t,n){const o=new Array(e.length);let s=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(t,e.length))},async()=>{for(;s<e.length;){const t=s++;o[t]=await n(e[t],t)}})),o}export function isPlatformModel(e){return/^(agent|workflow|datahuman|squad)-/.test(String(e||""))}export function buildPlatformMessages(e){return e.filter(e=>("user"===e.role||"assistant"===e.role)&&!x(e)&&("string"==typeof e.content?e.content.trim().length>0:Array.isArray(e.content)&&e.content.length>0)).map(e=>({role:e.role,content:e.content}))}function ge(e,t=200){const n=String(e).split("\n")[0];return n.length>t?n.slice(0,t)+"…":n}export async function runTurn(o){n();const s=e();if(!s.scope)return pe(o);const r=new AbortController,i=()=>r.abort(o.signal?.reason);o.signal?.aborted?i():o.signal?.addEventListener("abort",i,{once:!0});const a=setInterval(()=>{s.current()||r.abort(new Error("登录状态已改变,已停止旧账号任务"))},100);a.unref();try{return await t(s,()=>pe({...o,signal:r.signal}))}finally{clearInterval(a),o.signal?.removeEventListener("abort",i)}}async function pe({config:e,messages:t,permissions:n,cwd:s,ask:x,signal:O,extraTools:R=[],depth:P=0,io:Y,events:oe,todoState:se,compaction:re,pullQueued:pe,maxIters:he,fileJournal:be}){const ye=Y&&Y.write||(e=>process.stdout.write(e)),we=oe||null,$e=e=>{we?we.onInfo?.(e):ye(X.gray(" · "+e)+"\n")};let Se=0,xe=0,ke=0,_e=null;const Me=Number(he||e.maxTurns)||120,je=isPlatformModel(e.model),Ie={low:24e4,medium:36e4,high:6e5}[e.reasoningEffort];let Te=!1,Ee=String(e.fallbackModel||"").trim(),ve=0,Ce=e.model,Ae=null;if(0===P&&e.autoModelStrong&&!je){const n=[...t].reverse().find(e=>"user"===e.role),o=me(n?.content);Ae=Q({text:o,planMode:e.planMode,attachmentCount:e.attachmentCount},{threshold:e.autoModelThreshold}),Ae.upgrade&&e.autoModelStrong!==e.model&&(Ce=e.autoModelStrong),we?.onRouteDecision?.({...Ae,model:Ce,upgraded:Ce!==e.model}),e.debug&&$e(`[automodel] ${Ae.reason}`+(Ce!==e.model?` → 升配 ${Ce}`:" → 维持"))}const Oe=re||{},Re=[...R],Pe=!je&&Re.some(e=>"engineering_plan"===e.spec?.function?.name)?new z(s):null;let Ne=0;if(0===P&&!je&&!Re.some(e=>e.spec?.function?.name===M)){const e=se||{list:[]};Re.push(k({state:e,emit:e=>{we?we.onTodo?we.onTodo(e.map(e=>({...e}))):we.onInfo?.("任务清单:\n"+_(e)):ye(X.gray(g(_(e)," "))+"\n")}}))}if(0!==P||je||!1===e.memoryEnabled||Re.some(e=>"memory_write"===e.spec?.function?.name)||Re.push(v({cwd:s,emit:e=>{we?we.onMemory?we.onMemory(e):we.onInfo?.("已记入长期记忆:"+ge(e,80)):ye(X.gray(" ✎ 已记入长期记忆:"+ge(e,80))+"\n")}})),0===P&&!je&&!1!==e.decisionsEnabled&&!Re.some(e=>e.spec?.function?.name===I)){const n=t.filter(e=>"user"===e.role).length;Re.push(j({cwd:s,sessionId:e.sessionId||"",turn:n,emit:e=>{ct=!0,we?we.onDecision?we.onDecision(e):we.onInfo?.("已记录决策:"+ge(e.title,80)):ye(X.gray(" ◆ 已记录决策:"+ge(e.title,80))+"\n")}}))}const Ue=new Map;let Je=null,De=[],ze=new Set,Ke=0,Fe=null,Le=new Set,We=null;const Be=t=>{if("function"!=typeof t?.enabled)return!1;try{return!1===t.enabled(e)}catch{return!1}},qe=()=>{if(Array.isArray(R)&&R.length>Ke){for(const e of R.slice(Ke)){const t=e?.spec?.function?.name;t&&!Ue.has(t)&&Re.push(e)}Ke=R.length,Je=null}Fe!==e.toolMode&&(Fe=e.toolMode,Je=null);const t=je?[]:Re.filter(Be).map(e=>e.spec.function.name),n=t.join(",");if(We!==n&&(We=n,Le=new Set(t),Je=null),null===Je){Ue.clear();for(const e of Re)Ue.set(e.spec.function.name,e);const t="both"===e.toolMode||"code"===e.toolMode;De=je?[]:[...d,...Re.filter(e=>!Le.has(e.spec.function.name)).map(e=>e.spec)].filter(e=>t||"run_program"!==e.function.name),ze=new Set(De.map(e=>e.function.name));const n="code"===e.toolMode&&ze.has("run_program");Je=je?void 0:n?De.filter(e=>!H.has(e.function.name)):De}};Ke=Array.isArray(R)?R.length:0,qe();let Qe=0,Ge=!1,He=0,Ve="",Xe=0,Ye=0,Ze=null;const et=new Set;let tt=0;const nt=new Set;let ot=0;const st=new Set;let rt=0,it=0,at=0,lt=!1,ct=!1;const ut=new Set;let dt="";const mt=e.maxTokens||8192;let ft=Math.max(mt,ee.get(e.sessionId)||0),gt=0,pt=!1,ht=!1;const bt=0!==P||je?null:C(s),yt=!(!bt||!bt.all.length);if(yt){const n=[];if(t.filter(e=>"user"===e.role).length<=1){const t=A("SessionStart",{cwd:s,sessionId:e.sessionId||"",model:e.model||""},bt);t.injectContext&&n.push(t.injectContext);for(const e of t.outputs)n.push(e.stdout)}const o=[...t].reverse().find(e=>"user"===e.role),r=A("UserPromptSubmit",{cwd:s,prompt:me(o?.content),sessionId:e.sessionId||"",model:e.model||"",turn:t.filter(e=>"user"===e.role).length},bt);if(r.block)return $e("提交被UserPromptSubmit钩子阻断:"+r.reason),{error:"提交被钩子阻断:"+r.reason,usage:{prompt:Se,completion:xe},lastPromptTokens:ke,lastUsage:_e};r.injectContext&&n.push(r.injectContext);for(const e of r.outputs)n.push(e.stdout);n.length&&(Ze="【钩子提供的附加上下文】\n"+n.join("\n"))}for(let d=0;d<Me;d++){qe();const g=d===Me-1;!g||je||Ge||Ze||(Ze="已接近本回合的工具调用轮数上限。请不要再调用任何工具,基于目前已获得的信息,直接用中文输出面向用户的完整最终答复。");const x=je?0:await h(t,e,{state:Oe,lastPromptTokens:ke});x>0&&e.debug&&$e(`[debug] 已压缩上下文(${x} 条较早消息已摘要)`);let k=!1,_=!1;const M=new c,j=e=>{e&&(k||!_||we||ye("\n"),k=!0,we?we.onDelta?.(e):ye(e))};if(0===P&&!je&&!1!==e.skillsEnabled){const n=b(t,Oe),o=S(t,n,s,{allowInsert:!(Oe.upTo>0),maxTokens:w(e)});"none"!==o.action&&e.debug&&$e(`[skills] 目录${{initial:"首投",replace:"已刷新",restore:"压缩后重投"}[o.action]}(${o.count} 条,指纹 ${o.digest})`),"none"!==o.action&&we?.onSkillCatalog?.({...o})}const I=je?buildPlatformMessages(t):b(t,Oe);let v=I;if(!je&&I.length&&"system"===I[0]?.role){let n="";e.planMode&&(n+="\n\n# 计划模式(当前开启)\n你现在处于计划模式:只能用只读工具(read_file/glob/grep/list_files/fetch_url)调研,不得做任何改动——write_file/edit_file/run_bash等写或执行操作都会被拒绝,不要反复尝试。请先充分理解需求与现状,然后产出一份清晰、分步的实施方案:涉及哪些文件、每步改动要点、如何验证、风险点,用todo_write列出步骤,随后停下等待用户确认。用户关闭计划模式后你再执行。"),e.sandbox&&(n+="\n\n# 沙箱模式(当前开启)\n禁止执行shell命令(run_bash)、联网(fetch_url)与环境/应用自动化工具(install_*/setup_environment/open_*),调用会被拒绝,不要反复尝试。只做本地文件读写与分析。"),e.goal&&String(e.goal).trim()&&(n+=le(e.goal)),de(t)&&(n+=ue),ze.has("run_program")&&(n+="\n\n# 用程序编排工具(run_program)\n"+G(De)),n&&(v=[{...I[0],content:String(I[0].content||"")+n},...I.slice(1)])}const C=[];dt?C.push({role:"assistant",content:dt},{role:"user",content:"你上一条回复因达到单轮输出长度上限而被截断,用户尚未看到剩余部分。请从截断处直接继续输出剩余内容:无缝衔接上文(若截断处在句子、代码块或表格中间,从中断字符处接着写),不要重复任何已输出的内容,不要道歉或解释,不要重新开头或总结前文。"}):Ze&&C.push({role:"user",content:Ze});const R=Date.now();let z,Q=!1,H="";try{z=await i({baseUrl:e.baseUrl,apiKey:e.apiKey,model:Ce,supportsImages:e.imageCapabilities?.[Ce],messages:C.length?[...v,...C]:v,tools:Ge||g?void 0:Je,maxTokens:ft,extraBody:pt?{enable_thinking:!1}:void 0,reasoningEffort:e.reasoningEffort,stallTimeoutMs:Ie,client:e.client,sessionId:e.sessionId,aux:e.aux||void 0,continuation:!!dt,tailCount:C.length},{onContent:e=>{Q=!0,H+=e,j(M.feed(e))},onReasoning:t=>{!1!==e.showReasoning&&(dt||(we?we.onReasoning?.(t):(_||ye(X.gray("\n[思考] ")),ye(X.gray(t))),_=!0))},onReasoningEnd:()=>we?.onReasoningEnd?.(),onRetry:(t,n,o,s)=>{s?.network?$e(`${o.replace(/[::].*$/,"")},${Math.round(n/1e3)} 秒后自动重试(第 ${t}/${s.max} 次)`):e.debug&&$e(`[retry] 模型响应异常,${Math.round(n/1e3)}s后自动重试(第 ${t} 次):${o}`)}},O)}catch(n){if("AbortError"===n.name)return we?we.onInfo?.("已中断"):ye(X.gray("\n[已中断]\n")),{aborted:!0,usage:{prompt:Se,completion:xe},lastPromptTokens:ke,lastUsage:_e};if(!n.nonRetryable&&!n.partial&&ft>mt&&!ht&&!O?.aborted){ht=!0,gt=2,ft=mt,ee.delete(e.sessionId),e.debug&&$e(`[debug] 抬高输出预算后请求失败(${n.message}),已撤回到 ${mt} 重试`);continue}if(!n.nonRetryable&&!n.partial&&!je&&!O?.aborted&&isContextOverflowError(n.message)&&ve<3){ve++,$(e,n.message);const o=await h(t,e,{state:Oe,force:!0});if(o>0){Q&&H&&(dt+=H),e.debug&&$e(`[overflow] 上下文超出模型窗口,已强制压缩 ${o} 条较早消息后重试`);continue}e.debug&&$e("[overflow] 上下文超限但无可压缩内容,转兜底模型")}const o=l(n);if(!(o||n.nonRetryable||n.partial||n.network||je||Te||O?.aborted)){if(!Ee)try{const t=await a(e.baseUrl,e.apiKey);Ee=String(t&&t.fallback_model||"").trim()}catch{}if(Ee&&Ee!==Ce){Te=!0,Ce=Ee,Q&&H&&(dt+=H),e.debug&&$e(`[fallback] 主模型失败(${n.message}),静默切兜底模型 ${Ee}${Q?"(续写)":""}`);continue}}return we?we.onInfo?.("✗ 模型调用失败:"+n.message):ye("\n"+X.red("✗ 模型调用失败:")+n.message+"\n"),{error:n.message,errorKind:o,usage:{prompt:Se,completion:xe},lastPromptTokens:ke,lastUsage:_e}}j(M.flush()),z.usage&&(Se+=z.usage.prompt_tokens||0,xe+=z.usage.completion_tokens||0,ke=z.usage.prompt_tokens||ke,_e=z.usage,0===P&&we&&we.onUsage?.({lastPromptTokens:ke,prompt:Se,completion:xe,ctx:y({messages:t,compaction:Oe,config:e,lastPromptTokens:ke})})),e.debug&&$e(`[debug] 轮${d+1} · ${e.model} · in ${z.usage?.prompt_tokens||0} / out ${z.usage?.completion_tokens||0} tok · ${Date.now()-R}ms · ${(z.toolCalls||[]).length} 工具调用`);const{text:Y,sawMarkup:oe}=u(z.content),se=oe||M.sawMarkup;Ze=null;const re=je?[]:z.toolCalls;if(!re.length){if(se&&!Y.trim()&&!je){if(Qe<2){Qe++,2===Qe&&(Ge=!0),Ze=1===Qe?"你刚才的输出把工具调用写成了文本标记,工具并未被执行,该内容也不会展示给用户。请重新处理:若需调用工具,必须使用规范的函数调用(tool_calls)通道发起,绝不要以任何文本形式书写工具调用标记;若无需工具,请直接用中文输出面向用户的最终答复。":"请停止调用任何工具。基于目前已获得的全部信息,直接用中文输出面向用户的完整最终答复,不要输出任何工具调用或协议标记。",$e(`已拦截模型的文本化工具调用标记(工具未被执行),纠正重试中(第 ${Qe} 次)`);continue}$e("模型持续输出无效的工具调用标记,已全部拦截;本轮无有效答复(可换个说法重试或更换模型)")}if(!je&&"length"===z.finishReason&&Y&&it<3){it++,dt+=Y,e.debug&&$e(`[debug] 输出达单轮长度上限,自动续写(第 ${it} 段)`);continue}if(!je&&"length"===z.finishReason&&!Y.trim()&&!dt){if(gt<2&&ft<Z){gt++,ft=Math.min(3*ft,Z),te(e.sessionId,ft),e.debug&&$e(`[debug] 思考吃光输出预算(正文为空),抬高单轮上限到 ${ft} 重试(第 ${gt} 次)`);continue}if(!pt){pt=!0,Ze="你上一条把全部输出预算用在了思考上,没有输出任何正文,用户什么都没看到。请立刻直接输出面向用户的最终答复(结论、表格、结果放最前面),思考尽量精简。",e.debug&&$e("[debug] 抬高预算后仍无正文,改为要求先出正文(并best-effort关思考)");continue}}k&&!we&&ye("\n");let n=dt?dt+Y:Y;dt="",n&&t.push({role:"assistant",content:n});const o=!n.trim();if(lt&&!o&&ie(n)&&"length"!==z.finishReason&&!Ge&&at<2){at++,e.debug&&$e(`[debug] 检测到「宣告未落地」预告式收尾,注入续跑纠偏(第 ${at} 次)`),t.push({role:"user",content:'你上一条只是说明"接下来要做什么",但既没有发起工具调用、也没有给出结果——用户还没拿到任何最终产物。请立即执行你刚才所说的步骤(需要就用run_python/工具,不需要就直接算),并直接输出面向用户的完整最终结果(例如完整的按月汇总表与逐月核对表、差异结论),不要再只描述你打算做什么。'});continue}if(o&&!je&&"length"!==z.finishReason&&!Ge&&at<2){at++,e.debug&&$e(`[debug] 本回合正文为空,注入续跑纠偏(第 ${at} 次)`),Ze="你上一条回复没有输出任何面向用户的正文(内容可能全部留在了思考里),用户一个字都没拿到。请立刻用中文直接输出完整的最终答复,不要只在思考里推演。";continue}if(!je&&0===P&&!1!==e.codeSelfCheck&&et.size&&tt<1&&!Ge&&!o&&"length"!==z.finishReason){tt++;const t=await J(s,[...et],e);if(t&&t.issues.length){const e=t.issues.slice(0,12),n=e.map(e=>{const t=r.relative(s,e.path)||e.path;return` [${e.rule}] ${t}:${e.line}${e.unit?` 〔${e.unit}〕`:""}\n 问题:${e.msg}\n 改法:${e.hint||"按上面的说明处理"}`}).join("\n"),o=t.issues.length>e.length?`\n …… 另有 ${t.issues.length-e.length} 条,用code_inspect看全量。`:"";$e(`代码自检:本回合改动的 ${et.size} 个源码文件有 ${t.issues.length} 处必修问题,已要求修复`),Ze="【代码自检】你本回合改动的源码经体检发现以下**必修**问题,请逐条改完再结束:\n"+n+o+"\n\n改完用code_inspect(path) 复检确认必修清零。\n若确认某一条是误报,在那一行上方加 `// code-inspect-ignore: 规则名 简短理由`,并在回复里说明为什么判定它是误报——不要默默跳过,也不要在没修完时就宣布完成。";continue}}if(!je&&0===P&&!1!==e.webSelfCheck&&nt.size&&ot<1&&!Ge&&!o&&"length"!==z.finishReason){ot++;const t=await K(s,[...nt],e).catch(()=>null);if(t&&t.errors.length){$e(`网页自检:本回合改动的页面(${t.entries.join("、")})在浏览器里有 ${t.errors.length} 处必修问题,已要求修复`),Ze=L(t);continue}}if(!je&&0===P&&!1!==e.docSelfCheck&&st.size&&rt<1&&!Ge&&!o&&"length"!==z.finishReason){rt++;const t=await W(s,[...st],e).catch(()=>null);if(t&&t.issues.length){$e(`文档自检:本回合生成的 ${st.size} 份文档有 ${t.issues.length} 处必修问题,已要求修复`),Ze=B(t,s);continue}}if(o&&!se){const e=gt>0||pt?",自动抬高上限并重试后仍未产出":"";$e("length"===z.finishReason?`模型把本回合的输出预算(单轮上限 ${ft})全用在了思考上,没有产出正文${e}。建议把任务拆小一点分步做,或换个模型再试。`:`模型本回合没有返回任何正文${at>0?"(已自动重试)":""}。可以直接重发,或换个说法/换个模型再试。`)}const i=Pe?.pendingNote();if(i){if(!Ge&&Ne<2&&!O?.aborted){Ne++,Ze=i,$e(i);continue}n+="\n\n【未完成验证】"+i,"assistant"===t.at(-1)?.role?t.at(-1).content=n:t.push({role:"assistant",content:n}),$e("【未完成验证】"+i)}we&&we.onTurnEnd?.(n);const a=pe?.();if(a){t.push({role:"user",content:a});continue}if(yt){const o=A("Stop",{cwd:s,result:n,sessionId:e.sessionId||"",model:e.model||"",turn:t.filter(e=>"user"===e.role).length},bt);for(const e of o.outputs)$e("钩子·Stop:"+ge(e.stdout,160))}if(!je&&e.decisionAutoExtract&&!1!==e.decisionsEnabled&<&&!ct&&n&&n.trim())try{const o=[...t].reverse().find(e=>"user"===e.role),r=o&&"string"==typeof o.content?o.content:"",i=await E(e,{userText:r,assistantText:n,toolNames:[...ut]}),a="number"==typeof e.decisionExtractMinConfidence?e.decisionExtractMinConfidence:.55;if(i&&(i.confidence??0)>=a){const n=t.filter(e=>"user"===e.role).length,o=T(s,{...i,title:i.what,status:"draft",origin:"agent",confidence:Math.min(i.confidence??.5,.7),sessionId:e.sessionId||"",turn:n},{origin:"agent"});we?we.onDecision?we.onDecision(o):we.onInfo?.("已抽取决策草稿(待确认):"+ge(o.title,80)):ye(X.gray(" ◇ 已抽取决策草稿(待确认):"+ge(o.title,80))+"\n")}}catch{}return{usage:{prompt:Se,completion:xe},lastPromptTokens:ke,lastUsage:_e}}lt=!0,k&&!we&&ye("\n"),we&&Y&&we.onAssistantText?.(Y),t.push({role:"assistant",content:dt?dt+Y:Y,tool_calls:re,...z.nativeState?{huicai_anthropic:z.nativeState}:{}}),dt="";const me=re.map((t,n)=>{const o=t.function?.name||"";let s={},r=null,i=!1;const a=t.function?.arguments||"";if(a)try{s=JSON.parse(a)}catch(e){i=truncatedArgs(a,e,z.finishReason),r=i?`工具参数在第 ${String(a).length} 个字符处被截断:这次回复的输出触到了单次上限(约 ${ft} token),参数没写完——不是字符或转义的问题,原样重试还会在同一处断。常见成因是一段内容太长,或一条回复里并行发了好几个大参数的调用(上限是整条回复共用的)。请拆小:大参数的调用一次少发几个;write_file每段约 3000 字以内,后续段落用append=true追加;很长的JSON(测算假设、图表spec)先write_file落盘,再用对应的 *_path / spec_path参数传入。`:`工具参数不是合法JSON(${e.message})${jsonErrorContext(a,e)}请修正参数后重新调用。`}const l=Ue.get(o);if(l&&Le.has(o)&&!r){const t="function"==typeof l.disabledMessage?l.disabledMessage(e):l.disabledMessage;r=String(t||`工具 ${o} 当前已停用(开关已关闭),未执行。`)}const c=V.isActive()&&("write_file"===o||"edit_file"===o);return{tc:t,idx:n,name:o,args:s,argsError:r,argsTruncated:i,extra:l,readOnly:l?!!l.readOnly:m.has(o),deferToIde:c}});!je&&me.some(e=>e.argsTruncated)&><2&&ft<Z&&(gt++,ft=Math.min(3*ft,Z),te(e.sessionId,ft),e.debug&&$e(`[debug] 工具参数被单轮输出上限截断,抬高到 ${ft}`));const he=e=>{for(let n=e;n<me.length;n++)t.push({role:"tool",tool_call_id:me[n].tc.id||`call_${n}`,content:ce+" 该工具未执行,无任何结果,请勿臆测其输出。"});return we?we.onInfo?.("已中断"):ye(X.gray("\n[已中断]\n")),{aborted:!0,usage:{prompt:Se,completion:xe},lastPromptTokens:ke,lastUsage:_e}},Ae={cwd:s,config:e,signal:O,permissions:n,hooks:bt,hooksActive:yt,fileJournal:be,info:$e,shortLine:ge,engineering:Pe,turn:t.filter(e=>"user"===e.role).length,onStart:e=>e.nested?$e("程序内调用:"+f(e.name,e.args)):Ke(e),listTools:()=>De,resolveTool:e=>{const t=Ue.get(e);return t&&Le.has(e)?null:t?{readOnly:!!t.readOnly,extra:t}:ze.has(e)?{readOnly:m.has(e),extra:null}:null}},Re=e=>N({name:e.name,args:e.args,readOnly:e.readOnly,extra:e.extra,callId:e.tc?.id,deferToIde:e.deferToIde,argsError:e.argsError,item:e},Ae),Ke=e=>{ut.add(e.name),we?we.onToolStart?.(e.name,e.args,{callId:e.callId||e.tc?.id}):ye(X.cyan(" ⏺ ")+f(e.name,e.args)+"\n")},Fe=(e,n)=>{const o=e.item||e;ae(n)&&He++;const s=ne(e.name,e.args);s===Ve?Xe++:(Ve=s,Xe=1,Ye=0),we?we.onToolResult?.(e.name,String(n),{callId:o.tc?.id,failed:ae(n)}):ye(X.gray(" "+ge(n))+"\n"),t.push({role:"tool",tool_call_id:o.tc.id,content:String(n)})},We=(e,n)=>{const o=e.item||e;we?we.onSkip?.(e.name,e.args,{callId:o.tc?.id,reason:n}):ye(X.gray(` ⊘ ${f(e.name,e.args)} — 已跳过\n`)),t.push({role:"tool",tool_call_id:o.tc.id,content:n||"未授权,未执行。"})},Be=e=>"render_diagram"!==e,wt=e=>{try{return o.readFileSync(e,"utf8")}catch{return""}},$t=(e,t)=>{const n=r.resolve(s,String(e.args.path));if(U(n))return;const o=p(t,wt(n),{path:String(e.args.path)});o&&(D(n)&&et.add(n),F(n)&&nt.add(n),we?we.onFileDiff?.(o):ye(o+"\n"))};let St=0;for(;St<me.length;){if(O?.aborted)return he(St);const t=me[St];if(t.readOnly&&!t.argsError){let t=St;for(;t<me.length&&me[t].readOnly&&!me[t].argsError;)t++;const n=me.slice(St,t),o=await fe(n,toolConcurrency(e),e=>Re(e));for(const e of o)null!=e.denied?We(e,e.denied):Fe(e,e.result);if(St=t,O?.aborted)return he(St);continue}const n=await Re(t);if(O?.aborted&&null==n.denied&&!n.settled)return he(St);if(null==n.denied)if(n.settled)Ke(n),Fe(n,n.result),St++;else{if(Fe(n,n.result),null!=n.meta.before&&Be(n.name)&&$t(n,n.meta.before),q(n.name)&&/\.docx$/i.test(String(n.args?.path||""))){const e=r.resolve(s,String(n.args.path));o.existsSync(e)&&st.add(e)}St++}else We(n,n.denied),St++}He>=3&&!Ge&&!Ze&&(Ze="你刚才连续多次工具调用失败。请停下来重新审视:是不是文件路径/内容/前提假设错了?先用read_file / grep / list_files核实现状,再换一种更可靠的方法,绝不要重复同样会失败的调用。",He=0,$e("检测到连续工具失败,已提示智能体换一种更可靠的方法")),Ge||Ze||(Xe>=5&&Ye<2?(Ze="停止重复调用。你已经用完全相同的参数调用同一工具达 5 次,这不会产生任何新信息,只是在空转。现在必须二选一:① 基于已有结果直接给出结论/继续下一步;② 明确说明你缺少什么、为什么现有结果不够,然后换一种**明显不同**的方法去拿。",Ye=2,$e(`检测到同一调用连续重复 ${Xe} 次,已要求智能体停止空转`)):Xe>=3&&Ye<1&&(Ze="注意:你刚刚用**完全相同的参数**连续调用了同一个工具多次,结果不会因为再调一次而改变。请先仔细重读上一次的返回内容——答案很可能已经在里面;若确实需要更多信息,请换不同的参数或换一个工具,不要重复同一次调用。",Ye=1,$e(`检测到同一调用连续重复 ${Xe} 次,已提示智能体重读上次结果`)));const xt=pe?.();xt&&t.push({role:"user",content:xt})}return dt&&(t.push({role:"assistant",content:dt}),we&&we.onTurnEnd?.(dt)),we?we.onInfo?.("已达最大工具轮数,停止"):ye(X.yellow("\n[已达最大工具轮数,停止]\n")),{usage:{prompt:Se,completion:xe},lastPromptTokens:ke,lastUsage:_e}}
|
package/src/assets-import.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"node:fs";import s from"node:path";import
|
|
1
|
+
import e from"node:fs";import{accountDataDir as s}from"./account-scope.js";import t from"node:path";import i from"node:os";import{parseFrontmatter as n,countUserSkills as r}from"./skills.js";import{checkLimit as o,currentPlan as c,remainingOf as l}from"./plan-limits.js";export const EXTERNAL_SOURCES=[{cli:"Claude Code",agents:".claude/agents",skills:".claude/skills"},{cli:"Codex",agents:".codex/agents",skills:".codex/skills"},{cli:"Gemini CLI",agents:".gemini/agents",skills:".gemini/skills"},{cli:"Cursor",agents:".cursor/agents",skills:".cursor/skills"}];const a=new Set([".md",".txt",".py",".js",".ts",".sh",".json",".yaml",".yml",".csv",".sql",".toml",".ini"]),m=/^[a-zA-Z0-9_-]{1,64}$/;function d(){return t.join(s(),"agents")}function p(){return t.join(s(),"skills")}function u(){try{return new Set(e.readdirSync(d()).filter(e=>e.endsWith(".md")).map(e=>e.replace(/\.md$/,"")))}catch{return new Set}}export function discoverExternalAssets({base:s=i.homedir()}={}){const r=u(),o=function(){try{return new Set(e.readdirSync(p(),{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name))}catch{return new Set}}(),c=[],l=[],a=new Set,d=new Set;for(const i of EXTERNAL_SOURCES){const p=t.join(s,i.agents);let u=[];try{u=e.readdirSync(p).filter(e=>e.endsWith(".md"))}catch{}for(const s of u){const o=t.join(p,s);let l;try{l=n(e.readFileSync(o,"utf8"))}catch{continue}const d=String(l.name||s.replace(/\.md$/,"")).trim();m.test(d)&&!a.has(d)&&(a.add(d),c.push({name:d,description:(l.description||"").slice(0,200),cli:i.cli,src:o,exists:r.has(d)}))}const f=t.join(s,i.skills);let k=[];try{k=e.readdirSync(f,{withFileTypes:!0}).filter(e=>e.isDirectory())}catch{}for(const s of k){const r=s.name.trim(),c=t.join(f,r),a=t.join(c,"SKILL.md");if(!m.test(r)||d.has(r)||!e.existsSync(a))continue;d.add(r);let p="";try{p=(n(e.readFileSync(a,"utf8")).description||"").slice(0,200)}catch{}l.push({name:r,description:p,cli:i.cli,src:c,exists:o.has(r)})}}return{agents:c,skills:l}}function f(s,i,n=0){if(n>3)return 0;let r=0,o=[];try{o=e.readdirSync(s,{withFileTypes:!0})}catch{return 0}e.mkdirSync(i,{recursive:!0});for(const c of o){const o=t.join(s,c.name),l=t.join(i,c.name);if(c.isDirectory())r+=f(o,l,n+1);else if(c.isFile()&&("SKILL.md"===c.name||a.has(t.extname(c.name).toLowerCase())))try{e.copyFileSync(o,l),r++}catch{}}return r}export function importExternalAssets(s={},{overwrite:i=!1}={}){const n=[],a=[],k={agents:{imported:0,skipped:0},skills:{imported:0,skipped:0},details:n,limited:a},y=d(),h=p(),g=c(),S=u().size,x=r();let j=l("agents",S,g),$=l("skills",x,g);const w=(e,s)=>{const t=o(e,s,g)?.message;t&&!a.includes(t)&&a.push(t)};for(const r of s.agents||[]){if(!r||!m.test(String(r.name))||!r.src)continue;const s=t.join(y,r.name+".md"),o=e.existsSync(s);if(i||!o)if(!o&&j<=0)w("agents",S+k.agents.imported),k.agents.skipped++,n.push(`跳过agent(已达会员上限):${r.name}`);else try{e.mkdirSync(y,{recursive:!0}),e.copyFileSync(r.src,s),k.agents.imported++,o||j--,n.push(`导入agent:${r.name}`)}catch(e){n.push(`失败agent ${r.name}:${e.message}`)}else k.agents.skipped++,n.push(`跳过agent(已存在):${r.name}`)}for(const r of s.skills||[]){if(!r||!m.test(String(r.name))||!r.src)continue;const s=t.join(h,r.name),o=e.existsSync(t.join(s,"SKILL.md"));if(!i&&e.existsSync(s)){k.skills.skipped++,n.push(`跳过skill(已存在):${r.name}`);continue}if(!o&&$<=0){w("skills",x+k.skills.imported),k.skills.skipped++,n.push(`跳过skill(已达会员上限):${r.name}`);continue}const c=f(r.src,s);c>0?(k.skills.imported++,o||$--,n.push(`导入skill:${r.name}(${c} 文件)`)):(k.skills.skipped++,n.push(`跳过skill(无内容):${r.name}`))}return k}export function importAllExternalAssets(e={}){const s=discoverExternalAssets(e);return importExternalAssets({agents:s.agents.filter(e=>!e.exists),skills:s.skills.filter(e=>!e.exists)},e)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"node:fs";import t from"node:path";import r from"node:os";import o from"node:crypto";import{CONFIG_DIR as n}from"./config.js";import{accountSnapshot as i,withAccountOperation as a}from"./account-scope.js";import{readScheduleState as s,editScheduleState as c,scheduleFile as f}from"./schedule.js";export function automationDeviceId(){const r=t.join(n,"automation-device-id");e.mkdirSync(n,{recursive:!0});try{e.writeFileSync(r,o.randomUUID(),{flag:"wx",mode:384})}catch(e){if("EEXIST"!==e.code)throw e}const i=e.readFileSync(r,"utf8").trim();if(!/^[\w-]{8,80}$/.test(i))throw new Error("自动化终端标识损坏");return i}const d=e=>Array.isArray(e)?e.map(d):e&&"object"==typeof e?Object.fromEntries(Object.keys(e).sort().map(t=>[t,d(e[t])])):e,w=new Map;export async function syncAutomations({fetchFn:t=fetch}={}){const o=i();if(!o.scope)throw new Error("请先登录并完成用户身份校验");const n=o.scope+":"+o.config.apiKey;if(w.has(n))return w.get(n);const a=async function(t,o){const n=f(),i=automationDeviceId(),a=()=>{if(!t.current())throw new Error("登录状态已改变,请重试")},w=async e=>{a();const r=await o(t.config.baseUrl+"/v1/automations?deviceId="+encodeURIComponent(i),{method:e?"POST":"GET",headers:{Authorization:"Bearer "+t.config.apiKey,"Content-Type":"application/json"},...e?{body:JSON.stringify(e)}:{},signal:AbortSignal.timeout(15e3)}),n=await r.json();if(a(),!r.ok||200!==n.code)throw new Error(n.message||"自动化同步失败,请重试");if(!Array.isArray(n.data?.tasks)||!Number.isSafeInteger(n.data.revision))throw new Error("自动化同步响应格式错误");return n.data};let u=await w();a();const m=s(n);if(m.dirty){const t=(e=>e.map(({owner:e,user:t,...r})=>r))(m.tasks);if(l=t,h=u.tasks,JSON.stringify(d(l))!==JSON.stringify(d(h))){if(u.revision!==m.revision){const t=n+".conflict-"+Date.now()+".json";throw c(r=>{e.writeFileSync(t,JSON.stringify(r,null,2),{mode:384}),Object.assign(r,{tasks:y(u.tasks),revision:u.revision,dirty:!1,generation:r.generation+1})},n),new Error("控制台已更新任务,已采用云端版本。未同步的本地修改保存在:"+t)}u=await w({deviceId:i,deviceName:r.hostname(),revision:u.revision,tasks:t})}}var l,h;return a(),c(e=>{const t=e.generation!==m.generation;e.revision=Math.max(e.revision,u.revision),t||(e.tasks=y(u.tasks),e.dirty=!1)},n),s(n).tasks;function y(e){const r=t.config.user;return e.map(e=>({...e,owner:t.scope,user:String(r.id??r.userId??r.username)}))}}(o,t).finally(()=>w.delete(n));return w.set(n,a),a}export async function runClaimedAutomation(e,t,{manual:r=!1,slot:o=Math.floor(Date.now()/6e4),signal:n,fetchFn:d=fetch,heartbeatMs:w=3e4}={}){const u=i();if(!u.current()||e?.owner!==u.scope)throw new Error("任务不属于当前登录用户");const m=()=>{if(!u.current())throw new Error("登录状态已改变")},l=automationDeviceId(),h=async(t,r)=>{m();const o=await d(u.config.baseUrl+"/v1/automations/"+t,{method:"POST",headers:{Authorization:"Bearer "+u.config.apiKey,"Content-Type":"application/json"},body:JSON.stringify({deviceId:l,taskId:e.id,...r}),signal:AbortSignal.timeout(15e3)}),n=await o.json();if(m(),!o.ok||200!==n.code)throw new Error(n.message||"自动化领取失败");return n.data};if(await syncAutomations({fetchFn:d}),m(),n?.aborted)throw new Error("任务已取消");const y=f(),g=s(y);if(g.dirty)throw new Error("任务有尚未同步的修改,请重试");const p=g.tasks.find(t=>t.id===e.id);if(!p||!1===p.enabled)throw new Error("任务已删除或暂停");if(!r){const{isDue:e}=await import("./schedule-freq.js");if(!e(p,new Date(6e4*o)))throw new Error("任务尚未到执行时间")}const v=await h("claim",{revision:g.revision,manual:r,slot:o}),k=v?.token;if(!k||!v.document||v.task?.id!==e.id)throw new Error("领取响应格式错误");const b=new AbortController,E=()=>b.abort(new Error("任务已取消或执行租约失效"));n?.addEventListener("abort",E,{once:!0});let I,S,j,A=!1,D=Date.now()+12e4;try{if(m(),c(e=>{if(e.generation!==g.generation)throw new Error("领取期间任务已修改,请重试");const t=String(u.config.user.id??u.config.user.userId??u.config.user.username);e.tasks=v.document.tasks.map(e=>({...e,owner:u.scope,user:t})),e.revision=v.document.revision,e.dirty=!1,e.generation++},y),n?.aborted&&E(),S=setInterval(()=>{(!u.current()||Date.now()>=D)&&E()},100),I=setInterval(async()=>{if(!A&&!b.signal.aborted){A=!0;try{await h("heartbeat",{token:k})?D=Date.now()+12e4:E()}catch{E()}finally{A=!1}}},w),b.signal.aborted)throw b.signal.reason;const e={...v.task,owner:u.scope,user:p.user};return j=await a(u,()=>(m(),t(e,b.signal)))||{ok:!0},b.signal.aborted&&(j={...j,ok:!1,error:"任务已取消或执行租约失效"}),j}catch(e){throw j={ok:!1,error:e.message},e}finally{if(clearInterval(I),clearInterval(S),n?.removeEventListener("abort",E),u.current()){if(!await h("complete",{token:k,ok:!0===j?.ok,output:j?.output||j?.text||j?.error||""})&&j?.ok)throw new Error("运行已结束,但租约失效,结果未写入控制台")}}}
|
package/src/codeindex.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"node:fs";import t from"node:path";import n from"node:zlib";import s from"node:crypto";import{execFileSync as r}from"node:child_process";import{CONFIG_DIR as o}from"./config.js";import{embed as c}from"./api.js";export const IGNORE=new Set([".git","node_modules","dist","build","target","out",".next",".nuxt",".venv","venv","__pycache__",".idea",".gradle",".mvn","coverage",".cache","vendor","bower_components","Pods","DerivedData",".turbo",".parcel-cache",".terraform",".pytest_cache",".mypy_cache",".tox",".angular",".svelte-kit",".output",".serverless",".svn",".hg"]);const i=new Set([".js",".mjs",".cjs",".ts",".tsx",".jsx",".java",".kt",".py",".go",".rs",".rb",".php",".c",".h",".cpp",".hpp",".cs",".swift",".scala",".sql",".vue",".svelte",".sh",".css",".scss",".html",".md",".yaml",".yml",".json",".toml",".xml",".properties",".gradle",".groovy",".proto",".graphql",".gql",".tf",".ini",".cfg",".conf",".dart",".lua",".clj",".ex",".exs",".erl",".hs",".jl",".pl",".pm",".r",".dockerfile"]),a=/(?:\.min\.(?:js|css)$)|(?:\.bundle\.js$)|(?:\.map$)|(?:(?:^|\/)(?:package-lock\.json|pnpm-lock\.yaml|npm-shrinkwrap\.json)$)/i;export const MAX_FILE_BYTES=524288;const l=t.join(o,"cache","index"),u=new Map;export function tokenize(e){const t=String(e),n=t.replace(/([a-z0-9])([A-Z])/g,"$1 $2").toLowerCase().split(/[^a-z0-9_]+/).filter(e=>e.length>=2&&e.length<=40),s=t.match(/[一-鿿]/g);if(s)for(const e of s)n.push(e);const r=t.match(/[一-鿿]{2,}/g);if(r)for(const e of r)for(let t=0;t<e.length-1;t++)n.push(e.slice(t,t+2));return n}function*f(n,s=0,r=14){let o;try{o=e.readdirSync(n,{withFileTypes:!0})}catch{return}for(const e of o){if(IGNORE.has(e.name)||e.name.startsWith("."))continue;const o=t.join(n,e.name);e.isDirectory()?s<r&&(yield*f(o,s+1,r)):e.isFile()&&i.has(t.extname(e.name).toLowerCase())&&(yield o)}}let p=null;export function ignoreGlobs(){return[...IGNORE].flatMap(e=>["-g",`!**/${e}/**`,"-g",`!${e}/**`])}export function listSourceFiles(e){let n=null;if(function(){if(null!==p)return p;try{r("rg",["--version"],{stdio:"ignore"}),p=!0}catch{p=!1}return p}())try{n=r("rg",["--files",...ignoreGlobs()],{cwd:e,encoding:"utf8",maxBuffer:64<<20,stdio:["ignore","pipe","ignore"]}).split("\n").map(e=>e.replace(/\\/g,"/")).filter(Boolean)}catch{n=null}if(!n){n=[];for(const s of f(e))n.push(t.relative(e,s).split(t.sep).join("/"))}const s=[];for(const r of n)i.has(t.extname(r).toLowerCase())&&(a.test(r)||s.push({rel:r,abs:t.join(e,r)}));return s}const h=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".java",".kt",".py",".go",".rs",".rb",".php",".c",".h",".cpp",".hpp",".cs",".swift",".scala",".vue",".svelte",".dart",".lua",".clj",".ex",".exs",".erl",".hs",".jl",".groovy"]),d=/(?:^|\/)(?:tests?|__tests__|spec|specs|fixtures?|mocks?|__mocks__|generated|gen|snapshots?|migrations?|node_modules|vendor)(?:\/|$)/i;export function indexPriority(e){const n=e.toLowerCase();let s=0;return h.has(t.extname(n))?s-=100:s+=25,d.test(n)&&(s+=60),s+=2*(n.split("/").length-1),s}const m=new Set([".js",".mjs",".cjs",".ts",".tsx",".jsx",".java",".kt",".go",".rs",".c",".h",".cpp",".hpp",".cc",".cs",".swift",".scala",".php",".dart",".groovy"]);const g=/^(?:(?:export|default|public|private|protected|internal|static|final|abstract|sealed|open|override|async|suspend|inline|const|unsafe|extern|virtual|partial|pub|data|companion)\s+)*(?:function|func|fn|def|fun|class|interface|enum|record|struct|trait|impl|module|mod|namespace|protocol|extension|object|actor|type)\b(?:\s+([A-Za-z_]\w*))?/,y=/^(?:@[\w.]+(?:\([^)]*\))?\s+)*(?:(?:public|private|protected|internal|static|final|abstract|synchronized|native|override|open|suspend|inline|virtual|sealed|async|unsafe|extern)\s+)+[\w<>\[\],.?&:*\s]*?\b([A-Za-z_]\w*)\s*\([^;{]*\)[^;]*?(?:\{|$)/,x=/^(?!(?:if|for|while|switch|catch|return|do|else|try|with|function|class|new|await|yield|throw|case|default|synchronized)\b)(?:static\s+|async\s+|get\s+|set\s+|readonly\s+|public\s+|private\s+|protected\s+|\*\s*)*([A-Za-z_$][\w$]*)\s*\([^;]*\)\s*(?::\s*[\w<>\[\].|&, ]+)?\s*\{/,w=/^(?:export\s+)?(?:default\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?(?:function\b|\([^)]*\)\s*(?::\s*[\w<>\[\].|&, ]+)?\s*=>|[A-Za-z_$][\w$]*\s*=>)/,b=/^(?:async\s+)?(?:def|class)\s+([A-Za-z_]\w*)/,z=/^(?:def|class|module)\s+([A-Za-z_][\w:]*)/;function j(e,t){if("py"===e){const e=b.exec(t);return e?{name:e[1]}:null}if("ruby"===e){const e=z.exec(t);return e?{name:e[1]}:null}let n=g.exec(t);return n?{name:n[1]||""}:(n=y.exec(t),n?{name:n[1]}:(n=x.exec(t),n?{name:n[1]}:(n=w.exec(t),n?{name:n[1]}:null)))}function v(e,t=0){const n=[];for(let s=0;s<e.length;s+=40){const r=Math.min(e.length,s+50),o=e.slice(s,r).join("\n"),c=tokenize(o);if(c.length){const e=new Map;for(const t of c)e.set(t,(e.get(t)||0)+1);n.push({start:t+s+1,end:t+r,len:c.length,text:o,tf:[...e]})}if(r>=e.length)break}return n}function k(e,t){const n=function(e){return m.has(e)?"brace":".py"===e?"py":".rb"===e?"ruby":null}(e);if(!n)return null;const s=[];for(let e=0;e<t.length;e++){const r=t[e].trim();if(!r)continue;const o=j(n,r);o&&s.push({line:e,sym:o.name||""})}if(!s.length)return null;for(let e=0;e<s.length;e++){let n=s[e].line;const r=e>0?s[e-1].line+1:0;for(;n>r&&/^@[\w.]/.test(t[n-1].trim());)n--;s[e].start=n}const r=[];s[0].start>0&&r.push({start:0,sym:""});for(const e of s)r.push({start:e.start,sym:e.sym});const o=[];for(let e=0;e<r.length;e++){const n=r[e].start,s=e+1<r.length?r[e+1].start:t.length;if(s<=n)continue;const c=t.slice(n,s);if(c.length>160){const t=v(c,n);t.length&&r[e].sym&&(t[0].sym=r[e].sym),o.push(...t);continue}const i=c.join("\n"),a=tokenize(i);if(!a.length)continue;const l=new Map;for(const e of a)l.set(e,(l.get(e)||0)+1);const u={start:n+1,end:s,len:a.length,text:i,tf:[...l]};r[e].sym&&(u.sym=r[e].sym),o.push(u)}return o.length?o:null}export function chunkText(e,n=""){const s=e.split("\n"),r=n?t.extname(n).toLowerCase():"";return r&&k(r,s)||v(s,0)}export function buildIndexCore(t,n){const s=Date.now()+2500,r=listSourceFiles(t).map(t=>{let n=1/0,s=0;try{const r=e.statSync(t.abs);n=r.size,s=r.mtimeMs}catch{}return{...t,size:n,mtimeMs:s,prio:indexPriority(t.rel)}}).filter(e=>e.size<=524288).sort((e,t)=>e.prio-t.prio||e.size-t.size),o=new Set(r.map(e=>e.rel)),c=[],i=new Map,a={};let l=0,u=0,f=0,p=0,h=!1;for(const t of r){if(c.length>=12e3||Date.now()>s){h=!0;break}const r=n&&n[t.rel];let o;if(r&&r.mtimeMs===t.mtimeMs&&r.size===t.size&&Array.isArray(r.chunks))o=r.chunks,f++;else{let n;try{const s=e.readFileSync(t.abs);if(s.includes(0))continue;n=s.toString("utf8")}catch{continue}o=chunkText(n,t.rel),p++}u++,a[t.rel]={mtimeMs:t.mtimeMs,size:t.size,chunks:o};const d=new Set(tokenize(t.rel));for(const e of o){const n=new Map(e.tf);for(const[t]of e.tf)i.set(t,(i.get(t)||0)+1);c.push({file:t.rel,start:e.start,end:e.end,len:e.len,text:e.text,tf:n,pathTokens:d,sym:e.sym}),l+=e.len}}if(h&&n)for(const e in n)!a[e]&&o.has(e)&&(a[e]=n[e]);const d=n?Object.keys(n).length:-1,m=p>0||Object.keys(a).length!==d;return{index:{chunks:c,df:i,N:c.length,avgLen:c.length?l/c.length:1,truncated:h,indexedFiles:u,totalFiles:r.length,reused:f,rechunked:p},filesMap:a,changed:m}}export function buildIndex(e){return buildIndexCore(e,null).index}function _(e){const n=s.createHash("sha1").update(t.resolve(e)).digest("hex").slice(0,16);return t.join(l,n+".json.gz")}function S(s,r){try{e.mkdirSync(l,{recursive:!0});const o=_(s),c=n.gzipSync(JSON.stringify({v:2,cwd:t.resolve(s),at:Date.now(),files:r}));e.writeFileSync(o+".tmp",c),e.renameSync(o+".tmp",o),function(){try{const n=e.readdirSync(l).filter(e=>e.endsWith(".json.gz"));if(n.length<=24)return;const s=n.map(n=>{try{return{f:n,t:e.statSync(t.join(l,n)).mtimeMs}}catch{return null}}).filter(Boolean).sort((e,t)=>e.t-t.t);for(const{f:n}of s.slice(0,s.length-24))try{e.unlinkSync(t.join(l,n))}catch{}}catch{}}()}catch{}}export function getIndex(s){const r=u.get(s);if(r&&Date.now()-r.builtAt<3e5)return r.index;const{index:o,filesMap:c,changed:i}=buildIndexCore(s,function(s){try{const r=_(s),o=e.statSync(r);if(Date.now()-o.mtimeMs>12096e5)return null;const c=JSON.parse(n.gunzipSync(e.readFileSync(r)).toString("utf8"));if(!c||2!==c.v||c.cwd!==t.resolve(s)||!c.files)return null;const i=new Date;try{e.utimesSync(r,i,i)}catch{}return c.files}catch{return null}}(s));return i&&S(s,c),u.set(s,{index:o,builtAt:Date.now()}),o}export function invalidate(e){u.delete(e)}export function clearIndexCache(){u.clear();try{e.rmSync(l,{recursive:!0,force:!0})}catch{}}export function search(e,t,n=8){const s=[...new Set(tokenize(t))];if(!s.length||!e.N)return[];const{chunks:r,df:o,N:c,avgLen:i}=e,a=new Map;for(const e of s){const t=o.get(e)||1;a.set(e,Math.log(1+(c-t+.5)/(t+.5)))}const l=[];for(const e of r){let t=0;for(const n of s){const s=e.tf.get(n);s&&(t+=a.get(n)*(2.5*s/(s+1.5*(.25+e.len/i*.75))))}if(t>0){if(e.pathTokens)for(const n of s)e.pathTokens.has(n)&&(t+=.6*a.get(n));l.push({file:e.file,start:e.start,end:e.end,text:e.text,score:t,sym:e.sym})}}return l.sort((e,t)=>t.score-e.score),l.slice(0,n)}export function cosine(e,t){if(!e||!t)return 0;let n=0,s=0,r=0;const o=Math.min(e.length,t.length);for(let c=0;c<o;c++)n+=e[c]*t[c],s+=e[c]*e[c],r+=t[c]*t[c];return s&&r?n/(Math.sqrt(s)*Math.sqrt(r)):0}export async function searchCode(e,t,n){const s=String(t?.query||"").trim();if(!s)return"缺少query(要检索的概念/功能描述)";const r=Number.isFinite(t?.k)&&t.k>0?Math.min(t.k,20):8,o=getIndex(e);if(!o.N)return"(当前目录未索引到代码文件)";const i=!!(n&&n.embeddingModel&&n.baseUrl&&n.apiKey);let a=search(o,s,i?Math.min(30,Math.max(3*r,15)):r);if(!a.length)return`(未找到与「${s}」明显相关的代码;可换关键词,或用grep精确匹配)`;let l=!1;if(i&&a.length>1)try{const e=a.map(e=>e.text.slice(0,2e3)),t=await c(n.baseUrl,n.apiKey,n.embeddingModel,[s,...e]);if(t&&t.length===e.length+1&&t[0]){const e=t[0];a=a.map((n,s)=>({...n,score:cosine(e,t[s+1])})).sort((e,t)=>t.score-e.score),l=!0}}catch{}let u=l?"(向量语义重排)\n":"";return o.truncated&&(u+=`(大型仓库:已索引最相关的约 ${o.indexedFiles}/${o.totalFiles} 个文件;如需穷尽匹配请配合grep)\n`),u+a.slice(0,r).map(e=>{const t=e.text.length>1200?e.text.slice(0,1200)+"\n…(本块过长已截断)":e.text;return`${`${e.file}:${e.start}-${e.end}${e.sym?` ▸ ${e.sym}`:""}`}\n${t}`}).join("\n\n─────\n\n")}function M(e){const t=new Set,n=[/(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_]\w*)/g,/\bclass\s+([A-Za-z_]\w*)/g,/\bdef\s+([A-Za-z_]\w*)/g,/\b(?:export\s+)?const\s+([A-Za-z_]\w*)\s*=\s*(?:async\s*)?(?:function|\([^)]*\)\s*=>)/g,/@(?:Get|Post|Put|Delete|Request)Mapping\(\s*"([^"]+)"/g,/\bpublic\s+[\w<>[\]]+\s+([A-Za-z_]\w*)\s*\(/g,/\b(?:export\s+)?(?:interface|type|enum)\s+([A-Za-z_]\w*)/g,/\b(?:func|fn|fun)\s+([A-Za-z_]\w*)/g,/\b(?:struct|trait|impl)\s+([A-Za-z_]\w*)/g];for(const s of n){let n;for(;(n=s.exec(e))&&t.size<12;)t.add(n[1])}return[...t]}export function buildRepoMap(t,{maxFiles:n=40,maxChars:s=2500}={}){const r=[];for(const n of listSourceFiles(t))try{const t=e.statSync(n.abs);if(t.size>524288)continue;r.push({abs:n.abs,size:t.size,rel:n.rel,prio:indexPriority(n.rel)})}catch{}if(!r.length)return"";r.sort((e,t)=>e.prio-t.prio||t.size-e.size);const o=[];let c=0;for(const t of r.slice(0,n)){let n=[];try{n=M(e.readFileSync(t.abs,"utf8").slice(0,4e4))}catch{}const r=`- ${t.rel}${n.length?":"+n.slice(0,8).join(", "):""}`;if(c+r.length>s)break;o.push(r),c+=r.length+1}return o.join("\n")}
|
|
1
|
+
import e from"node:fs";import t from"node:path";import n from"node:zlib";import s from"node:crypto";import{execFileSync as r}from"node:child_process";import{accountDataDir as o}from"./account-scope.js";import{embed as c}from"./api.js";export const IGNORE=new Set([".git","node_modules","dist","build","target","out",".next",".nuxt",".venv","venv","__pycache__",".idea",".gradle",".mvn","coverage",".cache","vendor","bower_components","Pods","DerivedData",".turbo",".parcel-cache",".terraform",".pytest_cache",".mypy_cache",".tox",".angular",".svelte-kit",".output",".serverless",".svn",".hg"]);const i=new Set([".js",".mjs",".cjs",".ts",".tsx",".jsx",".java",".kt",".py",".go",".rs",".rb",".php",".c",".h",".cpp",".hpp",".cs",".swift",".scala",".sql",".vue",".svelte",".sh",".css",".scss",".html",".md",".yaml",".yml",".json",".toml",".xml",".properties",".gradle",".groovy",".proto",".graphql",".gql",".tf",".ini",".cfg",".conf",".dart",".lua",".clj",".ex",".exs",".erl",".hs",".jl",".pl",".pm",".r",".dockerfile"]),a=/(?:\.min\.(?:js|css)$)|(?:\.bundle\.js$)|(?:\.map$)|(?:(?:^|\/)(?:package-lock\.json|pnpm-lock\.yaml|npm-shrinkwrap\.json)$)/i;export const MAX_FILE_BYTES=524288;const l=()=>t.join(o(),"cache","index"),u=new Map;export function tokenize(e){const t=String(e),n=t.replace(/([a-z0-9])([A-Z])/g,"$1 $2").toLowerCase().split(/[^a-z0-9_]+/).filter(e=>e.length>=2&&e.length<=40),s=t.match(/[一-鿿]/g);if(s)for(const e of s)n.push(e);const r=t.match(/[一-鿿]{2,}/g);if(r)for(const e of r)for(let t=0;t<e.length-1;t++)n.push(e.slice(t,t+2));return n}function*f(n,s=0,r=14){let o;try{o=e.readdirSync(n,{withFileTypes:!0})}catch{return}for(const e of o){if(IGNORE.has(e.name)||e.name.startsWith("."))continue;const o=t.join(n,e.name);e.isDirectory()?s<r&&(yield*f(o,s+1,r)):e.isFile()&&i.has(t.extname(e.name).toLowerCase())&&(yield o)}}let p=null;export function ignoreGlobs(){return[...IGNORE].flatMap(e=>["-g",`!**/${e}/**`,"-g",`!${e}/**`])}export function listSourceFiles(e){let n=null;if(function(){if(null!==p)return p;try{r("rg",["--version"],{stdio:"ignore"}),p=!0}catch{p=!1}return p}())try{n=r("rg",["--files",...ignoreGlobs()],{cwd:e,encoding:"utf8",maxBuffer:64<<20,stdio:["ignore","pipe","ignore"]}).split("\n").map(e=>e.replace(/\\/g,"/")).filter(Boolean)}catch{n=null}if(!n){n=[];for(const s of f(e))n.push(t.relative(e,s).split(t.sep).join("/"))}const s=[];for(const r of n)i.has(t.extname(r).toLowerCase())&&(a.test(r)||s.push({rel:r,abs:t.join(e,r)}));return s}const h=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".java",".kt",".py",".go",".rs",".rb",".php",".c",".h",".cpp",".hpp",".cs",".swift",".scala",".vue",".svelte",".dart",".lua",".clj",".ex",".exs",".erl",".hs",".jl",".groovy"]),d=/(?:^|\/)(?:tests?|__tests__|spec|specs|fixtures?|mocks?|__mocks__|generated|gen|snapshots?|migrations?|node_modules|vendor)(?:\/|$)/i;export function indexPriority(e){const n=e.toLowerCase();let s=0;return h.has(t.extname(n))?s-=100:s+=25,d.test(n)&&(s+=60),s+=2*(n.split("/").length-1),s}const m=new Set([".js",".mjs",".cjs",".ts",".tsx",".jsx",".java",".kt",".go",".rs",".c",".h",".cpp",".hpp",".cc",".cs",".swift",".scala",".php",".dart",".groovy"]);const g=/^(?:(?:export|default|public|private|protected|internal|static|final|abstract|sealed|open|override|async|suspend|inline|const|unsafe|extern|virtual|partial|pub|data|companion)\s+)*(?:function|func|fn|def|fun|class|interface|enum|record|struct|trait|impl|module|mod|namespace|protocol|extension|object|actor|type)\b(?:\s+([A-Za-z_]\w*))?/,y=/^(?:@[\w.]+(?:\([^)]*\))?\s+)*(?:(?:public|private|protected|internal|static|final|abstract|synchronized|native|override|open|suspend|inline|virtual|sealed|async|unsafe|extern)\s+)+[\w<>\[\],.?&:*\s]*?\b([A-Za-z_]\w*)\s*\([^;{]*\)[^;]*?(?:\{|$)/,x=/^(?!(?:if|for|while|switch|catch|return|do|else|try|with|function|class|new|await|yield|throw|case|default|synchronized)\b)(?:static\s+|async\s+|get\s+|set\s+|readonly\s+|public\s+|private\s+|protected\s+|\*\s*)*([A-Za-z_$][\w$]*)\s*\([^;]*\)\s*(?::\s*[\w<>\[\].|&, ]+)?\s*\{/,w=/^(?:export\s+)?(?:default\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?(?:function\b|\([^)]*\)\s*(?::\s*[\w<>\[\].|&, ]+)?\s*=>|[A-Za-z_$][\w$]*\s*=>)/,b=/^(?:async\s+)?(?:def|class)\s+([A-Za-z_]\w*)/,z=/^(?:def|class|module)\s+([A-Za-z_][\w:]*)/;function j(e,t){if("py"===e){const e=b.exec(t);return e?{name:e[1]}:null}if("ruby"===e){const e=z.exec(t);return e?{name:e[1]}:null}let n=g.exec(t);return n?{name:n[1]||""}:(n=y.exec(t),n?{name:n[1]}:(n=x.exec(t),n?{name:n[1]}:(n=w.exec(t),n?{name:n[1]}:null)))}function v(e,t=0){const n=[];for(let s=0;s<e.length;s+=40){const r=Math.min(e.length,s+50),o=e.slice(s,r).join("\n"),c=tokenize(o);if(c.length){const e=new Map;for(const t of c)e.set(t,(e.get(t)||0)+1);n.push({start:t+s+1,end:t+r,len:c.length,text:o,tf:[...e]})}if(r>=e.length)break}return n}function k(e,t){const n=function(e){return m.has(e)?"brace":".py"===e?"py":".rb"===e?"ruby":null}(e);if(!n)return null;const s=[];for(let e=0;e<t.length;e++){const r=t[e].trim();if(!r)continue;const o=j(n,r);o&&s.push({line:e,sym:o.name||""})}if(!s.length)return null;for(let e=0;e<s.length;e++){let n=s[e].line;const r=e>0?s[e-1].line+1:0;for(;n>r&&/^@[\w.]/.test(t[n-1].trim());)n--;s[e].start=n}const r=[];s[0].start>0&&r.push({start:0,sym:""});for(const e of s)r.push({start:e.start,sym:e.sym});const o=[];for(let e=0;e<r.length;e++){const n=r[e].start,s=e+1<r.length?r[e+1].start:t.length;if(s<=n)continue;const c=t.slice(n,s);if(c.length>160){const t=v(c,n);t.length&&r[e].sym&&(t[0].sym=r[e].sym),o.push(...t);continue}const i=c.join("\n"),a=tokenize(i);if(!a.length)continue;const l=new Map;for(const e of a)l.set(e,(l.get(e)||0)+1);const u={start:n+1,end:s,len:a.length,text:i,tf:[...l]};r[e].sym&&(u.sym=r[e].sym),o.push(u)}return o.length?o:null}export function chunkText(e,n=""){const s=e.split("\n"),r=n?t.extname(n).toLowerCase():"";return r&&k(r,s)||v(s,0)}export function buildIndexCore(t,n){const s=Date.now()+2500,r=listSourceFiles(t).map(t=>{let n=1/0,s=0;try{const r=e.statSync(t.abs);n=r.size,s=r.mtimeMs}catch{}return{...t,size:n,mtimeMs:s,prio:indexPriority(t.rel)}}).filter(e=>e.size<=524288).sort((e,t)=>e.prio-t.prio||e.size-t.size),o=new Set(r.map(e=>e.rel)),c=[],i=new Map,a={};let l=0,u=0,f=0,p=0,h=!1;for(const t of r){if(c.length>=12e3||Date.now()>s){h=!0;break}const r=n&&n[t.rel];let o;if(r&&r.mtimeMs===t.mtimeMs&&r.size===t.size&&Array.isArray(r.chunks))o=r.chunks,f++;else{let n;try{const s=e.readFileSync(t.abs);if(s.includes(0))continue;n=s.toString("utf8")}catch{continue}o=chunkText(n,t.rel),p++}u++,a[t.rel]={mtimeMs:t.mtimeMs,size:t.size,chunks:o};const d=new Set(tokenize(t.rel));for(const e of o){const n=new Map(e.tf);for(const[t]of e.tf)i.set(t,(i.get(t)||0)+1);c.push({file:t.rel,start:e.start,end:e.end,len:e.len,text:e.text,tf:n,pathTokens:d,sym:e.sym}),l+=e.len}}if(h&&n)for(const e in n)!a[e]&&o.has(e)&&(a[e]=n[e]);const d=n?Object.keys(n).length:-1,m=p>0||Object.keys(a).length!==d;return{index:{chunks:c,df:i,N:c.length,avgLen:c.length?l/c.length:1,truncated:h,indexedFiles:u,totalFiles:r.length,reused:f,rechunked:p},filesMap:a,changed:m}}export function buildIndex(e){return buildIndexCore(e,null).index}function _(e){const n=s.createHash("sha1").update(t.resolve(e)).digest("hex").slice(0,16);return t.join(l(),n+".json.gz")}function S(s,r){try{e.mkdirSync(l(),{recursive:!0});const o=_(s),c=n.gzipSync(JSON.stringify({v:2,cwd:t.resolve(s),at:Date.now(),files:r}));e.writeFileSync(o+".tmp",c),e.renameSync(o+".tmp",o),function(){try{const n=e.readdirSync(l()).filter(e=>e.endsWith(".json.gz"));if(n.length<=24)return;const s=n.map(n=>{try{return{f:n,t:e.statSync(t.join(l(),n)).mtimeMs}}catch{return null}}).filter(Boolean).sort((e,t)=>e.t-t.t);for(const{f:n}of s.slice(0,s.length-24))try{e.unlinkSync(t.join(l(),n))}catch{}}catch{}}()}catch{}}export function getIndex(s){const r=u.get(o()+":"+s);if(r&&Date.now()-r.builtAt<3e5)return r.index;const{index:c,filesMap:i,changed:a}=buildIndexCore(s,function(s){try{const r=_(s),o=e.statSync(r);if(Date.now()-o.mtimeMs>12096e5)return null;const c=JSON.parse(n.gunzipSync(e.readFileSync(r)).toString("utf8"));if(!c||2!==c.v||c.cwd!==t.resolve(s)||!c.files)return null;const i=new Date;try{e.utimesSync(r,i,i)}catch{}return c.files}catch{return null}}(s));return a&&S(s,i),u.set(o()+":"+s,{index:c,builtAt:Date.now()}),c}export function invalidate(e){u.delete(o()+":"+e)}export function clearIndexCache(){u.clear();try{e.rmSync(l(),{recursive:!0,force:!0})}catch{}}export function search(e,t,n=8){const s=[...new Set(tokenize(t))];if(!s.length||!e.N)return[];const{chunks:r,df:o,N:c,avgLen:i}=e,a=new Map;for(const e of s){const t=o.get(e)||1;a.set(e,Math.log(1+(c-t+.5)/(t+.5)))}const l=[];for(const e of r){let t=0;for(const n of s){const s=e.tf.get(n);s&&(t+=a.get(n)*(2.5*s/(s+1.5*(.25+e.len/i*.75))))}if(t>0){if(e.pathTokens)for(const n of s)e.pathTokens.has(n)&&(t+=.6*a.get(n));l.push({file:e.file,start:e.start,end:e.end,text:e.text,score:t,sym:e.sym})}}return l.sort((e,t)=>t.score-e.score),l.slice(0,n)}export function cosine(e,t){if(!e||!t)return 0;let n=0,s=0,r=0;const o=Math.min(e.length,t.length);for(let c=0;c<o;c++)n+=e[c]*t[c],s+=e[c]*e[c],r+=t[c]*t[c];return s&&r?n/(Math.sqrt(s)*Math.sqrt(r)):0}export async function searchCode(e,t,n){const s=String(t?.query||"").trim();if(!s)return"缺少query(要检索的概念/功能描述)";const r=Number.isFinite(t?.k)&&t.k>0?Math.min(t.k,20):8,o=getIndex(e);if(!o.N)return"(当前目录未索引到代码文件)";const i=!!(n&&n.embeddingModel&&n.baseUrl&&n.apiKey);let a=search(o,s,i?Math.min(30,Math.max(3*r,15)):r);if(!a.length)return`(未找到与「${s}」明显相关的代码;可换关键词,或用grep精确匹配)`;let l=!1;if(i&&a.length>1)try{const e=a.map(e=>e.text.slice(0,2e3)),t=await c(n.baseUrl,n.apiKey,n.embeddingModel,[s,...e]);if(t&&t.length===e.length+1&&t[0]){const e=t[0];a=a.map((n,s)=>({...n,score:cosine(e,t[s+1])})).sort((e,t)=>t.score-e.score),l=!0}}catch{}let u=l?"(向量语义重排)\n":"";return o.truncated&&(u+=`(大型仓库:已索引最相关的约 ${o.indexedFiles}/${o.totalFiles} 个文件;如需穷尽匹配请配合grep)\n`),u+a.slice(0,r).map(e=>{const t=e.text.length>1200?e.text.slice(0,1200)+"\n…(本块过长已截断)":e.text;return`${`${e.file}:${e.start}-${e.end}${e.sym?` ▸ ${e.sym}`:""}`}\n${t}`}).join("\n\n─────\n\n")}function M(e){const t=new Set,n=[/(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_]\w*)/g,/\bclass\s+([A-Za-z_]\w*)/g,/\bdef\s+([A-Za-z_]\w*)/g,/\b(?:export\s+)?const\s+([A-Za-z_]\w*)\s*=\s*(?:async\s*)?(?:function|\([^)]*\)\s*=>)/g,/@(?:Get|Post|Put|Delete|Request)Mapping\(\s*"([^"]+)"/g,/\bpublic\s+[\w<>[\]]+\s+([A-Za-z_]\w*)\s*\(/g,/\b(?:export\s+)?(?:interface|type|enum)\s+([A-Za-z_]\w*)/g,/\b(?:func|fn|fun)\s+([A-Za-z_]\w*)/g,/\b(?:struct|trait|impl)\s+([A-Za-z_]\w*)/g];for(const s of n){let n;for(;(n=s.exec(e))&&t.size<12;)t.add(n[1])}return[...t]}export function buildRepoMap(t,{maxFiles:n=40,maxChars:s=2500}={}){const r=[];for(const n of listSourceFiles(t))try{const t=e.statSync(n.abs);if(t.size>524288)continue;r.push({abs:n.abs,size:t.size,rel:n.rel,prio:indexPriority(n.rel)})}catch{}if(!r.length)return"";r.sort((e,t)=>e.prio-t.prio||t.size-e.size);const o=[];let c=0;for(const t of r.slice(0,n)){let n=[];try{n=M(e.readFileSync(t.abs,"utf8").slice(0,4e4))}catch{}const r=`- ${t.rel}${n.length?":"+n.slice(0,8).join(", "):""}`;if(c+r.length>s)break;o.push(r),c+=r.length+1}return o.join("\n")}
|