@epoch-agent/core 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- import { existsSync, statSync, readdirSync, readFileSync, mkdirSync, writeFileSync, rmSync, symlinkSync, cpSync, renameSync, mkdtempSync, realpathSync, copyFileSync, unlinkSync, openSync, writeSync, closeSync, readSync, lstatSync } from 'fs';
1
+ import { existsSync, statSync, readdirSync, readFileSync, mkdirSync, writeFileSync, renameSync, rmSync, symlinkSync, cpSync, mkdtempSync, realpathSync, copyFileSync, unlinkSync, openSync, writeSync, closeSync, readSync, lstatSync } from 'fs';
2
2
  import { homedir, platform, tmpdir, hostname } from 'os';
3
- import { join, dirname, resolve, isAbsolute, relative, sep, extname, basename } from 'path';
3
+ import { join, dirname, resolve, relative, isAbsolute, sep, extname, basename } from 'path';
4
4
  import { execFile, execSync, execFileSync, spawn } from 'child_process';
5
- import { createLogger, dbPath, resolveHomeDir, DEFAULT_MAX_TURNS, t, unknownKeyIssues, parseLenient, issueDetails, splitWords, checkpointsDir, trustedImportsPath, budgetStatePath, artifactsDir, UNLIMITED_TURNS, turnLimitOf, configPath, managedSettingsPath, projectSettingsPath, projectLocalSettingsPath, getSecretValues, listStoredSecretNames, maskApiKey, setSectionField, isInWorkspace, workspacesPath, openDatabase, migrate, releaseDatabase, automationLogsDir, projectPoliciesDir, projectComplianceDir, projectHooksPath, projectClaudeSettingsPath, projectClaudeLocalSettingsPath, projectSkillsDir, trustPath, detectBackend, EXEC_PATH_AS_NODE_ENV, isolate, createStreamDecoder, parseShellCommand, writeArtifact, envPath, checkDangerousCommand, checkObfuscation, countCodePoints, keepHeadAndTail, normalizeForMatch, isReadOnlyCommand, CODE_EXEC_ROOTS } from '@epoch-agent/infra';
5
+ import { createLogger, dbPath, resolveHomeDir, DEFAULT_MAX_TURNS, t, openDatabase, migrate, releaseDatabase, unknownKeyIssues, parseLenient, issueDetails, splitWords, checkpointsDir, trustedImportsPath, budgetStatePath, artifactsDir, UNLIMITED_TURNS, turnLimitOf, configPath, managedSettingsPath, projectSettingsPath, projectLocalSettingsPath, getSecretValues, listStoredSecretNames, maskApiKey, setSectionField, isInWorkspace, workspacesPath, automationLogsDir, projectPoliciesDir, projectComplianceDir, projectHooksPath, projectClaudeSettingsPath, projectClaudeLocalSettingsPath, projectSkillsDir, trustPath, detectBackend, EXEC_PATH_AS_NODE_ENV, isolate, createStreamDecoder, parseShellCommand, writeArtifact, envPath, checkDangerousCommand, checkObfuscation, countCodePoints, keepHeadAndTail, normalizeForMatch, isReadOnlyCommand, CODE_EXEC_ROOTS, headCodePoints, tailCodePoints } from '@epoch-agent/infra';
6
6
  export { IS_WINDOWS, WINDOWS_HIDE_FLAGS, buildBwrapArgs, buildProfile, checkDangerousCommand, checkObfuscation, createLogger, detectBackend, getDefaultShell, getPythonCommand, isInWorkspace, isReadOnlyCommand, isolate, maskApiKey, probeBubblewrap, probeSeatbelt, complianceDir as resolveComplianceDir, policiesDir as resolvePoliciesDir, setLogLevel } from '@epoch-agent/infra';
7
- import { PROVIDER_TYPES, OPERATION_TYPES as OPERATION_TYPES$1, COMPLIANCE_CATEGORIES, COMPLIANCE_ACTIONS, SEARCH_PROVIDER_TYPES, LANGS, PERMISSION_LEVELS, SHELL_KINDS, ToolExposure, MAX_OPTIONS, MIN_OPTIONS, MAX_HEADER_CHARS, MAX_QUESTIONS, SCHEDULE_INTERVAL_MINUTES, isPermissionLevel, SCHEDULE_DRAFT_TOOL, isValidAgentRoleName, DEFAULT_AGENT_ROLE, isValidCommandName, RESERVED_COMMAND_NAMES, parseModelRef, systemNote, totalUsageTokens, NOOP_TELEMETRY, METRIC, contentToText, GEN_AI, promptTokens, apiKeyEnvVar, PROVIDER_INFOS, isProviderType, API_KEY_ENV_VARS, KEY_ACTIONS, tableOf, perAction, isKeyAction, parseSequence, RESERVED_CHORDS, chordId, sequenceId, ACTION_CONTEXTS, SEQUENCE_CONTEXTS, assertNeverPart, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_SESSION_REFS, utf8ByteLength, attachSessionSurface, isContextNearlyFull, isOperationType, normalizeApproval, MAX_ATTACH_BYTES, SIMULTANEOUS_CONTEXTS, SESSION_REFERENCE_ERROR_CODES, filePartSummary, sessionPartSummary } from '@epoch-agent/protocol';
7
+ import { PROVIDER_TYPES, OPERATION_TYPES as OPERATION_TYPES$1, COMPLIANCE_CATEGORIES, COMPLIANCE_ACTIONS, SEARCH_PROVIDER_TYPES, LANGS, PERMISSION_LEVELS, SHELL_KINDS, ToolExposure, MAX_OPTIONS, MIN_OPTIONS, MAX_HEADER_CHARS, MAX_QUESTIONS, SCHEDULE_INTERVAL_MINUTES, isPermissionLevel, SCHEDULE_DRAFT_TOOL, isValidAgentRoleName, DEFAULT_AGENT_ROLE, isValidCommandName, RESERVED_COMMAND_NAMES, parseModelRef, systemNote, totalUsageTokens, NOOP_TELEMETRY, METRIC, contentToText, GEN_AI, isPromptOversize, promptTokens, apiKeyEnvVar, PROVIDER_INFOS, isProviderType, API_KEY_ENV_VARS, KEY_ACTIONS, tableOf, perAction, isKeyAction, parseSequence, RESERVED_CHORDS, chordId, sequenceId, ACTION_CONTEXTS, SEQUENCE_CONTEXTS, assertNeverPart, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_SESSION_REFS, utf8ByteLength, attachSessionSurface, isContextNearlyFull, isOperationType, normalizeApproval, MAX_ATTACH_BYTES, SIMULTANEOUS_CONTEXTS, SESSION_REFERENCE_ERROR_CODES, filePartSummary, sessionPartSummary } from '@epoch-agent/protocol';
8
8
  export { API_KEY_ENV_VARS, COMPLIANCE_ACTIONS, COMPLIANCE_CATEGORIES, MAX_ATTACH_BYTES, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_SESSION_ATTACH_BYTES, MAX_SESSION_REFS, OPERATION_TYPES, PERMISSION_LEVELS, PLAN_OUTCOMES, PROVIDER_INFOS, PROVIDER_TYPES, ToolExposure, apiKeyEnvVar, extractAllMentions, extractMentions, extractSessionMentions, getProviderInfo, isOperationType, isPermissionLevel, isPlanOutcome, isProviderType, normalizeApproval } from '@epoch-agent/protocol';
9
9
  import { createRequire } from 'module';
10
10
  import { randomUUID, createHash, randomBytes } from 'crypto';
@@ -561,8 +561,9 @@ function mapOperation(tool) {
561
561
  if (tool.operation && isOperationType(tool.operation)) return tool.operation;
562
562
  return fromAnnotations(tool.annotations) ?? mapOperationType(tool.name);
563
563
  }
564
+ var MCP_TOOL_PREFIX = "mcp__";
564
565
  function isOpenWorldTool(tool) {
565
- return tool.annotations?.openWorldHint === true;
566
+ return tool.annotations?.openWorldHint === true || tool.name.startsWith(MCP_TOOL_PREFIX);
566
567
  }
567
568
  // src/code-mode/readonly.ts
568
569
  var BLOCKED_OPERATIONS = /* @__PURE__ */ new Set(["command", "code_exec"]);
@@ -628,7 +629,7 @@ var ToolRegistry = class {
628
629
  this.addTool(tool, source);
629
630
  }
630
631
  registerTools(tools, source = "builtin") {
631
- for (const t76 of tools) this.addTool(t76, source);
632
+ for (const t78 of tools) this.addTool(t78, source);
632
633
  }
633
634
  addTool(tool, source) {
634
635
  const existing = this.tools.get(tool.name);
@@ -677,14 +678,14 @@ var ToolRegistry = class {
677
678
  return isCodeModeDeferred(tool) ? ToolExposure.Deferred : declared;
678
679
  }
679
680
  exposed() {
680
- return this.list().filter((t76) => this.exposureOf(t76) === ToolExposure.Direct);
681
+ return this.list().filter((t78) => this.exposureOf(t78) === ToolExposure.Direct);
681
682
  }
682
683
  byExposure(exposure) {
683
- return this.list().filter((t76) => this.exposureOf(t76) === exposure);
684
+ return this.list().filter((t78) => this.exposureOf(t78) === exposure);
684
685
  }
685
686
  codeModeDeclared() {
686
687
  if (!this.codeMode) return [];
687
- return this.list().filter((t76) => isCodeModeDeferred(t76));
688
+ return this.list().filter((t78) => isCodeModeDeferred(t78));
688
689
  }
689
690
  sourceOf(name) {
690
691
  return this.tools.get(name)?.source;
@@ -733,7 +734,7 @@ var ToolRegistry = class {
733
734
  function staticToolProvider(tools) {
734
735
  return {
735
736
  list: () => tools,
736
- get: (name) => tools.find((t76) => t76.name === name)
737
+ get: (name) => tools.find((t78) => t78.name === name)
737
738
  };
738
739
  }
739
740
  var log = createLogger("tokenizer");
@@ -1145,7 +1146,7 @@ var DelegateManager = class {
1145
1146
  }
1146
1147
  normalizeTasks(args) {
1147
1148
  if (args.tasks && args.tasks.length > 0) {
1148
- return args.role ? args.tasks.map((t76) => ({ ...t76, role: t76.role ?? args.role })) : args.tasks;
1149
+ return args.role ? args.tasks.map((t78) => ({ ...t78, role: t78.role ?? args.role })) : args.tasks;
1149
1150
  }
1150
1151
  if (args.goal) return [{ goal: args.goal, ...args.role ? { role: args.role } : {} }];
1151
1152
  return [];
@@ -1155,7 +1156,7 @@ var DelegateManager = class {
1155
1156
  for (let i = 0; i < tasks.length; i += maxConcurrent) {
1156
1157
  const batch = tasks.slice(i, i + maxConcurrent);
1157
1158
  const batchResults = await Promise.allSettled(
1158
- batch.map((t76) => this.runSingleTask(t76, parentSessionId))
1159
+ batch.map((t78) => this.runSingleTask(t78, parentSessionId))
1159
1160
  );
1160
1161
  for (const r of batchResults) {
1161
1162
  if (r.status === "fulfilled") {
@@ -1296,21 +1297,21 @@ function createDelegateTool(runner, config, roles) {
1296
1297
  operation: "command",
1297
1298
  describeTarget: (args) => {
1298
1299
  const tasks = Array.isArray(args["tasks"]) ? args["tasks"] : [];
1299
- const goals = tasks.map((t76) => String(t76["goal"] ?? ""));
1300
+ const goals = tasks.map((t78) => String(t78["goal"] ?? ""));
1300
1301
  const single = typeof args["goal"] === "string" ? args["goal"] : "";
1301
1302
  return [...goals, single].filter(Boolean).join(" | ");
1302
1303
  },
1303
1304
  async execute(args, ctx) {
1304
1305
  const start = Date.now();
1305
1306
  const rawTasks = Array.isArray(args["tasks"]) ? args["tasks"] : void 0;
1306
- const tasks = rawTasks?.map((t76) => {
1307
- const o = t76;
1307
+ const tasks = rawTasks?.map((t78) => {
1308
+ const o = t78;
1308
1309
  return {
1309
1310
  goal: String(o["goal"] ?? "").trim(),
1310
1311
  context: String(o["context"] ?? ""),
1311
1312
  ...typeof o["role"] === "string" && o["role"].trim() ? { role: o["role"].trim() } : {}
1312
1313
  };
1313
- }).filter((t76) => t76.goal.length > 0);
1314
+ }).filter((t78) => t78.goal.length > 0);
1314
1315
  const goal = typeof args["goal"] === "string" ? args["goal"].trim() : "";
1315
1316
  const topRole = typeof args["role"] === "string" ? args["role"].trim() : "";
1316
1317
  if ((!tasks || tasks.length === 0) && !goal) {
@@ -1326,7 +1327,7 @@ function createDelegateTool(runner, config, roles) {
1326
1327
  };
1327
1328
  }
1328
1329
  const known = listRoles();
1329
- const requested = [...(tasks ?? []).map((t76) => t76.role), topRole].filter(
1330
+ const requested = [...(tasks ?? []).map((t78) => t78.role), topRole].filter(
1330
1331
  (r) => Boolean(r)
1331
1332
  );
1332
1333
  const unknown = [...new Set(requested)].filter((r) => !known.some((k) => k.name === r));
@@ -1498,9 +1499,9 @@ function createGoalTool(deps) {
1498
1499
  };
1499
1500
  }
1500
1501
  var VALID_TARGETS = ["memory", "user"];
1501
- var VALID_ACTIONS = ["add", "replace", "remove", "search", "budget"];
1502
- function isValidTarget(t76) {
1503
- return typeof t76 === "string" && VALID_TARGETS.includes(t76);
1502
+ var VALID_ACTIONS = ["add", "replace", "remove", "search", "read", "budget"];
1503
+ function isValidTarget(t78) {
1504
+ return typeof t78 === "string" && VALID_TARGETS.includes(t78);
1504
1505
  }
1505
1506
  function isValidAction(a) {
1506
1507
  return VALID_ACTIONS.includes(a);
@@ -1521,9 +1522,10 @@ function createMemoryTool(memory) {
1521
1522
  name: "memory",
1522
1523
  description: [
1523
1524
  "\u8BFB\u5199\u957F\u671F\u8BB0\u5FC6\u3002\u8DE8\u4F1A\u8BDD\u6301\u4E45\u5316\uFF0C\u7528\u4E8E\u8BB0\u4F4F\u7528\u6237\u504F\u597D\u3001\u9879\u76EE\u7EA6\u5B9A\u3001\u8E29\u8FC7\u7684\u5751\u3002",
1524
- "action=add \u65B0\u589E\u6761\u76EE\uFF1Breplace \u66FF\u6362\u5DF2\u6709\u6761\u76EE\uFF1Bremove \u5220\u9664\uFF1Bsearch \u68C0\u7D22\uFF1Bbudget \u67E5\u770B\u914D\u989D\u3002",
1525
+ "action=add \u65B0\u589E\u6761\u76EE\uFF1Breplace \u66FF\u6362\u5DF2\u6709\u6761\u76EE\uFF1Bremove \u5220\u9664\uFF1Bsearch \u68C0\u7D22\uFF1Bread \u6309 id \u53D6\u5168\u6587\uFF1Bbudget \u67E5\u770B\u914D\u989D\u3002",
1525
1526
  'target=user \u5B58"\u7528\u6237\u662F\u8C01/\u504F\u597D\u4EC0\u4E48"\uFF1Btarget=memory \u5B58"\u6211\u9700\u8981\u8BB0\u4F4F\u7684\u9879\u76EE\u77E5\u8BC6"\u3002',
1526
- "\u53EA\u8BB0\u5F55\u975E\u663E\u800C\u6613\u89C1\u3001\u4EE5\u540E\u8FD8\u4F1A\u7528\u5230\u7684\u4FE1\u606F\u2014\u2014\u4E0D\u8981\u8BB0\u5F55\u80FD\u4ECE\u4EE3\u7801\u6216 git \u5386\u53F2\u8BFB\u51FA\u6765\u7684\u5185\u5BB9\u3002"
1527
+ "\u53EA\u8BB0\u5F55\u975E\u663E\u800C\u6613\u89C1\u3001\u4EE5\u540E\u8FD8\u4F1A\u7528\u5230\u7684\u4FE1\u606F\u2014\u2014\u4E0D\u8981\u8BB0\u5F55\u80FD\u4ECE\u4EE3\u7801\u6216 git \u5386\u53F2\u8BFB\u51FA\u6765\u7684\u5185\u5BB9\u3002",
1528
+ "system prompt \u91CC\u7684 MEMORY INDEX \u53EA\u6709\u6BCF\u6761\u7684\u9996\u884C\u6458\u8981\uFF1B\u8981\u5168\u6587\u7528 read\uFF0C\u627E\u4E0D\u5230\u7684\u7528 search\u3002"
1527
1529
  ].join("\n"),
1528
1530
  parameters: {
1529
1531
  type: "object",
@@ -1531,7 +1533,7 @@ function createMemoryTool(memory) {
1531
1533
  action: {
1532
1534
  type: "string",
1533
1535
  description: "\u64CD\u4F5C\u7C7B\u578B",
1534
- enum: ["add", "replace", "remove", "search", "budget"]
1536
+ enum: ["add", "replace", "remove", "search", "read", "budget"]
1535
1537
  },
1536
1538
  target: {
1537
1539
  type: "string",
@@ -1544,7 +1546,11 @@ function createMemoryTool(memory) {
1544
1546
  },
1545
1547
  old_text: {
1546
1548
  type: "string",
1547
- description: "replace/remove \u8981\u5B9A\u4F4D\u7684\u539F\u6761\u76EE\u5185\u5BB9\uFF08\u9700\u5B8C\u6574\u5339\u914D\uFF09\u3002"
1549
+ description: "replace/remove \u5B9A\u4F4D\u6761\u76EE\u7684**\u5168\u6587**\uFF08\u9700\u5B8C\u5168\u76F8\u540C\uFF09\u3002\u624B\u4E0A\u53EA\u6709\u7D22\u5F15\u65F6\u7528 id\uFF0C\u522B\u7528\u8FD9\u4E2A\u2014\u2014\u7D22\u5F15\u91CC\u7684\u6458\u8981\u662F\u622A\u65AD\u7684\uFF0C\u5339\u914D\u4E0D\u4E0A\u3002"
1550
+ },
1551
+ id: {
1552
+ type: "string",
1553
+ description: "replace/remove \u8981\u5B9A\u4F4D\u7684\u6761\u76EE id\uFF0C\u5F62\u5982 m:a1b2c3\uFF08\u6765\u81EA MEMORY INDEX \u6216 search\uFF09\u3002\u4E0E old_text \u4E8C\u9009\u4E00\uFF0C\u4F18\u5148\u7528\u5B83\u3002"
1548
1554
  },
1549
1555
  query: {
1550
1556
  type: "string",
@@ -1553,6 +1559,11 @@ function createMemoryTool(memory) {
1553
1559
  limit: {
1554
1560
  type: "number",
1555
1561
  description: "search \u8FD4\u56DE\u6761\u6570\u4E0A\u9650\uFF0C\u9ED8\u8BA4 5\u3002"
1562
+ },
1563
+ ids: {
1564
+ type: "array",
1565
+ items: { type: "string" },
1566
+ description: "read \u8981\u53D6\u7684\u6761\u76EE id\uFF0C\u5F62\u5982 m:a1b2c3 / u:0b22e9\uFF08\u6765\u81EA MEMORY INDEX \u6216 search \u7ED3\u679C\uFF09\u3002"
1556
1567
  }
1557
1568
  },
1558
1569
  required: ["action"]
@@ -1590,7 +1601,21 @@ function createMemoryTool(memory) {
1590
1601
  const limit = typeof args["limit"] === "number" ? args["limit"] : 5;
1591
1602
  const results = await memory.search(query, limit);
1592
1603
  if (results.length === 0) return ok("(\u65E0\u5339\u914D\u8BB0\u5FC6)", start);
1593
- return ok(results.map((r) => `[${r.target}] ${r.content}`).join("\n"), start);
1604
+ return ok(results.map((r) => `${r.id} [${r.target}] ${r.content}`).join("\n"), start);
1605
+ }
1606
+ if (action === "read") {
1607
+ const raw = args["ids"];
1608
+ const ids = Array.isArray(raw) ? raw.map((v) => String(v)) : [];
1609
+ if (ids.length === 0) return fail("read \u9700\u8981\u975E\u7A7A ids \u6570\u7EC4", start);
1610
+ const { found, missing } = await memory.read(ids);
1611
+ const lines = found.map((f) => `${f.id} [${f.target}]
1612
+ ${f.content}`);
1613
+ if (missing.length > 0) {
1614
+ lines.push(
1615
+ `(\u8BA4\u4E0D\u51FA\u7684 id: ${missing.join(", ")} \u2014\u2014 \u5B83\u4EEC\u4E0D\u5728\u5F53\u524D\u8BB0\u5FC6\u5E93\u91CC\uFF0C\u7528 search \u91CD\u65B0\u627E)`
1616
+ );
1617
+ }
1618
+ return ok(lines.join("\n\n"), start);
1594
1619
  }
1595
1620
  const target = args["target"];
1596
1621
  if (!isValidTarget(target)) {
@@ -1605,18 +1630,24 @@ function createMemoryTool(memory) {
1605
1630
  return fail(r.error ?? "\u5199\u5165\u5931\u8D25", start);
1606
1631
  }
1607
1632
  if (action === "replace") {
1608
- const oldText2 = String(args["old_text"] ?? "").trim();
1633
+ const ref2 = String(args["id"] ?? args["old_text"] ?? "").trim();
1609
1634
  const content = String(args["content"] ?? "").trim();
1610
- if (!oldText2) return fail("replace \u9700\u8981 old_text", start);
1635
+ if (!ref2) return fail("replace \u9700\u8981 id \u6216 old_text", start);
1611
1636
  if (!content) return fail("replace \u9700\u8981\u975E\u7A7A content", start);
1612
- const r = await memory.replace(target, oldText2, content);
1637
+ const r = await memory.replace(target, ref2, content);
1613
1638
  if (r.success) return ok(`\u5DF2\u66FF\u6362 ${target} \u8BB0\u5FC6\u6761\u76EE`, start);
1614
1639
  if (r.consolidateHint) return ok(r.consolidateHint, start);
1615
1640
  return fail(r.error ?? "\u66FF\u6362\u5931\u8D25", start);
1616
1641
  }
1617
- const oldText = String(args["old_text"] ?? "").trim();
1618
- if (!oldText) return fail("remove \u9700\u8981 old_text", start);
1619
- await memory.remove(target, oldText);
1642
+ const ref = String(args["id"] ?? args["old_text"] ?? "").trim();
1643
+ if (!ref) return fail("remove \u9700\u8981 id \u6216 old_text", start);
1644
+ const done2 = await memory.remove(target, ref);
1645
+ if (!done2) {
1646
+ return fail(
1647
+ `${target} \u91CC\u6CA1\u6709\u5339\u914D "${ref}" \u7684\u6761\u76EE\u3002\u7528 MEMORY INDEX \u91CC\u7684 id\uFF08\u5982 id="m:a1b2c3"\uFF09\uFF0C\u6216\u5148 memory(action="search") \u627E\u4E00\u904D \u2014\u2014 old_text \u8981\u6C42\u5168\u6587\u5B8C\u5168\u76F8\u540C\uFF0C\u7D22\u5F15\u91CC\u90A3\u884C\u662F\u622A\u65AD\u8FC7\u7684\u3002`,
1648
+ start
1649
+ );
1650
+ }
1620
1651
  return ok(`\u5DF2\u5220\u9664 ${target} \u8BB0\u5FC6\u6761\u76EE`, start);
1621
1652
  } catch (err) {
1622
1653
  return fail(err instanceof Error ? err.message : String(err), start, 4002);
@@ -2398,6 +2429,57 @@ var SCHEDULE_DEFAULT_ALLOWLIST = {
2398
2429
  allowOperations: ["file_read"],
2399
2430
  allowRules: []
2400
2431
  };
2432
+ // src/schedule/unattended-lint.ts
2433
+ var ANCHORS = [
2434
+ { kind: "asks-human", zh: /问(?:一下|下)?我/, en: /\bask\s+(?:me|us)\b/i },
2435
+ {
2436
+ kind: "asks-human",
2437
+ zh: /(?:告诉我|让我知道)[^\n]{0,8}(?:要不要|是否|该不该|需不需要)/,
2438
+ en: /\blet\s+(?:me|us)\s+know\s+(?:if|whether|before)\b/i
2439
+ },
2440
+ {
2441
+ kind: "asks-human",
2442
+ zh: /(?:请(?:先)?确认|(?:跟|向|和)我确认|征求我)/,
2443
+ en: /\b(?:confirm|check)\s+with\s+(?:me|us)\b/i
2444
+ },
2445
+ {
2446
+ kind: "asks-human",
2447
+ zh: /等(?:我|用户)[^\n]{0,6}(?:确认|回复|答复|决定|批)/,
2448
+ en: /\bwait\s+for\s+(?:my|your|user)\w*\s+(?:confirmation|approval|reply|response)\b/i
2449
+ },
2450
+ {
2451
+ kind: "asks-human",
2452
+ zh: /(?:不确定|拿不准|有疑问)(?:的话|时|就|再)?[^\n]{0,6}(?:问|确认一下|找我)/,
2453
+ en: /\bif\s+(?:you'?re\s+)?(?:unsure|not\s+sure|in\s+doubt)\b[^\n]{0,24}\bask\b/i
2454
+ },
2455
+ {
2456
+ kind: "needs-context",
2457
+ zh: /(?:刚才|上面(?:那|说|提到|的))/,
2458
+ en: /\b(?:the\s+\w+\s+above|we\s+just\s+(?:did|said|discussed))\b/i
2459
+ },
2460
+ {
2461
+ kind: "needs-context",
2462
+ zh: /我们(?:之前|刚|前面|上次)[^\n]{0,4}(?:说|聊|讨论|定|提)/,
2463
+ en: /\bas\s+(?:we\s+)?(?:discussed|agreed|mentioned)\b/i
2464
+ },
2465
+ {
2466
+ kind: "needs-context",
2467
+ zh: /(?:按|照|接着|继续)上(?:次|一次|轮|回)/,
2468
+ en: /\b(?:like|same\s+as|per)\s+last\s+time\b/i
2469
+ }
2470
+ ];
2471
+ function lintUnattendedPrompt(prompt) {
2472
+ const out = [];
2473
+ const seen = /* @__PURE__ */ new Set();
2474
+ for (const anchor of ANCHORS) {
2475
+ if (seen.has(anchor.kind)) continue;
2476
+ const hit = anchor.zh.exec(prompt) ?? anchor.en.exec(prompt);
2477
+ if (!hit) continue;
2478
+ seen.add(anchor.kind);
2479
+ out.push({ kind: anchor.kind, matched: hit[0] });
2480
+ }
2481
+ return out;
2482
+ }
2401
2483
  // src/permission/rule-syntax.ts
2402
2484
  var ESCAPABLE = /* @__PURE__ */ new Set(["(", ")", "\\"]);
2403
2485
  function isEscaped(s, index) {
@@ -2524,8 +2606,8 @@ function resolvePathPattern(content, workDir) {
2524
2606
  const head = wildcardAt === -1 ? raw : raw.slice(0, wildcardAt);
2525
2607
  const tail = wildcardAt === -1 ? "" : raw.slice(wildcardAt);
2526
2608
  const resolvedHead = base === "" && isAbsolute(head) ? resolve(head) : resolve(base || ".", head);
2527
- const sep6 = head.endsWith("/") || head.endsWith("\\") ? "/" : "";
2528
- return `${resolvedHead}${sep6}${tail}`;
2609
+ const sep7 = head.endsWith("/") || head.endsWith("\\") ? "/" : "";
2610
+ return `${resolvedHead}${sep7}${tail}`;
2529
2611
  }
2530
2612
  function contentMatchesPath(content, operation) {
2531
2613
  if (!operation.target) return false;
@@ -3017,6 +3099,32 @@ function validateDraft(draft, homeDir, current) {
3017
3099
  (x) => x.detail ? `${x.code}\uFF08${x.detail}\uFF09` : x.code
3018
3100
  );
3019
3101
  }
3102
+ var UNATTENDED_TEXT = {
3103
+ "asks-human": "\u8FD9\u6BB5\u8BDD\u5728\u51B2\u7740\u4E00\u4E2A\u4EBA\u8BF4\uFF0C\u800C\u5230\u70B9\u4E86\u6CA1\u6709\u4EBA\u5728 \u2014\u2014 \u65E0\u4EBA\u503C\u5B88\u4E0B\u5BA1\u6279\u4F1A\u88AB\u76F4\u63A5\u62D2\uFF08\u8BB0\u4E00\u6761\u6B20\u6761\uFF09\u3001`ask_question` \u4E5F\u6CA1\u6709\u95EE\u51FA\u53BB\u7684\u90A3\u6761\u8DEF\u3002\u628A\u6BCF\u4E00\u4E2A\u5C94\u8DEF\u53E3\u5C31\u5730\u5B9A\u6B7B\uFF1A\u4E0E\u5176\u300C\u4E0D\u786E\u5B9A\u5C31\u95EE\u6211\u300D\uFF0C\u4E0D\u5982\u5199\u300C\u4E0D\u786E\u5B9A\u5C31\u6309 X \u529E\uFF0C\u5E76\u5728\u62A5\u544A\u91CC\u5355\u5217\u4E00\u8282\u8BF4\u660E\u4E3A\u4EC0\u4E48\u62FF\u4E0D\u51C6\u300D",
3104
+ "needs-context": "\u8FD9\u6BB5\u8BDD\u6307\u671B\u7740\u4E0A\u4E00\u8F6E\u7684\u4E0A\u4E0B\u6587\uFF0C\u800C**\u6BCF\u4E00\u6B21\u89E6\u53D1\u90FD\u662F\u4E00\u6761\u5168\u65B0\u4F1A\u8BDD** \u2014\u2014 \u6CA1\u6709\u300C\u521A\u624D\u300D\u3001\u6CA1\u6709\u300C\u4E0A\u9762\u90A3\u4E2A\u300D\uFF0C\u4F60\u73B0\u5728\u77E5\u9053\u7684\u4E1C\u897F\u5B83\u4E00\u4E2A\u5B57\u90FD\u4E0D\u77E5\u9053\u3002\u628A\u6307\u4EE3\u6362\u6210\u5199\u6B7B\u7684\u4E1C\u897F\uFF1A\u5177\u4F53\u7684\u8DEF\u5F84\u3001\u5177\u4F53\u7684\u5206\u652F\u540D\u3001\u5177\u4F53\u7684\u5224\u636E"
3105
+ };
3106
+ function unattendedIssues(draft) {
3107
+ const prompt = draft.prompt;
3108
+ if (prompt === void 0 || prompt === "") return [];
3109
+ return lintUnattendedPrompt(prompt).map((x) => `\u300C${x.matched}\u300D\u2014\u2014 ${UNATTENDED_TEXT[x.kind]}`);
3110
+ }
3111
+ function structuralNotes(draft, current) {
3112
+ const out = [];
3113
+ const workDir = draft.workDir ?? current?.workDir;
3114
+ if (workDir === void 0 || workDir === "") {
3115
+ out.push(
3116
+ "\u8FD9\u6761\u4EFB\u52A1**\u6CA1\u6709\u7ED1\u5DE5\u4F5C\u76EE\u5F55**\uFF0C\u6240\u4EE5\u5230\u70B9\u4E86\u5B83\u8DD1\u5728 `~/.epoch/automation/<id>/` \u91CC \u2014\u2014 \u4E0D\u662F\u4F60\u6B64\u523B\u6240\u5728\u7684\u8FD9\u4E2A\u9879\u76EE\u3002\u63D0\u793A\u8BCD\u91CC\u51E1\u662F\u6307\u7740\u7528\u6237\u4ED3\u5E93\u7684\u8DEF\u5F84\u90FD\u8981\u5199\u6210**\u7EDD\u5BF9\u8DEF\u5F84**\uFF0C\u6216\u8005\u5E72\u8106\u7ED9 `workDir`\uFF1B\u76F8\u5BF9\u8DEF\u5F84\u4F1A\u843D\u5728\u90A3\u4E2A\u4EFB\u52A1\u81EA\u5DF1\u7684\u76EE\u5F55\u4E0B\u9762\uFF08\u62A5\u544A\u843D\u90A3\u513F\u6B63\u597D\uFF0C\u522B\u7684\u591A\u534A\u4E0D\u662F\u4F60\u8981\u7684\uFF09\u3002"
3117
+ );
3118
+ }
3119
+ const permission2 = draft.permission ?? current?.permission;
3120
+ const listed = (draft.allowRules ?? current?.allowRules ?? []).length + (draft.allowTools ?? current?.allowTools ?? []).length + (draft.allowOperations ?? current?.allowOperations ?? []).length;
3121
+ if (permission2 !== void 0 && needsAllowlist(permission2) && listed === 0) {
3122
+ out.push(
3123
+ `\u6743\u9650\u6863\u662F ${permission2}\uFF0C\u800C\u4E09\u4EFD\u6E05\u5355\u4E00\u6761\u90FD\u6CA1\u6709 \u2014\u2014 \u8FD9\u6761\u4EFB\u52A1\u9664\u4E86\u8BFB\u4E4B\u5916**\u4EC0\u4E48\u90FD\u505A\u4E0D\u4E86**\uFF1A\u5199\u6587\u4EF6\u548C\u8DD1\u547D\u4EE4\u5728\u8FD9\u4E00\u6863\u4E0B\u90FD\u8981\u786E\u8BA4\uFF0C\u800C\u6CA1\u4EBA\u53EF\u786E\u8BA4\u65F6\u662F\u76F4\u63A5\u62D2 + \u8BB0\u4E00\u6761\u6B20\u6761\u3002\u628A\u5B83\u5230\u70B9\u4E86\u771F\u8981\u505A\u7684\u4E8B\u9010\u6761\u5217\u8FDB \`allowRules\`\uFF08\u8FDE\u843D\u62A5\u544A\u90A3\u6761 \`file_write\` \u4E00\u8D77\uFF09\u3002`
3124
+ );
3125
+ }
3126
+ return out;
3127
+ }
3020
3128
  function summarize(draft, current) {
3021
3129
  const lines = [];
3022
3130
  lines.push(
@@ -3031,6 +3139,7 @@ function summarize(draft, current) {
3031
3139
  `\u26A0\uFE0F \u8FD9\u4EFD\u63D0\u6848\u653E\u5BBD\u4E86\u6388\u6743\u9762\uFF1A${widening.map((k) => WIDENING_TEXT[k]).join("\uFF1B")}\u3002\u5728\u4F60\u7684\u56DE\u590D\u91CC\u4E3B\u52A8\u628A\u8FD9\u4E00\u70B9\u8BF4\u7ED9\u7528\u6237\u542C\uFF0C\u522B\u8BA9\u4ED6\u4ECE\u5361\u7247\u4E0A\u81EA\u5DF1\u53D1\u73B0\u3002`
3032
3140
  );
3033
3141
  }
3142
+ for (const note of structuralNotes(draft, current)) lines.push(`\u26A0\uFE0F ${note}`);
3034
3143
  const cli = draftCommandLine(draft);
3035
3144
  lines.push(
3036
3145
  cli === void 0 ? "\uFF08\u6CA1\u6709\u754C\u9762\u7684\u5BBF\u4E3B\u4E0A\uFF1A\u6539\u4E00\u6761\u5DF2\u6709\u4EFB\u52A1\u4ECA\u5929\u53EA\u6709\u754C\u9762\u90A3\u6761\u8DEF \u2014\u2014 `epoch schedule` \u5E95\u4E0B\u6CA1\u6709\u6539\u5B57\u6BB5\u7684\u5B50\u547D\u4EE4\uFF0C\u53EA\u6709 enable / disable / rm\u3002\u522B\u7F16\u4E00\u6761 update \u51FA\u6765\u3002\uFF09" : `\u6CA1\u6709\u754C\u9762\u7684\u5BBF\u4E3B\u4E0A\uFF0C\u7B49\u4EF7\u7684\u547D\u4EE4\u884C\u662F\uFF08\u9884\u7B97\u90A3\u4E00\u683C\u8981\u7528\u6237\u81EA\u5DF1\u586B\uFF09\uFF1A
@@ -3148,6 +3257,17 @@ ${rows.map(describeRow).join("\n")}`,
3148
3257
  )
3149
3258
  );
3150
3259
  }
3260
+ const unattended = unattendedIssues(draft);
3261
+ if (unattended.length > 0) {
3262
+ return Promise.resolve(
3263
+ say4(
3264
+ `\u8FD9\u4EFD\u8349\u7A3F\u4FDD\u5B58\u5F97\u4E86\uFF0C\u4F46\u5B83\u5230\u70B9\u4E86**\u8DD1\u4E0D\u51FA\u7ED3\u679C** \u2014\u2014 \u5B9A\u65F6\u4EFB\u52A1\u662F\u65E0\u4EBA\u503C\u5B88\u7684\uFF0C\u6CA1\u4EBA\u5728\u65C1\u8FB9\u56DE\u8BDD\u3002\u6539\u5B8C\u518D\u4EA4\uFF1A
3265
+ ${unattended.map((x) => `\xB7 ${x}`).join("\n")}`,
3266
+ false,
3267
+ startedAt
3268
+ )
3269
+ );
3270
+ }
3151
3271
  return Promise.resolve(say4(summarize(draft, current), true, startedAt));
3152
3272
  }
3153
3273
  };
@@ -4005,6 +4125,7 @@ function createSkillViewTool(deps) {
4005
4125
  },
4006
4126
  exposure: ToolExposure.Direct,
4007
4127
  supportsParallel: true,
4128
+ governsOwnOutput: true,
4008
4129
  annotations: {
4009
4130
  readOnlyHint: true,
4010
4131
  destructiveHint: false,
@@ -4370,7 +4491,7 @@ function available(tools) {
4370
4491
  if (tools.length === 0) {
4371
4492
  return "No deferred tools in this session \u2014 every tool is already in your tool list.";
4372
4493
  }
4373
- const names = tools.map((t76) => t76.name).sort();
4494
+ const names = tools.map((t78) => t78.name).sort();
4374
4495
  const shown = names.slice(0, MAX_LISTED);
4375
4496
  const rest = names.length - shown.length;
4376
4497
  return `Searchable tools: ${shown.join(", ")}${rest > 0 ? ` (\u2026and ${rest} more)` : ""}.`;
@@ -4431,6 +4552,83 @@ ${lines.join("\n")}${truncated}`,
4431
4552
  }
4432
4553
  };
4433
4554
  }
4555
+ function isOpenTodo(status) {
4556
+ return status === "pending" || status === "in_progress";
4557
+ }
4558
+ var NS = "tracker";
4559
+ var MIGRATIONS = [
4560
+ {
4561
+ id: "tracker-baseline",
4562
+ version: 1,
4563
+ description: "TODO \u8868\uFF08\u57FA\u7EBF\uFF09",
4564
+ up: (db) => {
4565
+ db.exec(`
4566
+ CREATE TABLE IF NOT EXISTS todos (
4567
+ id TEXT PRIMARY KEY,
4568
+ session_id TEXT NOT NULL,
4569
+ content TEXT NOT NULL,
4570
+ status TEXT NOT NULL DEFAULT 'pending'
4571
+ CHECK(status IN ('pending','in_progress','completed','cancelled')),
4572
+ created_at TEXT NOT NULL,
4573
+ updated_at TEXT NOT NULL
4574
+ );
4575
+ CREATE INDEX IF NOT EXISTS idx_todos_session ON todos(session_id);
4576
+ `);
4577
+ }
4578
+ },
4579
+ {
4580
+ id: "todos-reason-column",
4581
+ version: 2,
4582
+ description: "TODO \u8868\u52A0 reason \u5217\uFF08\u53D6\u6D88\u7406\u7531\uFF0C\u53EA\u6709 cancelled \u90A3\u4E00\u6863\u4F1A\u6709\uFF09",
4583
+ up: (db) => {
4584
+ db.exec(`ALTER TABLE todos ADD COLUMN reason TEXT;`);
4585
+ }
4586
+ }
4587
+ ];
4588
+ var TrackerDB = class {
4589
+ db;
4590
+ path;
4591
+ constructor(dbPath) {
4592
+ this.path = dbPath;
4593
+ this.db = openDatabase(this.path);
4594
+ migrate(this.db, NS, MIGRATIONS);
4595
+ }
4596
+ listBySession(sessionId) {
4597
+ const rows = this.db.prepare(
4598
+ "SELECT id, content, status, reason, created_at, updated_at FROM todos WHERE session_id = ? ORDER BY updated_at ASC"
4599
+ ).all(sessionId);
4600
+ return rows.map(({ reason: reason9, ...rest }) => reason9 ? { ...rest, reason: reason9 } : rest);
4601
+ }
4602
+ countOpen(sessionId) {
4603
+ const row = this.db.prepare(
4604
+ "SELECT COUNT(*) AS n FROM todos WHERE session_id = ? AND status IN ('pending','in_progress')"
4605
+ ).get(sessionId);
4606
+ return row.n;
4607
+ }
4608
+ replaceAll(sessionId, todos) {
4609
+ const insertStmt = this.db.prepare(
4610
+ "INSERT INTO todos (id, session_id, content, status, reason, created_at, updated_at) VALUES (@id, @session_id, @content, @status, @reason, @created_at, @updated_at)"
4611
+ );
4612
+ this.db.transaction(() => {
4613
+ this.db.prepare("DELETE FROM todos WHERE session_id = ?").run(sessionId);
4614
+ for (const t78 of todos) {
4615
+ insertStmt.run({
4616
+ id: t78.id,
4617
+ session_id: sessionId,
4618
+ content: t78.content,
4619
+ status: t78.status,
4620
+ reason: t78.reason || null,
4621
+ created_at: t78.created_at,
4622
+ updated_at: t78.updated_at
4623
+ });
4624
+ }
4625
+ })();
4626
+ }
4627
+ close() {
4628
+ releaseDatabase(this.path);
4629
+ }
4630
+ };
4631
+ // src/tools/builtin/todo.ts
4434
4632
  function generateShortId() {
4435
4633
  return randomBytes(3).toString("hex");
4436
4634
  }
@@ -4443,9 +4641,9 @@ function formatTodoList(todos, pendingCount) {
4443
4641
  cancelled: "\u5DF2\u53D6\u6D88"
4444
4642
  };
4445
4643
  lines.push(`[todo] \u72B6\u6001\u6C47\u603B: ${pendingCount} \u4E2A\u5F85\u5B8C\u6210`);
4446
- for (const t76 of todos) {
4447
- const label = statusLabel[t76.status] || t76.status;
4448
- lines.push(`- [${label}] ${t76.content}${t76.reason ? ` \u2014\u2014 ${t76.reason}` : ""}`);
4644
+ for (const t78 of todos) {
4645
+ const label = statusLabel[t78.status] || t78.status;
4646
+ lines.push(`- [${label}] ${t78.content}${t78.reason ? ` \u2014\u2014 ${t78.reason}` : ""}`);
4449
4647
  }
4450
4648
  return lines.join("\n");
4451
4649
  }
@@ -4519,14 +4717,14 @@ function createTodoTool(db) {
4519
4717
  };
4520
4718
  }
4521
4719
  const now = (/* @__PURE__ */ new Date()).toISOString();
4522
- const existing = new Map(db.listBySession(ctx.sessionId).map((t76) => [t76.id, t76]));
4523
- const todos = rawTodos.map((t76) => {
4524
- const prev = t76.id ? existing.get(t76.id) : void 0;
4525
- const status = isValidStatus(t76.status) ? t76.status : "pending";
4526
- const reason9 = normalizeReason(status, t76.reason);
4720
+ const existing = new Map(db.listBySession(ctx.sessionId).map((t78) => [t78.id, t78]));
4721
+ const todos = rawTodos.map((t78) => {
4722
+ const prev = t78.id ? existing.get(t78.id) : void 0;
4723
+ const status = isValidStatus(t78.status) ? t78.status : "pending";
4724
+ const reason9 = normalizeReason(status, t78.reason);
4527
4725
  return {
4528
- id: t76.id || generateShortId(),
4529
- content: String(t76.content || "").slice(0, 500),
4726
+ id: t78.id || generateShortId(),
4727
+ content: String(t78.content || "").slice(0, 500),
4530
4728
  status,
4531
4729
  ...reason9 ? { reason: reason9 } : {},
4532
4730
  created_at: prev?.created_at || now,
@@ -4534,9 +4732,7 @@ function createTodoTool(db) {
4534
4732
  };
4535
4733
  });
4536
4734
  db.replaceAll(ctx.sessionId, todos);
4537
- const pendingCount = todos.filter(
4538
- (t76) => t76.status === "pending" || t76.status === "in_progress"
4539
- ).length;
4735
+ const pendingCount = todos.filter((t78) => isOpenTodo(t78.status)).length;
4540
4736
  return {
4541
4737
  success: true,
4542
4738
  output: formatTodoList(todos, pendingCount),
@@ -4871,7 +5067,7 @@ function intersectNames(base, incoming) {
4871
5067
  if (!base) return incoming ? [...incoming] : void 0;
4872
5068
  if (!incoming) return [...base];
4873
5069
  const baseSet = new Set(base);
4874
- return incoming.filter((t76) => baseSet.has(t76));
5070
+ return incoming.filter((t78) => baseSet.has(t78));
4875
5071
  }
4876
5072
  function findRole(roles, name) {
4877
5073
  return roles.find((r) => r.name === name);
@@ -4957,7 +5153,7 @@ function roleScopedProvider(base, role, blocked) {
4957
5153
  return allow ? allow.has(name) : true;
4958
5154
  };
4959
5155
  return {
4960
- list: () => base.list().filter((t76) => permitted(t76.name)),
5156
+ list: () => base.list().filter((t78) => permitted(t78.name)),
4961
5157
  get: (name) => permitted(name) ? base.get(name) : void 0
4962
5158
  };
4963
5159
  }
@@ -4984,7 +5180,7 @@ function createRoleScope(base, sessionRole, blocked) {
4984
5180
  }
4985
5181
  function unknownRoleTools(base, role, blocked) {
4986
5182
  if (!role?.tools) return [];
4987
- const available2 = new Set(base.list().map((t76) => t76.name));
5183
+ const available2 = new Set(base.list().map((t78) => t78.name));
4988
5184
  const blockedSet = new Set(blocked);
4989
5185
  return role.tools.filter((name) => !available2.has(name) && !blockedSet.has(name));
4990
5186
  }
@@ -5320,7 +5516,7 @@ function createToolScope(base) {
5320
5516
  const permitted = (name) => allow ? allow.has(name) : true;
5321
5517
  return {
5322
5518
  provider: {
5323
- list: () => base.list().filter((t76) => permitted(t76.name)),
5519
+ list: () => base.list().filter((t78) => permitted(t78.name)),
5324
5520
  get: (name) => permitted(name) ? base.get(name) : void 0
5325
5521
  },
5326
5522
  enter: (next) => {
@@ -5490,7 +5686,7 @@ function scanHooks(dir, issues) {
5490
5686
  return [...parsed.entries].map(([type, list]) => ({
5491
5687
  type,
5492
5688
  count: list.reduce((sum, entry) => sum + entry.hooks.length, 0)
5493
- })).filter((t76) => t76.count > 0).sort((a, b) => a.type.localeCompare(b.type));
5689
+ })).filter((t78) => t78.count > 0).sort((a, b) => a.type.localeCompare(b.type));
5494
5690
  }
5495
5691
  function scanMcp(dir, plugin, issues) {
5496
5692
  const path = join(dir, PLUGIN_LAYOUT.mcp);
@@ -5568,11 +5764,11 @@ function firstLine(err) {
5568
5764
  return message.split("\n")[0] ?? t("plugin_inventory.unknown_error");
5569
5765
  }
5570
5766
  var PLUGIN_MANIFEST_FILE = "epoch-plugin.json";
5571
- var PLUGIN_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
5767
+ var PLUGIN_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
5572
5768
  function isValidPluginName(name) {
5573
5769
  return PLUGIN_NAME_PATTERN.test(name);
5574
5770
  }
5575
- var AuthorSchema = z.object({
5771
+ var PluginAuthorSchema = z.object({
5576
5772
  name: z.string().min(1),
5577
5773
  email: z.string().min(1).optional(),
5578
5774
  url: z.string().min(1).optional()
@@ -5581,7 +5777,7 @@ var ManifestSchema = z.object({
5581
5777
  name: z.string().min(1),
5582
5778
  version: z.string().min(1),
5583
5779
  description: z.string().min(1).optional(),
5584
- author: AuthorSchema.optional(),
5780
+ author: PluginAuthorSchema.optional(),
5585
5781
  epochVersion: z.string().min(1).optional(),
5586
5782
  homepage: z.string().min(1).optional(),
5587
5783
  keywords: z.array(z.string().min(1)).optional()
@@ -5750,7 +5946,7 @@ var RecordSchema = z.object({
5750
5946
  name: z.string().regex(PLUGIN_NAME_PATTERN),
5751
5947
  version: z.string().min(1),
5752
5948
  source: z.string().min(1),
5753
- sourceType: z.enum(["local", "git", "npm", "archive"]),
5949
+ sourceType: z.enum(["local", "git", "npm", "archive", "host"]),
5754
5950
  path: z.string().min(1),
5755
5951
  linked: z.boolean(),
5756
5952
  enabled: z.boolean(),
@@ -6148,6 +6344,144 @@ function flattenSingleRoot(dest) {
6148
6344
  }
6149
6345
  rmSync(inner, { recursive: true, force: true });
6150
6346
  }
6347
+ var PACK_EXCLUDES = [
6348
+ ".git",
6349
+ "node_modules",
6350
+ ".DS_Store",
6351
+ "__pycache__"
6352
+ ];
6353
+ var SECRET_LOOKING = [
6354
+ /^\.env(\..+)?$/i,
6355
+ /\.pem$/i,
6356
+ /\.key$/i,
6357
+ /^id_(rsa|dsa|ecdsa|ed25519)$/i
6358
+ ];
6359
+ var PACK_MAX_BYTES = 50 * 1024 * 1024;
6360
+ var PACK_MAX_FILES = 2e3;
6361
+ function planPack(dir, limits = {}) {
6362
+ const read = readPluginManifest(dir);
6363
+ if (!read.manifest) {
6364
+ const detail = read.issues.map((i) => `${i.path}: ${i.message}`).join("\n");
6365
+ return { ok: false, reason: t("plugin_pack.not_a_plugin", { detail: `
6366
+ ${detail}` }) };
6367
+ }
6368
+ const maxFiles = limits.maxFiles ?? PACK_MAX_FILES;
6369
+ const maxBytes = limits.maxBytes ?? PACK_MAX_BYTES;
6370
+ const files = [];
6371
+ const skippedLinks = [];
6372
+ let totalBytes = 0;
6373
+ const walk3 = (current) => {
6374
+ let entries;
6375
+ try {
6376
+ entries = readdirSync(current, { withFileTypes: true });
6377
+ } catch (err) {
6378
+ return t("plugin_pack.read_failed", { path: current, reason: firstLine3(err) });
6379
+ }
6380
+ for (const entry of entries) {
6381
+ if (PACK_EXCLUDES.includes(entry.name)) continue;
6382
+ const abs = join(current, entry.name);
6383
+ const rel = relative(dir, abs).split(sep).join("/");
6384
+ if (entry.isSymbolicLink()) {
6385
+ skippedLinks.push(rel);
6386
+ continue;
6387
+ }
6388
+ if (entry.isDirectory()) {
6389
+ if (isVirtualenv(abs)) continue;
6390
+ const failed = walk3(abs);
6391
+ if (failed) return failed;
6392
+ continue;
6393
+ }
6394
+ if (!entry.isFile()) continue;
6395
+ if (rel.includes("\n") || rel.includes("\r")) {
6396
+ return t("plugin_pack.bad_name", { path: rel });
6397
+ }
6398
+ let stat5;
6399
+ try {
6400
+ stat5 = statSync(abs);
6401
+ } catch (err) {
6402
+ return t("plugin_pack.read_failed", { path: abs, reason: firstLine3(err) });
6403
+ }
6404
+ files.push({ path: rel, bytes: stat5.size, mtimeMs: Math.floor(stat5.mtimeMs) });
6405
+ totalBytes += stat5.size;
6406
+ if (files.length > maxFiles) {
6407
+ return t("plugin_pack.too_many_files", { max: maxFiles });
6408
+ }
6409
+ if (totalBytes > maxBytes) {
6410
+ return t("plugin_pack.too_large", { max: Math.floor(maxBytes / (1024 * 1024)) });
6411
+ }
6412
+ }
6413
+ return void 0;
6414
+ };
6415
+ const failure = walk3(dir);
6416
+ if (failure) return { ok: false, reason: failure };
6417
+ if (files.length === 0) return { ok: false, reason: t("plugin_pack.empty") };
6418
+ files.sort((a, b) => a.path.localeCompare(b.path));
6419
+ const suspicious = files.map((f) => f.path).filter((p) => SECRET_LOOKING.some((re) => re.test(p.split("/").pop() ?? "")));
6420
+ return {
6421
+ ok: true,
6422
+ plan: { dir, manifest: read.manifest, files, totalBytes, suspicious, skippedLinks }
6423
+ };
6424
+ }
6425
+ function packFileName(manifest) {
6426
+ return `${manifest.name}-${manifest.version}.zip`;
6427
+ }
6428
+ async function packPlugin(plan, destDir) {
6429
+ const rel = relative(plan.dir, destDir);
6430
+ const outside = rel !== "" && (rel.startsWith("..") || isAbsolute(rel));
6431
+ if (!outside) {
6432
+ return { ok: false, reason: t("plugin_pack.dest_inside", { dir: plan.dir }) };
6433
+ }
6434
+ const listFile = join(destDir, "epoch-pack-files.txt");
6435
+ const archivePath = join(destDir, packFileName(plan.manifest));
6436
+ try {
6437
+ writeFileSync(listFile, `${plan.files.map((f) => f.path).join("\n")}
6438
+ `, "utf-8");
6439
+ } catch (err) {
6440
+ return {
6441
+ ok: false,
6442
+ reason: t("plugin_pack.read_failed", { path: listFile, reason: firstLine3(err) })
6443
+ };
6444
+ }
6445
+ let result;
6446
+ try {
6447
+ result = await runCommand(resolveTar(), [
6448
+ "-c",
6449
+ "-f",
6450
+ archivePath,
6451
+ "--format",
6452
+ "zip",
6453
+ "-C",
6454
+ plan.dir,
6455
+ "-T",
6456
+ listFile
6457
+ ]);
6458
+ } finally {
6459
+ rmSync(listFile, { force: true });
6460
+ }
6461
+ if (!result.ok) {
6462
+ return {
6463
+ ok: false,
6464
+ reason: t("plugin_pack.tar_failed", { detail: result.detail }) + (result.notFound ? t("plugin_archive.no_tar_hint") : "")
6465
+ };
6466
+ }
6467
+ let bytes;
6468
+ try {
6469
+ bytes = statSync(archivePath).size;
6470
+ } catch (err) {
6471
+ return {
6472
+ ok: false,
6473
+ reason: t("plugin_pack.read_failed", { path: archivePath, reason: firstLine3(err) })
6474
+ };
6475
+ }
6476
+ return { ok: true, archivePath, bytes };
6477
+ }
6478
+ function isVirtualenv(dir) {
6479
+ return existsSync(join(dir, "pyvenv.cfg"));
6480
+ }
6481
+ function firstLine3(err) {
6482
+ const message = err instanceof Error ? err.message : String(err);
6483
+ return message.split("\n")[0] ?? message;
6484
+ }
6151
6485
  var NPM_SPEC = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(?:@[a-zA-Z0-9.^~*+-]+)?$/;
6152
6486
  var REMOTE_URL = /^https?:\/\/[^\s'"\\^|<>]+$/;
6153
6487
  function resolveSource(raw) {
@@ -6177,6 +6511,11 @@ function resolveSource(raw) {
6177
6511
  if (!NPM_SPEC.test(pkg)) return void 0;
6178
6512
  return { type: "npm", raw: input, target: pkg };
6179
6513
  }
6514
+ if (input.startsWith("host:")) {
6515
+ const opaque = input.slice("host:".length).trim();
6516
+ if (!opaque) return void 0;
6517
+ return { type: "host", raw: input, target: opaque };
6518
+ }
6180
6519
  if (input.startsWith("https://") || input.startsWith("http://")) {
6181
6520
  const [url = "", fragment] = input.split("#", 2);
6182
6521
  if (!REMOTE_URL.test(url)) return void 0;
@@ -6192,7 +6531,11 @@ function resolveSource(raw) {
6192
6531
  }
6193
6532
  return { type: "git", raw: input, target: url };
6194
6533
  }
6195
- return { type: "local", raw: input, target: isAbsolute(input) ? input : resolve(input) };
6534
+ const abs = isAbsolute(input) ? input : resolve(input);
6535
+ if (isArchiveUrl(abs)) {
6536
+ return { type: "archive", raw: input, target: abs, localFile: true };
6537
+ }
6538
+ return { type: "local", raw: input, target: abs };
6196
6539
  }
6197
6540
  function isArchiveUrl(url) {
6198
6541
  const path = url.split("?")[0] ?? "";
@@ -6237,7 +6580,8 @@ async function runInstall(resolved, staging, opts) {
6237
6580
  return { ok: false, reason: t("plugin_install.manifest_unusable", { detail: `
6238
6581
  ${detail}` }) };
6239
6582
  }
6240
- const conflict = new PluginStore(opts.statePath).read().records.find((r) => r.name === manifest.name);
6583
+ const taken = new PluginStore(opts.statePath).read().records.find((r) => r.name === manifest.name);
6584
+ const conflict = taken && taken.name !== opts.replace ? taken : void 0;
6241
6585
  const current = opts.epochVersion ?? currentEpochVersion();
6242
6586
  const version = checkEpochVersion(manifest.epochVersion, current);
6243
6587
  const versionUnmet = !version.ok && manifest.epochVersion ? { required: manifest.epochVersion, current, reason: version.reason } : void 0;
@@ -6255,7 +6599,7 @@ ${detail}` }) };
6255
6599
  reason: t("plugin_install.renamed", { found: manifest.name, expected: opts.replace })
6256
6600
  };
6257
6601
  }
6258
- if (conflict && opts.replace === void 0) {
6602
+ if (conflict) {
6259
6603
  return {
6260
6604
  ok: false,
6261
6605
  preview: preview3,
@@ -6320,8 +6664,51 @@ async function fetchInto(source, staging, opts) {
6320
6664
  if (source.type === "local") return fetchLocal(source);
6321
6665
  if (source.type === "git") return fetchGit(source, staging, opts);
6322
6666
  if (source.type === "npm") return fetchNpm(source, staging, opts);
6667
+ if (source.type === "host") return fetchFromHost(source, staging, opts);
6668
+ if (source.localFile) return fetchLocalArchive(source, staging);
6323
6669
  return fetchArchive(source, staging, opts);
6324
6670
  }
6671
+ async function fetchLocalArchive(source, staging) {
6672
+ if (!existsSync(source.target)) {
6673
+ return { ok: false, reason: t("plugin_install.archive_missing", { path: source.target }) };
6674
+ }
6675
+ mkdirSync(staging, { recursive: true });
6676
+ return extractArchive(source.target, staging);
6677
+ }
6678
+ async function fetchFromHost(source, staging, opts) {
6679
+ const ask = opts.fetchFromHost;
6680
+ if (!ask) {
6681
+ return { ok: false, reason: t("plugin_install.host_fetch_unavailable") };
6682
+ }
6683
+ opts.onProgress?.(t("plugin_install.host_fetching", { target: source.target }));
6684
+ mkdirSync(staging, { recursive: true });
6685
+ return withDownloadDir(async (dl) => {
6686
+ let answer;
6687
+ try {
6688
+ answer = await ask({ target: source.target, destDir: dl });
6689
+ } catch (err) {
6690
+ return {
6691
+ ok: false,
6692
+ reason: t("plugin_install.host_fetch_threw", {
6693
+ reason: (err instanceof Error ? err.message : String(err)).split("\n")[0] ?? ""
6694
+ })
6695
+ };
6696
+ }
6697
+ if (!answer.ok) {
6698
+ return {
6699
+ ok: false,
6700
+ reason: answer.reason ?? t("plugin_install.host_fetch_failed", { target: source.target })
6701
+ };
6702
+ }
6703
+ if (!existsSync(answer.archivePath)) {
6704
+ return {
6705
+ ok: false,
6706
+ reason: t("plugin_install.host_fetch_no_file", { path: answer.archivePath })
6707
+ };
6708
+ }
6709
+ return extractArchive(answer.archivePath, staging);
6710
+ });
6711
+ }
6325
6712
  function fetchLocal(source) {
6326
6713
  if (!existsSync(source.target)) {
6327
6714
  return { ok: false, reason: t("plugin_install.local_missing", { path: source.target }) };
@@ -6468,18 +6855,44 @@ async function updatePlugin(name, opts) {
6468
6855
  });
6469
6856
  }
6470
6857
  var MARKETPLACE_FILE = "epoch-marketplace.json";
6471
- var NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
6858
+ var NAME_PATTERN = PLUGIN_NAME_PATTERN;
6472
6859
  var EntrySchema = z.object({
6473
6860
  name: z.string().regex(NAME_PATTERN),
6474
6861
  description: z.string().min(1).optional(),
6475
6862
  source: z.string().min(1),
6476
6863
  category: z.string().min(1).optional(),
6477
- keywords: z.array(z.string().min(1)).optional()
6864
+ keywords: z.array(z.string().min(1)).optional(),
6865
+ author: PluginAuthorSchema.optional(),
6866
+ downloads: z.number().int().nonnegative().optional(),
6867
+ likes: z.number().int().nonnegative().optional(),
6868
+ displayName: z.string().min(1).optional(),
6869
+ icon: z.string().min(1).optional(),
6870
+ version: z.string().min(1).optional(),
6871
+ changelog: z.string().min(1).optional(),
6872
+ size: z.number().int().positive().optional(),
6873
+ homepage: z.string().min(1).optional(),
6874
+ visibility: z.enum(["private", "public"]).optional().catch(void 0),
6875
+ status: z.enum(["listed", "reviewing", "rejected", "offline"]).optional().catch(void 0)
6876
+ });
6877
+ var MARKETPLACE_DISPLAY_FIELDS = [
6878
+ "status",
6879
+ "downloads",
6880
+ "likes",
6881
+ "author",
6882
+ "category",
6883
+ "visibility",
6884
+ "icon",
6885
+ "homepage",
6886
+ "size"
6887
+ ];
6888
+ var DisplaySchema = z.object({
6889
+ fields: z.array(z.string().min(1)).optional()
6478
6890
  });
6479
6891
  var CatalogSchema = z.object({
6480
6892
  name: z.string().regex(NAME_PATTERN),
6481
6893
  description: z.string().min(1).optional(),
6482
6894
  owner: z.object({ name: z.string().min(1), url: z.string().min(1).optional() }).optional(),
6895
+ display: DisplaySchema.optional(),
6483
6896
  plugins: z.array(EntrySchema)
6484
6897
  });
6485
6898
  var RecordSchema2 = z.object({
@@ -6492,6 +6905,16 @@ var StateSchema2 = z.object({
6492
6905
  version: z.literal(1).optional(),
6493
6906
  marketplaces: z.array(RecordSchema2)
6494
6907
  });
6908
+ function projectEntryForDisplay(entry, display) {
6909
+ const allowed = display?.fields;
6910
+ if (allowed === void 0) return entry;
6911
+ const keep = new Set(allowed);
6912
+ const out = { ...entry };
6913
+ for (const field of MARKETPLACE_DISPLAY_FIELDS) {
6914
+ if (!keep.has(field)) delete out[field];
6915
+ }
6916
+ return out;
6917
+ }
6495
6918
  function readMarketplaces(statePath) {
6496
6919
  if (!existsSync(statePath)) return { records: [], issues: [] };
6497
6920
  let raw;
@@ -6580,10 +7003,15 @@ function searchMarketplaces(keyword, statePath) {
6580
7003
  entry.name,
6581
7004
  entry.description ?? "",
6582
7005
  entry.category ?? "",
7006
+ entry.author?.name ?? "",
6583
7007
  ...entry.keywords ?? []
6584
7008
  ].join(" ").toLowerCase();
6585
7009
  if (needle && !haystack.includes(needle)) continue;
6586
- hits.push({ marketplace: record.name, entry, ref: `${record.name}/${entry.name}` });
7010
+ hits.push({
7011
+ marketplace: record.name,
7012
+ entry: projectEntryForDisplay(entry, record.catalog.display),
7013
+ ref: `${record.name}/${entry.name}`
7014
+ });
6587
7015
  }
6588
7016
  }
6589
7017
  return hits;
@@ -6944,14 +7372,14 @@ var CheckpointStore = class {
6944
7372
  }
6945
7373
  async enforceTotalSize(protectSession, protectTurnIndex) {
6946
7374
  const turns = await this.collectTurns();
6947
- let total = turns.reduce((sum, t76) => sum + t76.bytes, 0);
7375
+ let total = turns.reduce((sum, t78) => sum + t78.bytes, 0);
6948
7376
  if (total <= this.maxTotalBytes) return;
6949
7377
  turns.sort((a, b) => a.createdAt - b.createdAt);
6950
- for (const t76 of turns) {
7378
+ for (const t78 of turns) {
6951
7379
  if (total <= this.maxTotalBytes) return;
6952
- if (t76.sessionId === protectSession && t76.turnIndex === protectTurnIndex) continue;
6953
- await this.removeTurn(t76.sessionId, t76.turnIndex);
6954
- total -= t76.bytes;
7380
+ if (t78.sessionId === protectSession && t78.turnIndex === protectTurnIndex) continue;
7381
+ await this.removeTurn(t78.sessionId, t78.turnIndex);
7382
+ total -= t78.bytes;
6955
7383
  }
6956
7384
  }
6957
7385
  async collectTurns() {
@@ -7472,7 +7900,8 @@ function addUsage(a, b) {
7472
7900
  const cacheHit = (a.cacheHitTokens ?? 0) + (b.cacheHitTokens ?? 0);
7473
7901
  const cacheWrite = (a.cacheWriteTokens ?? 0) + (b.cacheWriteTokens ?? 0);
7474
7902
  const reasoning = (a.reasoningTokens ?? 0) + (b.reasoningTokens ?? 0);
7475
- const total = (a.totalTokens ?? 0) + (b.totalTokens ?? 0);
7903
+ const reported = a.totalTokens !== void 0 || b.totalTokens !== void 0;
7904
+ const total = reported ? totalUsageTokens(a) + totalUsageTokens(b) : 0;
7476
7905
  return {
7477
7906
  inputTokens: a.inputTokens + b.inputTokens,
7478
7907
  outputTokens: a.outputTokens + b.outputTokens,
@@ -7754,10 +8183,13 @@ function systemPromptSegments(params) {
7754
8183
  `## \u89C4\u5219`,
7755
8184
  "- \u7528\u4E2D\u6587\u56DE\u590D\u7528\u6237",
7756
8185
  "- \u6D89\u53CA\u6587\u4EF6\u64CD\u4F5C\u65F6\uFF0C\u5148\u7528\u5DE5\u5177\u83B7\u53D6\u4FE1\u606F\u518D\u884C\u52A8",
7757
- "- \u4E0D\u786E\u5B9A\u65F6\u5411\u7528\u6237\u786E\u8BA4\uFF0C\u4E0D\u8981\u731C\u6D4B",
8186
+ "- \u4E8B\u5B9E\u4E0D\u786E\u5B9A\u5C31\u7528\u5DE5\u5177\u53BB\u67E5\uFF0C\u67E5\u4E0D\u5230\u5C31\u8BF4\u4E0D\u77E5\u9053 \u2014\u2014 \u522B\u7F16",
8187
+ "- \u505A\u6CD5\u6709\u4E24\u79CD\u5408\u7406\u8D70\u6CD5\u3001\u4E14\u9009\u9519\u4E86\u8981\u767D\u5E72\u65F6\uFF0C\u624D\u505C\u4E0B\u6765\u95EE\u4E00\u53E5\uFF1B\u6D3B\u591A\u3001\u8981\u82B1\u65F6\u95F4\u3001\u6B65\u9AA4\u91CD\u590D\u90FD\u4E0D\u662F\u8981\u95EE\u7684\u7406\u7531",
7758
8188
  "- file_patch \u6BD4 file_write \u66F4\u5B89\u5168\uFF0C\u4F18\u5148\u4F7F\u7528",
7759
8189
  "- \u540C\u65F6\u8BFB\u53D6\u591A\u4E2A\u6587\u4EF6\u65F6\u7528 file_multi_read",
7760
- "- \u4E00\u6B21\u53EA\u505A\u5FC5\u8981\u7684\u4E8B\uFF0C\u4E0D\u8981\u8FC7\u5EA6\u8C03\u7528\u5DE5\u5177",
8190
+ "- \u7528\u6237\u7ED9\u4E86\u8303\u56F4\u5C31\u628A\u90A3\u4E2A\u8303\u56F4\u505A\u5B8C\uFF1A\u8BF4 100 \u6761\u5C31\u662F 100 \u6761\u3002\u505A 3 \u6761\u518D\u95EE\u300C\u8981\u4E0D\u8981\u7EE7\u7EED\u300D\u662F\u6CA1\u505A\u5B8C\uFF0C\u4E0D\u662F\u8C28\u614E",
8191
+ "- \u8BE5\u7701\u7684\u662F\u91CD\u590D\u8C03\u540C\u4E00\u6B21\u5DE5\u5177\uFF08\u540C\u6837\u7684\u53C2\u6570\u95EE\u7B2C\u4E8C\u904D\uFF09\uFF0C\u4E0D\u662F\u5C11\u505A\u51E0\u4EF6\u4E8B \u2014\u2014 \u5DE5\u5177\u8C03\u7528\u6B21\u6570\u4ECE\u6765\u4E0D\u662F\u8981\u538B\u7684\u6307\u6807",
8192
+ "- \u8BF4\u4E86\u4E0B\u4E00\u6B65\u5C31\u5F53\u573A\u505A\uFF1A\u300C\u6211\u5148\u53BB\u67E5\u4E00\u4E0B\u300D\u300Clet me \u68C0\u67E5\u4E00\u4E0B\u300D\u8FD9\u79CD\u8BDD\u540E\u9762\u5FC5\u987B\u7D27\u8DDF\u4E00\u6B21\u5DE5\u5177\u8C03\u7528\uFF0C\u522B\u62FF\u5B83\u5F53\u4E00\u8F6E\u56DE\u590D\u7684\u7ED3\u5C3E",
7761
8193
  "- \u88AB\u95EE\u5230\u4F60\u662F\u8C01 / \u4F60\u662F\u4EC0\u4E48\uFF1A\u8BF4\u4F60\u662F\u4E00\u4E2A\u8DD1\u5728\u7528\u6237\u672C\u673A\u7684\u667A\u80FD\u4F53\uFF0C\u518D\u8BF4\u6E05\u695A\u4F60\u80FD\u505A\u4EC0\u4E48\u3002\u4E0D\u62A5\u54C1\u724C\u540D\uFF0C\u4E5F\u522B\u81EA\u79F0\u662F\u522B\u5BB6\u4EA7\u54C1\u6216\u5382\u5546\u7684\u5B98\u65B9\u52A9\u624B \u2014\u2014 \u540E\u8005\u662F\u5047\u8BDD",
7762
8194
  "- \u7528\u6237\u76F4\u63A5\u95EE\u5E95\u5C42\u662F\u4EC0\u4E48\u6A21\u578B\uFF1A\u7167\u5B9E\u8BF4\uFF08`## \u73AF\u5883` \u91CC\u90A3\u884C\u300C\u5F53\u524D\u6A21\u578B\u300D\u5C31\u662F\uFF09\uFF0C\u4F46\u522B\u628A\u5B83\u5F53\u6210\u4F60\u7684\u8EAB\u4EFD"
7763
8195
  ].join("\n")
@@ -8903,6 +9335,79 @@ ${this.describeWarning(status)}
8903
9335
  });
8904
9336
  }
8905
9337
  };
9338
+ // src/provider/errors.ts
9339
+ var ProviderError = class extends Error {
9340
+ constructor(message, code, recoverable, recovery = "abort") {
9341
+ super(message);
9342
+ this.code = code;
9343
+ this.recoverable = recoverable;
9344
+ this.recovery = recovery;
9345
+ this.name = "ProviderError";
9346
+ }
9347
+ code;
9348
+ recoverable;
9349
+ recovery;
9350
+ };
9351
+ var PROVIDER_NO_CREDENTIAL = 1001;
9352
+ var PROVIDER_ABORTED = 1060;
9353
+ var PROVIDER_CONTEXT_OVERFLOW = 1030;
9354
+ function isContextOverflow(err) {
9355
+ if (err instanceof ProviderError) return err.code === PROVIDER_CONTEXT_OVERFLOW;
9356
+ return classifyProviderError(err).code === PROVIDER_CONTEXT_OVERFLOW;
9357
+ }
9358
+ function isAbortError(err) {
9359
+ for (let cur = err, depth = 0; cur && depth < 5; depth++) {
9360
+ if (typeof cur !== "object") return false;
9361
+ if (cur.name === "AbortError") return true;
9362
+ cur = cur.cause;
9363
+ }
9364
+ return false;
9365
+ }
9366
+ function extractStatus(err) {
9367
+ if (!err || typeof err !== "object") return void 0;
9368
+ const e = err;
9369
+ for (const candidate of [e.statusCode, e.status, e.response?.status]) {
9370
+ if (typeof candidate === "number" && candidate >= 100 && candidate < 600) return candidate;
9371
+ }
9372
+ return void 0;
9373
+ }
9374
+ function extractStatusFromMessage(msg) {
9375
+ const leading = msg.match(/^\s*\[?(4\d{2}|5\d{2})\b/);
9376
+ if (leading?.[1]) return Number(leading[1]);
9377
+ const labelled = msg.match(/(?:status(?:\s*code)?|http|code)\D{0,3}(4\d{2}|5\d{2})\b/i);
9378
+ if (labelled?.[1]) return Number(labelled[1]);
9379
+ return void 0;
9380
+ }
9381
+ function classifyProviderError(err) {
9382
+ const msg = err instanceof Error ? err.message : String(err);
9383
+ const status = extractStatus(err);
9384
+ if (isAbortError(err)) return { code: PROVIDER_ABORTED, recovery: "abort" };
9385
+ if (/context.?length|context.?window|maximum.*token|token.?limit|too.?many.?tokens|prompt.*too.?long|reduce.*(length|tokens)/i.test(
9386
+ msg
9387
+ ))
9388
+ return { code: PROVIDER_CONTEXT_OVERFLOW, recovery: "abort" };
9389
+ if (/invalid.*api.?key|unauthorized|authentication|permission.?denied|forbidden/i.test(msg))
9390
+ return { code: 1001, recovery: "rotate_credential" };
9391
+ if (/billing|quota.*exceeded|insufficient.*(balance|credit)|payment.?required/i.test(msg))
9392
+ return { code: 1012, recovery: "switch_provider" };
9393
+ if (/rate.?limit|too.?many.?requests/i.test(msg)) return { code: 1010, recovery: "retry" };
9394
+ if (/model.?not.?found|model.*does.*not.*exist|unknown.?model|no.?such.?model/i.test(msg))
9395
+ return { code: 1040, recovery: "switch_model" };
9396
+ if (/overloaded|server.?error|bad.?gateway|service.?unavailable/i.test(msg))
9397
+ return { code: 1020, recovery: "retry" };
9398
+ if (/timeout|timed.?out|etimedout|econnreset|enotfound|socket.?hang.?up/i.test(msg))
9399
+ return { code: 1041, recovery: "retry" };
9400
+ const code = status ?? extractStatusFromMessage(msg);
9401
+ if (code !== void 0) {
9402
+ if (code === 401 || code === 403) return { code: 1001, recovery: "rotate_credential" };
9403
+ if (code === 402) return { code: 1012, recovery: "switch_provider" };
9404
+ if (code === 404) return { code: 1040, recovery: "switch_model" };
9405
+ if (code === 429) return { code: 1010, recovery: "retry" };
9406
+ if (code >= 500) return { code: 1020, recovery: "retry" };
9407
+ if (code >= 400) return { code: 1050, recovery: "abort" };
9408
+ }
9409
+ return { code: 9999, recovery: "retry" };
9410
+ }
8906
9411
  function toModelMessages(messages) {
8907
9412
  return messages.map(toModelMessage);
8908
9413
  }
@@ -9140,6 +9645,34 @@ var HookDispatcher = class {
9140
9645
  return verifications.some((v) => v.action === "continue");
9141
9646
  }
9142
9647
  };
9648
+ // src/agent/idle-turn-detector.ts
9649
+ var IDLE_ANCHORS = [
9650
+ {
9651
+ zh: /^我(?:先|现在就?|这就|马上|来|去|再|打算|准备|会先|会再|会去|会用)\s*\S/,
9652
+ en: /^i['’]?(?:ll|m\s+going\s+to|\s+will|\s+am\s+going\s+to)\s+\S/i
9653
+ },
9654
+ { zh: /^让我\s*\S/, en: /^let\s+me\s*\S/i },
9655
+ { zh: /^接下来我\s*\S/, en: /^next[,,]?\s+i['’]?ll\s+\S/i },
9656
+ { zh: /^先\s*\S/, en: /^first[,,]?\s+(?:let\s+me|i['’]?ll)\s+\S/i }
9657
+ ];
9658
+ var SPEECH_ACTS = [
9659
+ {
9660
+ zh: /^(?:我(?:先|现在就?|这就|马上|来|去|再|打算|准备)|让我|接下来我|先)\s*(?:说|讲|解释|回答|答|总结|归纳|汇报|说明|捋|梳理|聊|谈)/,
9661
+ en: /^(?:let\s+me|i['’]?(?:ll|m\s+going\s+to)|next[,,]?\s+i['’]?ll|first[,,]?\s+(?:let\s+me|i['’]?ll))\s+(?:explain|summari[sz]e|clarify|recap|answer|describe|outline|walk\s+you\s+through|tell\s+you)\b/i
9662
+ }
9663
+ ];
9664
+ function sentenceHeads(text) {
9665
+ return text.split(/[\n。!?!?]+/).map((s) => s.trim()).filter((s) => s !== "");
9666
+ }
9667
+ function announcedNextStep(text) {
9668
+ for (const head of sentenceHeads(text)) {
9669
+ const hit = IDLE_ANCHORS.some((a) => a.zh.test(head) || a.en.test(head));
9670
+ if (!hit) continue;
9671
+ if (SPEECH_ACTS.some((a) => a.zh.test(head) || a.en.test(head))) continue;
9672
+ return true;
9673
+ }
9674
+ return false;
9675
+ }
9143
9676
  // src/agent/message-codec.ts
9144
9677
  var TOOL_OUTPUT_PREFIX = "[tool output] ";
9145
9678
  function flattenMessage(m) {
@@ -9327,14 +9860,32 @@ function currentGoalBlock(view) {
9327
9860
  }
9328
9861
  function wrapCurrentRequest(userText) {
9329
9862
  return [
9330
- "[\u4EE5\u4E0B\u662F\u7528\u6237\u6B64\u523B\u53D1\u6765\u7684**\u65B0\u8BF7\u6C42**\u3002\u4E0A\u9762\u90A3\u4E9B\u7528\u6237\u6D88\u606F\u90FD\u662F\u5DF2\u7ECF\u5904\u7406\u8FC7\u7684\u4E0A\u6587\uFF1A",
9331
- "\u5B83\u4EEC\u91CC\u7684\u4EFB\u52A1\u4E0D\u8981\u91CD\u505A\uFF0C\u9664\u975E\u8FD9\u4E00\u6761\u660E\u786E\u63D0\u5230\uFF1B\u800C\u5176\u4E2D\u7684\u504F\u597D\u548C\u957F\u671F\u7EA6\u675F\uFF08\u56DE\u590D\u8BED\u8A00\u3001",
9332
- "\u4EE3\u7801\u98CE\u683C\u3001\u4E0D\u8BB8\u78B0\u7684\u4E1C\u897F\uFF09\u4ECD\u7136\u6709\u6548\u3002\u6709\u51B2\u7A81\u65F6\u4EE5\u8FD9\u4E00\u6761\u4E3A\u51C6\u3002]",
9863
+ "[\u4EE5\u4E0B\u662F\u7528\u6237\u6B64\u523B\u53D1\u6765\u7684**\u65B0\u8BF7\u6C42**\u3002\u4E0A\u9762\u90A3\u4E9B\u7528\u6237\u6D88\u606F\u91CC**\u5DF2\u7ECF\u529E\u5B8C**\u7684\u90A3\u4E9B\u4E0D\u8981\u91CD\u505A\uFF0C",
9864
+ "\u9664\u975E\u8FD9\u4E00\u6761\u660E\u786E\u63D0\u5230\uFF1B\u800C\u5176\u4E2D\u7684\u504F\u597D\u548C\u957F\u671F\u7EA6\u675F\uFF08\u56DE\u590D\u8BED\u8A00\u3001\u4EE3\u7801\u98CE\u683C\u3001\u4E0D\u8BB8\u78B0\u7684\u4E1C\u897F\uFF09",
9865
+ "\u4ECD\u7136\u6709\u6548\u3002\u6709\u51B2\u7A81\u65F6\u4EE5\u8FD9\u4E00\u6761\u4E3A\u51C6\u3002",
9866
+ "\u26A0\uFE0F **\u529E\u4E86\u4E00\u534A\u7684\u4E0D\u7B97\u529E\u5B8C**\uFF1A\u4E0A\u6587\u91CC\u8FD8\u6B20\u7740\u7684\u6D3B\u4ECD\u7136\u8981\u505A\u5B8C\u3002\u8FD9\u4E00\u6761\u662F\u300C\u7EE7\u7EED\u300D\u300C\u786E\u8BA4\u300D",
9867
+ "\u300C\u597D\u300D\u8FD9\u7C7B\u77ED\u56DE\u590D\u65F6\uFF0C\u5B83\u6307\u7684\u5C31\u662F\u90A3\u4EF6\u6B20\u7740\u7684\u6D3B\uFF0C\u4E0D\u662F\u300C\u6CA1\u4E8B\u53EF\u505A\u4E86\u300D\u3002]",
9333
9868
  "<current_request>",
9334
9869
  userText,
9335
9870
  "</current_request>"
9336
9871
  ].join("\n");
9337
9872
  }
9873
+ function unfinishedTodoNote(open) {
9874
+ return [
9875
+ `\u4F60\u7684 TODO \u6E05\u5355\u4E0A\u8FD8\u6709 ${open} \u9879\u6CA1\u505A\u5B8C\uFF0C\u6240\u4EE5\u8FD9\u4EF6\u4E8B\u8FD8\u6CA1\u7ED3\u675F\u3002`,
9876
+ "\u63A5\u7740\u505A\u4E0B\u4E00\u9879\uFF0C\u522B\u518D\u95EE\u4E00\u904D\u300C\u8981\u4E0D\u8981\u7EE7\u7EED\u300D\u2014\u2014 \u7528\u6237\u5DF2\u7ECF\u8981\u8FC7\u8FD9\u4EF6\u4E8B\u4E86\u3002",
9877
+ "\u771F\u505A\u4E0D\u4E0B\u53BB\uFF08\u53D6\u6570\u4E00\u76F4\u5931\u8D25\u3001\u7F3A\u51ED\u636E\u3001\u8981\u82B1\u94B1\uFF09\u5C31\u8BF4\u6E05\u5DF2\u5B8C\u6210\u51E0\u9879\u3001\u8FD8\u5269\u51E0\u9879\u3001\u5361\u5728\u54EA\u4E00\u9879\u7684\u54EA\u4E00\u6B65\u3002"
9878
+ ].join("\n");
9879
+ }
9880
+ function announcedButIdleNote() {
9881
+ return [
9882
+ "\u4F60\u521A\u624D\u90A3\u6BB5\u8BDD\u5BA3\u5E03\u4E86\u4E0B\u4E00\u6B65\uFF0C\u4F46\u8FD9\u4E00\u8F6E\u4E00\u4E2A\u5DE5\u5177\u90FD\u6CA1\u8C03 \u2014\u2014 \u5728\u7528\u6237\u90A3\u8FB9\uFF0C\u8FD9\u4E00\u8F6E\u5C31\u662F\u505C\u4F4F\u4E86\u3002",
9883
+ "\u73B0\u5728\u5C31\u628A\u4F60\u8BF4\u7684\u90A3\u4E00\u6B65\u505A\u6389\uFF1A\u76F4\u63A5\u8C03\u5DE5\u5177\uFF0C\u522B\u518D\u6362\u4E2A\u8BF4\u6CD5\u628A\u300C\u6211\u9A6C\u4E0A\u53BB\u505A\u300D\u5199\u7B2C\u4E8C\u904D\u3002",
9884
+ "\u5982\u679C\u4F60\u5176\u5B9E\u5DF2\u7ECF\u628A\u7528\u6237\u8981\u7684\u4E8B\u505A\u5B8C\u4E86\u3001\u6216\u8005\u521A\u624D\u53EA\u662F\u5728\u56DE\u7B54\u4E00\u4E2A\u95EE\u9898\uFF0C\u90A3\u5C31\u76F4\u63A5\u8BF4\u7ED3\u8BBA\uFF0C\u522B\u518D\u5BA3\u5E03\u4E0B\u4E00\u6B65\u3002",
9885
+ "\u5982\u679C\u7528\u6237\u660E\u8BF4\u4E86\u300C\u5148\u522B\u52A8\u624B\u300D\u300C\u53EA\u8BF4\u8BA1\u5212\u300D\u300C\u5148\u522B\u6539\u300D\u8FD9\u7C7B\u8BDD\uFF0C\u90A3\u5C31\u7167\u7528\u6237\u8BF4\u7684\u529E \u2014\u2014 \u8FD9\u6761\u63D0\u9192\u7BA1\u4E0D\u7740\u7528\u6237\u521A\u4E0B\u7684\u6307\u4EE4\uFF0C\u522B\u56E0\u4E3A\u5B83\u53BB\u8C03\u5DE5\u5177\u3002",
9886
+ "\u771F\u505A\u4E0D\u4E0B\u53BB\uFF08\u7F3A\u51ED\u636E\u3001\u8981\u82B1\u94B1\u3001\u53D6\u6570\u4E00\u76F4\u5931\u8D25\uFF09\u5C31\u8BF4\u6E05\u5361\u5728\u54EA\u4E00\u6B65\u7684\u54EA\u4E00\u73AF\u3002"
9887
+ ].join("\n");
9888
+ }
9338
9889
  var CODE_VERIFICATION_POLICY = [
9339
9890
  "<code_verification>",
9340
9891
  "\u6539\u5B8C\u4EE3\u7801\u3001\u5BA3\u5E03\u5B8C\u6210\u4E4B\u524D\uFF0C\u8C03\u4E00\u6B21 `lsp_diagnostics`\uFF0C\u628A\u672C\u8F6E\u65B0\u5EFA\u6216\u4FEE\u6539\u8FC7\u7684\u6E90\u6587\u4EF6\u5217\u8FDB\u53BB\u3002",
@@ -9356,6 +9907,20 @@ var ASK_QUESTION_POLICY = [
9356
9907
  "\u522B\u7528\u5B83\u95EE\u300C\u6211\u7684\u65B9\u6848\u884C\u4E0D\u884C\u300D\uFF08\u90A3\u662F `exit_plan_mode`\uFF09\uFF0C\u4E5F\u522B\u7528\u5B83\u6C47\u62A5\u8FDB\u5EA6\u3002",
9357
9908
  "</ask_user_question>"
9358
9909
  ].join("\n");
9910
+ var BATCH_WORK_POLICY = [
9911
+ "<batch_work>",
9912
+ "\u4E00\u4EF6\u4E8B\u8981\u5BF9 N \u4E2A\u5BF9\u8C61\u5404\u505A\u4E00\u904D\uFF08\u4E00\u6279\u5546\u54C1 / \u4E00\u6279\u6587\u4EF6 / \u4E00\u6279\u884C\uFF09\u65F6\uFF1A",
9913
+ "1. **\u5148\u6570\u6E05\u662F\u51E0\u4E2A**\uFF0C\u7528 `todo` \u843D\u6210\u6E05\u5355\u518D\u52A8\u624B\u3002\u6570\u76EE\u591A\u5C31\u5206\u6279\u5EFA\u6761\u76EE\uFF0C\u522B\u53EA\u5EFA\u524D\u51E0\u4E2A\u3002",
9914
+ "2. **\u4E00\u6761\u4E00\u6761\u505A\u5B8C**\uFF0C\u6BCF\u505A\u5B8C\u4E00\u6761\u5C31\u628A\u5B83\u6807 `completed`\u3002\u4E2D\u95F4\u4E0D\u8981\u505C\u4E0B\u6765\u6C47\u62A5\u8FDB\u5EA6 \u2014\u2014",
9915
+ " \u6C47\u62A5\u4E00\u6B21\u5C31\u662F\u6536\u5DE5\u4E00\u6B21\uFF0C\u4E4B\u540E\u8981\u7528\u6237\u518D\u5F00\u53E3\u624D\u80FD\u7EE7\u7EED\u3002",
9916
+ "3. **\u4E0D\u8BB8\u62FF\u300C\u6B65\u9AA4\u5F88\u591A\u300D\u300C\u8981\u82B1\u65F6\u95F4\u300D\u300C\u6BCF\u4E2A\u90FD\u8981\u53D6\u4E00\u904D\u6570\u636E\u300D\u5F53\u7406\u7531\u7F29\u5C0F\u8303\u56F4\u3002**",
9917
+ " \u90A3\u6B63\u662F\u7528\u6237\u8BA9\u4F60\u505A\u8FD9\u4EF6\u4E8B\u7684\u539F\u56E0\u3002\u6162\u4E0D\u662F\u95EE\u9898\uFF0C\u53EA\u505A\u4E00\u90E8\u5206\u624D\u662F\u3002",
9918
+ "4. \u53EA\u5728**\u771F\u505A\u4E0D\u4E0B\u53BB**\u65F6\u505C\uFF08\u53D6\u6570\u4E00\u76F4\u5931\u8D25\u3001\u7F3A\u51ED\u636E\u3001\u8981\u82B1\u94B1\uFF09\u3002\u505C\u7684\u65F6\u5019\u8BF4\u6E05",
9919
+ " \u300C\u5DF2\u5B8C\u6210 X \u9879\u3001\u8FD8\u5269 Y \u9879\u3001\u5361\u5728\u54EA\u4E00\u9879\u7684\u54EA\u4E00\u6B65\u300D\uFF0C**\u522B\u95EE\u300C\u8981\u4E0D\u8981\u7EE7\u7EED\u300D**\u3002",
9920
+ "5. \u6570\u636E\u6E90\u4E00\u6B21\u53D6\u4E0D\u5168\uFF08\u5DE5\u5177\u8F93\u51FA\u88AB\u622A\u65AD\uFF09\u65F6\uFF0C\u5206\u6BB5\u53D6\u6216\u6362\u66F4\u7CBE\u786E\u7684\u53D6\u6CD5\u518D\u53D6\u4E00\u6B21 \u2014\u2014",
9921
+ " **\u522B\u628A\u770B\u5F97\u89C1\u7684\u90A3\u51E0\u6761\u5F53\u6210\u5168\u90E8**\u3002",
9922
+ "</batch_work>"
9923
+ ].join("\n");
9359
9924
  var PromptBuilder = class {
9360
9925
  constructor(deps) {
9361
9926
  this.deps = deps;
@@ -9364,9 +9929,10 @@ var PromptBuilder = class {
9364
9929
  memoryInitialized = false;
9365
9930
  projectBlock(tools) {
9366
9931
  const permLevel = this.deps.permissionManager?.getLevel() || "default";
9367
- const hasPlanTool = tools.some((t76) => t76.name === "enter_plan_mode");
9368
- const hasLspTool = tools.some((t76) => t76.name === "lsp_diagnostics");
9369
- const hasAskTool = tools.some((t76) => t76.name === "ask_user_question");
9932
+ const hasPlanTool = tools.some((t78) => t78.name === "enter_plan_mode");
9933
+ const hasLspTool = tools.some((t78) => t78.name === "lsp_diagnostics");
9934
+ const hasAskTool = tools.some((t78) => t78.name === "ask_user_question");
9935
+ const hasTodoTool = tools.some((t78) => t78.name === "todo");
9370
9936
  const plan = this.deps.approvedPlan?.()?.trim();
9371
9937
  const codeSdk = renderSdkDeclaration(this.deps.codeModeTools?.() ?? []);
9372
9938
  return renderProjectContext(this.deps.site().projectContext) + `
@@ -9379,7 +9945,10 @@ ${PLAN_MODE_POLICY}` : "") + (hasLspTool ? `
9379
9945
 
9380
9946
  ${CODE_VERIFICATION_POLICY}` : "") + (hasAskTool ? `
9381
9947
 
9382
- ${ASK_QUESTION_POLICY}` : "") + (codeSdk ? `
9948
+ ${ASK_QUESTION_POLICY}` : "") +
9949
+ (hasTodoTool ? `
9950
+
9951
+ ${BATCH_WORK_POLICY}` : "") + (codeSdk ? `
9383
9952
 
9384
9953
  ${codeSdk}` : "") +
9385
9954
  (plan ? `
@@ -9435,9 +10004,16 @@ ${recalled}
9435
10004
  }
9436
10005
  };
9437
10006
  var STALL_THRESHOLD = 5;
10007
+ var FAILURE_THRESHOLD = 3;
10008
+ var MAX_FAILURE_KEYS = 64;
10009
+ var FINGERPRINT_MAX_CHARS = 200;
10010
+ function fingerprint(output) {
10011
+ return output.replace(/\s+/g, " ").trim().slice(0, FINGERPRINT_MAX_CHARS);
10012
+ }
9438
10013
  var StallDetector = class {
9439
10014
  history = [];
9440
- recordAction(toolName, args) {
10015
+ failures = /* @__PURE__ */ new Map();
10016
+ recordAction(toolName, args, outcome) {
9441
10017
  const argsKey2 = JSON.stringify(args);
9442
10018
  const last = this.history[this.history.length - 1];
9443
10019
  if (last && last.toolName === toolName && last.args === argsKey2) {
@@ -9445,26 +10021,66 @@ var StallDetector = class {
9445
10021
  } else {
9446
10022
  this.history = [{ toolName, args: argsKey2, consecutive: 1 }];
9447
10023
  }
10024
+ if (!outcome) return;
10025
+ const key = `${toolName}\0${argsKey2}`;
10026
+ if (outcome.success) {
10027
+ this.failures.delete(key);
10028
+ return;
10029
+ }
10030
+ const error = fingerprint(outcome.output);
10031
+ const rec = this.failures.get(key);
10032
+ if (rec?.error === error) {
10033
+ rec.count++;
10034
+ return;
10035
+ }
10036
+ if (rec === void 0 && this.failures.size >= MAX_FAILURE_KEYS) {
10037
+ const oldest = this.failures.keys().next();
10038
+ if (!oldest.done) this.failures.delete(oldest.value);
10039
+ }
10040
+ this.failures.set(key, { toolName, args: argsKey2, error, count: 1 });
9448
10041
  }
9449
10042
  isStalled() {
9450
- const last = this.history[this.history.length - 1];
9451
- return last ? last.consecutive >= STALL_THRESHOLD : false;
10043
+ return this.stalledFailure() !== null || this.stalledStreak() !== null;
9452
10044
  }
9453
10045
  getStallInfo() {
9454
- const last = this.history[this.history.length - 1];
9455
- if (!last || last.consecutive < STALL_THRESHOLD) return null;
9456
- return t("agent_run.stall_info", {
9457
- count: last.consecutive,
9458
- tool: last.toolName,
9459
- args: last.args
9460
- });
10046
+ const failure = this.stalledFailure();
10047
+ if (failure) {
10048
+ return t("agent_run.stall_failure_info", {
10049
+ count: failure.count,
10050
+ tool: failure.toolName,
10051
+ args: failure.args,
10052
+ error: failure.error
10053
+ });
10054
+ }
10055
+ const streak = this.stalledStreak();
10056
+ if (streak) {
10057
+ return t("agent_run.stall_info", {
10058
+ count: streak.consecutive,
10059
+ tool: streak.toolName,
10060
+ args: streak.args
10061
+ });
10062
+ }
10063
+ return null;
9461
10064
  }
9462
- resetOnSuccess() {
10065
+ reset() {
9463
10066
  this.history = [];
10067
+ this.failures.clear();
9464
10068
  }
9465
10069
  getLastToolName() {
9466
10070
  return this.history[this.history.length - 1]?.toolName ?? null;
9467
10071
  }
10072
+ stalledStreak() {
10073
+ const last = this.history[this.history.length - 1];
10074
+ return last && last.consecutive >= STALL_THRESHOLD ? last : null;
10075
+ }
10076
+ stalledFailure() {
10077
+ let worst = null;
10078
+ for (const rec of this.failures.values()) {
10079
+ if (rec.count < FAILURE_THRESHOLD) continue;
10080
+ if (worst === null || rec.count > worst.count) worst = rec;
10081
+ }
10082
+ return worst;
10083
+ }
9468
10084
  };
9469
10085
  var ARTIFACT_RETENTION_DEFAULTS = {
9470
10086
  maxAgeDays: 7,
@@ -9530,10 +10146,133 @@ function pruneArtifacts(root, keepSession, retention = ARTIFACT_RETENTION_DEFAUL
9530
10146
  async function removeSessionArtifacts(sessionId, homeDir) {
9531
10147
  await rm(artifactsDir(sessionId, homeDir), { recursive: true, force: true });
9532
10148
  }
9533
- // src/agent/tool-executor.ts
9534
- var MAX_TOOL_OUTPUT = 2e3;
10149
+ var MAX_TOOL_OUTPUT = 3e4;
9535
10150
  var MAX_SUB_CALL_OUTPUT = CODE_MODE_LIMITS.maxValueChars;
9536
- var TRUNCATED = "\n...[truncated]";
10151
+ function budgetOf2(tool, depth, path = "success") {
10152
+ if (tool.governsOwnOutput && path === "success") return Number.POSITIVE_INFINITY;
10153
+ return depth > 0 ? MAX_SUB_CALL_OUTPUT : MAX_TOOL_OUTPUT;
10154
+ }
10155
+ function shapeOf(output) {
10156
+ if (output === void 0 || output === null) return "text";
10157
+ return typeof output === "string" ? "text" : "json";
10158
+ }
10159
+ var NEXT_STEP = "\u8981\u62FF\u5B8C\u6574\u5185\u5BB9\uFF1A\u6536\u7A84\u8FD9\u4E2A\u5DE5\u5177\u7684\u8303\u56F4\u53C2\u6570\uFF08\u5206\u9875 / \u5B57\u6BB5 / limit\uFF09\u540E\u91CD\u53D6\uFF0C\u6216\u5728 run_code \u91CC\u8C03\u5B83\u3001\u5728\u7A0B\u5E8F\u91CC\u7B5B";
10160
+ var CUT_LABEL = {
10161
+ line: "\u5207\u5728\u884C\u8FB9\u754C",
10162
+ space: "\u5207\u5728\u7A7A\u767D\u8FB9\u754C",
10163
+ hard: "\u786C\u5207\uFF08\u8FD9\u4E00\u6BB5\u91CC\u6CA1\u6709\u6362\u884C\uFF0C\u672B\u5C3E\u53EF\u80FD\u662F\u6B8B\u7684\uFF09"
10164
+ };
10165
+ var CUT_RANK = { line: 0, space: 1, hard: 2 };
10166
+ function truncateForModel(text, limit, shape) {
10167
+ if (!Number.isFinite(limit)) return text;
10168
+ const total = countCodePoints(text);
10169
+ if (total <= limit) return text;
10170
+ return shape === "json" ? cutJson(text, limit, total) : cutText(text, limit, total);
10171
+ }
10172
+ function notice(detail) {
10173
+ return `[\u5F15\u64CE\u6309\u8F93\u51FA\u9884\u7B97\u622A\u65AD\uFF1A${detail}\u3002${NEXT_STEP}]`;
10174
+ }
10175
+ function cutText(text, limit, total) {
10176
+ const half = Math.floor(limit / 2);
10177
+ const head = backOff(headCodePoints(text, half), "head");
10178
+ const tail = backOff(tailCodePoints(text, limit - half), "tail");
10179
+ const skipped = total - countCodePoints(head.text) - countCodePoints(tail.text);
10180
+ const kind = CUT_RANK[head.kind] >= CUT_RANK[tail.kind] ? head.kind : tail.kind;
10181
+ return [
10182
+ head.text,
10183
+ `\u2026[\u4E2D\u6BB5\u7701\u7565 ${skipped} \u5B57\u7B26\uFF0C${CUT_LABEL[kind]}]\u2026`,
10184
+ tail.text,
10185
+ notice(`\u539F\u6587 ${total} \u5B57\u7B26\uFF0C\u9884\u7B97 ${limit}`)
10186
+ ].join("\n");
10187
+ }
10188
+ function backOff(piece, side) {
10189
+ const floor = piece.length / 2;
10190
+ for (const [kind, ch] of [
10191
+ ["line", "\n"],
10192
+ ["space", " "]
10193
+ ]) {
10194
+ const at = side === "head" ? piece.lastIndexOf(ch) : piece.indexOf(ch);
10195
+ if (at < 0) continue;
10196
+ const kept = side === "head" ? piece.slice(0, at) : piece.slice(at + 1);
10197
+ if (kept.length >= floor) return { text: kept, kind };
10198
+ }
10199
+ return { text: piece, kind: "hard" };
10200
+ }
10201
+ function cutJson(text, limit, total) {
10202
+ const cut = scanJson(text, limit);
10203
+ const head = `\u5E8F\u5217\u5316\u540E ${total} \u5B57\u7B26\uFF0C\u9884\u7B97 ${limit}`;
10204
+ if (cut.element !== void 0) {
10205
+ const dropped = total - countCodePoints(cut.element);
10206
+ return `${cut.element}
10207
+ ${notice(`${head}\uFF0C\u5DF2\u6309\u7ED3\u6784\u622A\u5230\u6700\u540E\u4E00\u4E2A\u5B8C\u6574\u7684\u6570\u7EC4\u5143\u7D20\u5E76\u8865\u9F50\u62EC\u53F7\uFF0C\u4E22\u6389\u5C3E\u90E8 ${dropped} \u5B57\u7B26\u3002\u7559\u4E0B\u6765\u7684\u6BCF\u4E00\u6761\u90FD\u662F\u6574\u6761\uFF0C\u4F46**\u88AB\u8865\u9F50\u7684\u5BB9\u5668\u540E\u9762\u53EF\u80FD\u8FD8\u7F3A\u5B57\u6BB5**\uFF08\u6BD4\u5982\u6839\u5BF9\u8C61\u4E0A\u6392\u5728\u540E\u9762\u7684\u90A3\u4E9B\u952E\uFF09`)}`;
10208
+ }
10209
+ if (cut.field !== void 0) {
10210
+ const dropped = total - countCodePoints(cut.field);
10211
+ return `${cut.field}
10212
+ ${notice(`${head}\uFF0C\u9884\u7B97\u5185\u653E\u4E0D\u4E0B\u4E00\u4E2A\u5B8C\u6574\u5143\u7D20\uFF0C\u6539\u4E3A\u5C31\u5730\u622A\u65AD\u90A3\u4E2A\u5B57\u7B26\u4E32\u5B57\u6BB5\uFF08\u6807\u8BB0\u5728\u503C\u91CC\u9762\uFF09\uFF0C\u4E22\u6389 ${dropped} \u5B57\u7B26`)}`;
10213
+ }
10214
+ return notice(
10215
+ `${head}\uFF0C\u800C\u9884\u7B97\u5185\u8FDE\u4E00\u4E2A\u5B8C\u6574\u5143\u7D20\u90FD\u653E\u4E0D\u4E0B\u3002**\u8FD9\u91CC\u6CA1\u6709\u6570\u636E** \u2014\u2014 \u6309\u5B57\u7B26\u5207\u4F1A\u5207\u51FA\u4E00\u4E2A\u5408\u6CD5\u4F46\u9519\u8BEF\u7684\u6570\u503C\uFF0C\u6240\u4EE5\u4E00\u4E2A\u5B57\u90FD\u4E0D\u7ED9`
10216
+ );
10217
+ }
10218
+ var FIELD_MARK = "\u2026[\u672C\u5B57\u6BB5\u88AB\u5F15\u64CE\u622A\u65AD]";
10219
+ function scanJson(text, limit) {
10220
+ const stack = [];
10221
+ const expectKey = [];
10222
+ let inString = false;
10223
+ let escaped = false;
10224
+ let element;
10225
+ let field;
10226
+ for (let i = 0; i < text.length; i += 1) {
10227
+ if (i === limit && inString && expectKey[expectKey.length - 1] !== true) {
10228
+ field = closeString(text, i, escaped, stack);
10229
+ }
10230
+ if (i > limit) break;
10231
+ const c = text[i];
10232
+ if (inString) {
10233
+ if (escaped) escaped = false;
10234
+ else if (c === "\\") escaped = true;
10235
+ else if (c === '"') inString = false;
10236
+ continue;
10237
+ }
10238
+ if (c === '"') inString = true;
10239
+ else if (c === "{" || c === "[") {
10240
+ stack.push(c === "{" ? "}" : "]");
10241
+ expectKey.push(c === "{");
10242
+ } else if (c === "}" || c === "]") {
10243
+ stack.pop();
10244
+ expectKey.pop();
10245
+ if (inArray(stack) && i + 1 + stack.length <= limit) {
10246
+ element = text.slice(0, i + 1) + closers(stack);
10247
+ }
10248
+ } else if (c === ":") {
10249
+ expectKey[expectKey.length - 1] = false;
10250
+ } else if (c === ",") {
10251
+ expectKey[expectKey.length - 1] = stack[stack.length - 1] === "}";
10252
+ if (inArray(stack) && i + stack.length <= limit) {
10253
+ element = text.slice(0, i) + closers(stack);
10254
+ }
10255
+ }
10256
+ }
10257
+ return {
10258
+ ...element !== void 0 ? { element } : {},
10259
+ ...field !== void 0 ? { field } : {}
10260
+ };
10261
+ }
10262
+ function closers(stack) {
10263
+ return [...stack].reverse().join("");
10264
+ }
10265
+ function inArray(stack) {
10266
+ return stack[stack.length - 1] === "]";
10267
+ }
10268
+ function closeString(text, at, escaped, stack) {
10269
+ const kept = dropLoneHighSurrogate(text.slice(0, escaped ? at - 1 : at));
10270
+ return `${kept}${FIELD_MARK}"${closers(stack)}`;
10271
+ }
10272
+ function dropLoneHighSurrogate(text) {
10273
+ return /[\uD800-\uDBFF]$/.test(text) ? text.slice(0, -1) : text;
10274
+ }
10275
+ // src/agent/tool-executor.ts
9537
10276
  var MAX_ARGS_KEY = 200;
9538
10277
  var OUTPUT_TAG = "tool_output";
9539
10278
  var MAX_SUB_CALL_DEPTH = 1;
@@ -9554,17 +10293,14 @@ function sourceLabel2(toolName) {
9554
10293
  let label = toolName;
9555
10294
  if (toolName.startsWith("mcp__")) {
9556
10295
  const rest = toolName.slice("mcp__".length);
9557
- const sep6 = rest.indexOf("__");
9558
- label = `mcp:${sep6 > 0 ? rest.slice(0, sep6) : rest}`;
10296
+ const sep7 = rest.indexOf("__");
10297
+ label = `mcp:${sep7 > 0 ? rest.slice(0, sep7) : rest}`;
9559
10298
  }
9560
10299
  return label.replace(/[^A-Za-z0-9_.:-]/g, "_");
9561
10300
  }
9562
- function truncate(text, limit) {
9563
- return text.length > limit ? text.slice(0, limit) + TRUNCATED : text;
9564
- }
9565
- function appendArtifactSummaries(text, artifacts, limit) {
9566
- if (artifacts.length === 0) return truncate(text, limit);
9567
- const body2 = truncate(text, limit);
10301
+ function appendArtifactSummaries(text, artifacts, limit, shape) {
10302
+ const body2 = truncateForModel(text, limit, shape);
10303
+ if (artifacts.length === 0) return body2;
9568
10304
  const lines = artifacts.map((a) => a.summary).join("\n");
9569
10305
  return body2 ? `${body2}
9570
10306
  ${lines}` : lines;
@@ -9662,6 +10398,7 @@ var ToolExecutor = class {
9662
10398
  permissionLevel: this.deps.permissionManager?.getLevel() || "auto",
9663
10399
  ...signal ? { signal } : {},
9664
10400
  ...onOutput ? { onOutput } : {},
10401
+ outputBudget: budgetOf2(et, depth),
9665
10402
  ...depth < MAX_SUB_CALL_DEPTH ? { callTool: this.subCallChannel(sessionId, signal, span, depth) } : {}
9666
10403
  });
9667
10404
  if (snapshotted.length > 0) {
@@ -9693,11 +10430,7 @@ var ToolExecutor = class {
9693
10430
  success: true,
9694
10431
  output: this.renderOutput(
9695
10432
  et,
9696
- appendArtifactSummaries(
9697
- text,
9698
- artifacts,
9699
- depth > 0 ? MAX_SUB_CALL_OUTPUT : MAX_TOOL_OUTPUT
9700
- )
10433
+ appendArtifactSummaries(text, artifacts, budgetOf2(et, depth), shapeOf(r.output))
9701
10434
  ),
9702
10435
  ...artifacts.length > 0 ? { artifacts } : {},
9703
10436
  ...diffs.length > 0 ? { diffs } : {}
@@ -9708,7 +10441,7 @@ var ToolExecutor = class {
9708
10441
  success: false,
9709
10442
  output: this.renderOutput(
9710
10443
  et,
9711
- truncate(failureText(r), depth > 0 ? MAX_SUB_CALL_OUTPUT : MAX_TOOL_OUTPUT)
10444
+ truncateForModel(failureText(r), budgetOf2(et, depth, "failure"), "text")
9712
10445
  )
9713
10446
  });
9714
10447
  } catch (err) {
@@ -9820,7 +10553,8 @@ var ToolExecutor = class {
9820
10553
  type: opType,
9821
10554
  target: opTarget,
9822
10555
  detail: opDetail,
9823
- reason: decision.reason
10556
+ reason: decision.reason,
10557
+ ...decision.rememberable === false ? { rememberable: false } : {}
9824
10558
  })
9825
10559
  );
9826
10560
  this.deps.telemetry?.counter(METRIC.TOOL_APPROVAL, 1, {
@@ -9844,6 +10578,72 @@ var ToolExecutor = class {
9844
10578
  return { allowed: true };
9845
10579
  }
9846
10580
  };
10581
+ var WINDOW_CHARS = 1024;
10582
+ var NGRAM = 16;
10583
+ var MAX_DISTINCT_RATIO = 0.2;
10584
+ var CHECK_EVERY_CHARS = 256;
10585
+ var SUSTAIN_CHECKS = 2;
10586
+ var MIN_NORMALIZED_CHARS = 256;
10587
+ function normalizeRuns(text) {
10588
+ return text.replace(/\s+/g, " ").replace(/(.)\1{3,}/gu, (_m, c) => c.repeat(3));
10589
+ }
10590
+ function distinctNgramRatio(text, n = NGRAM) {
10591
+ if (text.length < n) return 1;
10592
+ const seen = /* @__PURE__ */ new Set();
10593
+ let total = 0;
10594
+ for (let i = 0; i + n <= text.length; i++) {
10595
+ seen.add(text.slice(i, i + n));
10596
+ total++;
10597
+ }
10598
+ return total === 0 ? 1 : seen.size / total;
10599
+ }
10600
+ var RepetitionDetector = class {
10601
+ buf = "";
10602
+ sinceCheck = 0;
10603
+ streak = 0;
10604
+ chars = 0;
10605
+ hit = null;
10606
+ push(delta) {
10607
+ if (this.hit) return true;
10608
+ if (!delta) return false;
10609
+ this.chars += delta.length;
10610
+ this.buf = (this.buf + delta).slice(-WINDOW_CHARS);
10611
+ this.sinceCheck += delta.length;
10612
+ if (this.sinceCheck < CHECK_EVERY_CHARS) return false;
10613
+ this.sinceCheck = 0;
10614
+ if (this.buf.length < WINDOW_CHARS) return false;
10615
+ const norm = normalizeRuns(this.buf);
10616
+ if (norm.length < MIN_NORMALIZED_CHARS) {
10617
+ this.streak = 0;
10618
+ return false;
10619
+ }
10620
+ const ratio = distinctNgramRatio(norm);
10621
+ if (ratio > MAX_DISTINCT_RATIO) {
10622
+ this.streak = 0;
10623
+ return false;
10624
+ }
10625
+ if (++this.streak < SUSTAIN_CHECKS) return false;
10626
+ this.hit = { chars: this.chars, distinctRatio: ratio };
10627
+ return true;
10628
+ }
10629
+ pushAll(text) {
10630
+ for (let i = 0; i < text.length; i += CHECK_EVERY_CHARS) {
10631
+ if (this.push(text.slice(i, i + CHECK_EVERY_CHARS))) return true;
10632
+ }
10633
+ return false;
10634
+ }
10635
+ isLooping() {
10636
+ return this.hit !== null;
10637
+ }
10638
+ getInfo() {
10639
+ if (!this.hit) return null;
10640
+ return t("agent_run.looping_info", {
10641
+ chars: this.hit.chars,
10642
+ percent: Math.round(this.hit.distinctRatio * 100)
10643
+ });
10644
+ }
10645
+ };
10646
+ // src/agent/turn.ts
9847
10647
  async function* runTurn(opts) {
9848
10648
  if (opts.streaming) {
9849
10649
  return yield* runTurnStreaming(opts);
@@ -9852,55 +10652,72 @@ async function* runTurn(opts) {
9852
10652
  }
9853
10653
  async function* runTurnStreaming(opts) {
9854
10654
  const { provider, input, guard, reasoningGuard } = opts;
10655
+ const repetition = new RepetitionDetector();
10656
+ const reasoningRepetition = new RepetitionDetector();
9855
10657
  let text = "";
9856
10658
  let reasoningText = "";
9857
10659
  let finishReason = "stop";
10660
+ let looping;
9858
10661
  let usage;
9859
10662
  const toolCalls = [];
9860
10663
  let streamError;
9861
10664
  let model;
9862
10665
  let warnings;
9863
- for await (const chunk of provider.stream(input)) {
9864
- switch (chunk.type) {
9865
- case "text-delta": {
9866
- const raw = chunk.textDelta ?? "";
9867
- if (!raw) break;
9868
- const delta = guard ? guard.push(raw) : raw;
9869
- if (!delta) break;
9870
- text += delta;
9871
- yield { type: "text-delta", text: delta };
9872
- break;
10666
+ const ac = new AbortController();
10667
+ const unlink = linkAbort(input.signal, ac);
10668
+ try {
10669
+ for await (const chunk of provider.stream({ ...input, signal: ac.signal })) {
10670
+ switch (chunk.type) {
10671
+ case "text-delta": {
10672
+ const raw = chunk.textDelta ?? "";
10673
+ if (!raw) break;
10674
+ const delta = guard ? guard.push(raw) : raw;
10675
+ if (!delta) break;
10676
+ text += delta;
10677
+ yield { type: "text-delta", text: delta };
10678
+ if (repetition.push(delta)) looping = repetition;
10679
+ break;
10680
+ }
10681
+ case "reasoning-delta": {
10682
+ const raw = chunk.textDelta ?? "";
10683
+ if (!raw) break;
10684
+ const delta = reasoningGuard ? reasoningGuard.push(raw) : raw;
10685
+ if (!delta) break;
10686
+ reasoningText += delta;
10687
+ yield { type: "reasoning-delta", text: delta };
10688
+ if (reasoningRepetition.push(delta)) looping = reasoningRepetition;
10689
+ break;
10690
+ }
10691
+ case "tool-call":
10692
+ if (chunk.toolCall) {
10693
+ toolCalls.push({
10694
+ toolCallId: chunk.toolCall.toolCallId,
10695
+ toolName: chunk.toolCall.toolName,
10696
+ input: chunk.toolCall.input
10697
+ });
10698
+ }
10699
+ break;
10700
+ case "finish":
10701
+ if (chunk.usage) usage = chunk.usage;
10702
+ if (chunk.finishReason) finishReason = chunk.finishReason;
10703
+ if (chunk.model) model = chunk.model;
10704
+ if (chunk.warnings) warnings = chunk.warnings;
10705
+ break;
10706
+ case "error":
10707
+ streamError = chunk.error ?? t("agent_run.unknown_provider_error");
10708
+ break;
10709
+ case "tool-result":
10710
+ break;
9873
10711
  }
9874
- case "reasoning-delta": {
9875
- const raw = chunk.textDelta ?? "";
9876
- if (!raw) break;
9877
- const delta = reasoningGuard ? reasoningGuard.push(raw) : raw;
9878
- if (!delta) break;
9879
- reasoningText += delta;
9880
- yield { type: "reasoning-delta", text: delta };
10712
+ if (looping) {
10713
+ ac.abort();
9881
10714
  break;
9882
10715
  }
9883
- case "tool-call":
9884
- if (chunk.toolCall) {
9885
- toolCalls.push({
9886
- toolCallId: chunk.toolCall.toolCallId,
9887
- toolName: chunk.toolCall.toolName,
9888
- input: chunk.toolCall.input
9889
- });
9890
- }
9891
- break;
9892
- case "finish":
9893
- if (chunk.usage) usage = chunk.usage;
9894
- if (chunk.finishReason) finishReason = chunk.finishReason;
9895
- if (chunk.model) model = chunk.model;
9896
- if (chunk.warnings) warnings = chunk.warnings;
9897
- break;
9898
- case "error":
9899
- streamError = chunk.error ?? t("agent_run.unknown_provider_error");
9900
- break;
9901
10716
  }
10717
+ } finally {
10718
+ unlink();
9902
10719
  }
9903
- if (streamError) throw new Error(streamError);
10720
+ if (streamError && !looping) throw new Error(streamError);
9904
10721
  if (guard) {
9905
10722
  const tail = guard.flush();
9906
10723
  if (tail) {
@@ -9918,7 +10735,8 @@ async function* runTurnStreaming(opts) {
9918
10735
  return {
9919
10736
  text,
9920
10737
  ...usage ? { usage } : {},
9921
- finishReason,
10738
+ finishReason: looping ? "looping" : finishReason,
10739
+ ...looping ? { loopingInfo: looping.getInfo() ?? "" } : {},
9922
10740
  ...toolCalls.length > 0 ? { toolCalls } : {},
9923
10741
  ...reasoningText ? { reasoningText } : {},
9924
10742
  ...model ? { model } : {},
@@ -9937,20 +10755,53 @@ async function* runTurnBuffered(opts) {
9937
10755
  yield { type: "text-delta", text };
9938
10756
  }
9939
10757
  const { reasoningText: _replaced, ...rest } = result;
9940
- return { ...rest, text, ...reasoningText ? { reasoningText } : {} };
10758
+ const textLoop = new RepetitionDetector();
10759
+ const reasoningLoop = new RepetitionDetector();
10760
+ textLoop.pushAll(text);
10761
+ if (reasoningText) reasoningLoop.pushAll(reasoningText);
10762
+ let looping;
10763
+ if (textLoop.isLooping()) looping = textLoop;
10764
+ else if (reasoningLoop.isLooping()) looping = reasoningLoop;
10765
+ return {
10766
+ ...rest,
10767
+ text,
10768
+ ...reasoningText ? { reasoningText } : {},
10769
+ ...looping ? { finishReason: "looping", loopingInfo: looping.getInfo() ?? "" } : {}
10770
+ };
10771
+ }
10772
+ function linkAbort(outer, ac) {
10773
+ if (!outer) return () => {
10774
+ };
10775
+ if (outer.aborted) {
10776
+ ac.abort(outer.reason);
10777
+ return () => {
10778
+ };
10779
+ }
10780
+ const onAbort = () => ac.abort(outer.reason);
10781
+ outer.addEventListener("abort", onAbort, { once: true });
10782
+ return () => outer.removeEventListener("abort", onAbort);
9941
10783
  }
9942
10784
  function filterWhole(guard, text) {
9943
10785
  if (!guard || !text) return text;
9944
10786
  return guard.push(text) + guard.flush();
9945
10787
  }
10788
+ // src/agent/types.ts
10789
+ function isAbortSignal(signal) {
10790
+ return !!signal && typeof signal.addEventListener === "function" && typeof signal.removeEventListener === "function";
10791
+ }
9946
10792
  // src/agent/loop.ts
9947
10793
  var DEFAULT_CONTEXT_LENGTH = 128e3;
10794
+ function shrank(before, after) {
10795
+ const chars = (list) => list.reduce((sum, m) => sum + m.content.length, 0);
10796
+ return chars(after) < chars(before);
10797
+ }
10798
+ var NO_OUTPUT_TEXT = "(\u65E0\u8F93\u51FA)";
9948
10799
  function imageParts(content) {
9949
10800
  return typeof content === "string" ? [] : content.filter((p) => p.type === "image");
9950
10801
  }
9951
10802
  function toolSignatureOf(config, tools) {
9952
- const direct = tools.map((t76) => t76.name).join("\0");
9953
- const declared = (config.codeModeTools?.() ?? []).map((t76) => t76.name).join("\0");
10803
+ const direct = tools.map((t78) => t78.name).join("\0");
10804
+ const declared = (config.codeModeTools?.() ?? []).map((t78) => t78.name).join("\0");
9954
10805
  return declared ? `${direct}|${declared}` : direct;
9955
10806
  }
9956
10807
  function modelSignatureOf(info) {
@@ -10110,7 +10961,7 @@ var AgentLoop = class {
10110
10961
  }
10111
10962
  async *runTurns(input, turns) {
10112
10963
  const maxTurns = turnLimitOf(input.maxTurns ?? this.config.maxTurns);
10113
- this.stallDetector.resetOnSuccess();
10964
+ this.stallDetector.reset();
10114
10965
  const rawUserText = contentToText(input.userMessage, { imagesCarriedAsParts: true });
10115
10966
  const images = imageParts(input.userMessage);
10116
10967
  const inputGate = this.config.compliance?.checkInput(rawUserText);
@@ -10152,6 +11003,7 @@ var AgentLoop = class {
10152
11003
  let lastPromptTokens = 0;
10153
11004
  let lastText = "";
10154
11005
  let imagesDroppedReported = false;
11006
+ let overflowRescued = false;
10155
11007
  let runUsage;
10156
11008
  let lastModel;
10157
11009
  const addRun = (u) => {
@@ -10194,6 +11046,8 @@ var AgentLoop = class {
10194
11046
  { role: "user", content: userContent, ...userParts ? { parts: userParts } : {} }
10195
11047
  ];
10196
11048
  const taskNotice = new TaskNoticeTracker(this.config.backgroundTasks);
11049
+ let todoNudged = false;
11050
+ let idleNudged = false;
10197
11051
  for (let turn = 1; turn <= maxTurns; turn++) {
10198
11052
  if (this.isAborted(input.signal)) {
10199
11053
  yield finish("aborted");
@@ -10214,8 +11068,8 @@ var AgentLoop = class {
10214
11068
  yield finish("budget-exceeded", stop.breach);
10215
11069
  return;
10216
11070
  }
10217
- const notice = taskNotice.notice();
10218
- if (notice) messages.push({ role: "user", content: notice });
11071
+ const notice2 = taskNotice.notice();
11072
+ if (notice2) messages.push({ role: "user", content: notice2 });
10219
11073
  this.hooks.fire("turn:before", {
10220
11074
  sessionId: input.sessionId,
10221
11075
  userMessage: transformed,
@@ -10232,11 +11086,35 @@ var AgentLoop = class {
10232
11086
  system = await this.prompt.buildSystem(input.sessionId);
10233
11087
  }
10234
11088
  if (turn === 1) {
11089
+ const contextLength = this.config.contextLength;
11090
+ let estimated = estimateRunTokens({ system, messages, tools });
11091
+ if (this.config.contextEngine && isPromptOversize(estimated, contextLength)) {
11092
+ const rescued = await this.compressNow(messages, {
11093
+ promptTokens: estimated,
11094
+ totalTokens: totalInput + totalOutput
11095
+ });
11096
+ if (rescued.messages !== messages) {
11097
+ messages = rescued.messages;
11098
+ estimated = estimateRunTokens({ system, messages, tools });
11099
+ yield {
11100
+ type: "notice",
11101
+ code: "context-compacted",
11102
+ text: t("agent_run.context_compacted")
11103
+ };
11104
+ }
11105
+ if (rescued.usage) {
11106
+ addRun(rescued.usage);
11107
+ yield* budget.toEvents(
11108
+ budget.add(rescued.usage, this.rateOf(rescued.model)),
11109
+ runUsage
11110
+ );
11111
+ }
11112
+ }
10235
11113
  const role = this.config.role?.();
10236
11114
  yield {
10237
11115
  type: "run-start",
10238
11116
  ...role ? { role: { name: role.name, ...role.source ? { source: role.source } : {} } } : {},
10239
- estimatedTokens: estimateRunTokens({ system, messages, tools })
11117
+ estimatedTokens: estimated
10240
11118
  };
10241
11119
  if (learned.length > 0) {
10242
11120
  this.learnedSkills.splice(0, learned.length);
@@ -10260,17 +11138,24 @@ var AgentLoop = class {
10260
11138
  },
10261
11139
  runSpan
10262
11140
  );
10263
- const guard = this.config.compliance?.outputGuard();
10264
- const reasoningGuard = this.config.compliance?.outputGuard();
11141
+ let guard = this.config.compliance?.outputGuard();
11142
+ let reasoningGuard = this.config.compliance?.outputGuard();
11143
+ const requestFor = (msgs) => ({
11144
+ provider: this.config.provider,
11145
+ input: {
11146
+ messages: msgs,
11147
+ tools,
11148
+ system,
11149
+ maxSteps: 1,
11150
+ ...isAbortSignal(input.signal) ? { signal: input.signal } : {}
11151
+ },
11152
+ streaming,
11153
+ ...guard ? { guard } : {},
11154
+ ...reasoningGuard ? { reasoningGuard } : {}
11155
+ });
10265
11156
  let result;
10266
11157
  try {
10267
- result = yield* runTurn({
10268
- provider: this.config.provider,
10269
- input: { messages, tools, system, maxSteps: 1 },
10270
- streaming,
10271
- ...guard ? { guard } : {},
10272
- ...reasoningGuard ? { reasoningGuard } : {}
10273
- });
11158
+ result = yield* runTurn(requestFor(messages));
10274
11159
  if (result.usage) {
10275
11160
  llmSpan.setAttribute(GEN_AI.USAGE_INPUT_TOKENS, result.usage.inputTokens);
10276
11161
  llmSpan.setAttribute(GEN_AI.USAGE_OUTPUT_TOKENS, result.usage.outputTokens);
@@ -10278,7 +11163,26 @@ var AgentLoop = class {
10278
11163
  llmSpan.setAttribute("epoch.finish_reason", result.finishReason);
10279
11164
  } catch (err) {
10280
11165
  llmSpan.recordError(err);
10281
- throw err;
11166
+ const engine = this.config.contextEngine;
11167
+ if (overflowRescued || !engine || !isContextOverflow(err)) throw err;
11168
+ overflowRescued = true;
11169
+ const rescued = await this.compressNow(messages, {
11170
+ totalTokens: totalInput + totalOutput
11171
+ });
11172
+ if (rescued.usage) {
11173
+ addRun(rescued.usage);
11174
+ yield* budget.toEvents(budget.add(rescued.usage, this.rateOf(rescued.model)), runUsage);
11175
+ }
11176
+ if (!shrank(messages, rescued.messages)) throw err;
11177
+ messages = rescued.messages;
11178
+ yield {
11179
+ type: "notice",
11180
+ code: "context-compacted",
11181
+ text: t("agent_run.context_compacted")
11182
+ };
11183
+ guard = this.config.compliance?.outputGuard();
11184
+ reasoningGuard = this.config.compliance?.outputGuard();
11185
+ result = yield* runTurn(requestFor(messages));
10282
11186
  } finally {
10283
11187
  llmSpan.end();
10284
11188
  this.telemetry.histogram(METRIC.LLM_CALL_LATENCY, Date.now() - turnStart);
@@ -10314,6 +11218,16 @@ var AgentLoop = class {
10314
11218
  this.telemetry.histogram(METRIC.TOKEN_USAGE, usage.outputTokens, {
10315
11219
  "epoch.token_type": "output"
10316
11220
  });
11221
+ if (usage.cacheHitTokens !== void 0) {
11222
+ this.telemetry.histogram(METRIC.TOKEN_USAGE, usage.cacheHitTokens, {
11223
+ "epoch.token_type": "cache_read"
11224
+ });
11225
+ }
11226
+ if (usage.cacheWriteTokens !== void 0) {
11227
+ this.telemetry.histogram(METRIC.TOKEN_USAGE, usage.cacheWriteTokens, {
11228
+ "epoch.token_type": "cache_write"
11229
+ });
11230
+ }
10317
11231
  input.onTurnUsage?.(usage);
10318
11232
  addRun(usage);
10319
11233
  lastModel = result.model;
@@ -10343,6 +11257,19 @@ var AgentLoop = class {
10343
11257
  await this.prompt.syncMemory(userText, msg2);
10344
11258
  return;
10345
11259
  }
11260
+ if (result.finishReason === "looping") {
11261
+ const msg2 = t("agent_run.looping", { info: result.loopingInfo ?? "" });
11262
+ yield { type: "text-delta", text: msg2 };
11263
+ await this.prompt.syncMemory(userText, msg2);
11264
+ this.hooks.fire("stop", {
11265
+ sessionId: input.sessionId,
11266
+ response: msg2,
11267
+ totalTokens: totalInput + totalOutput,
11268
+ turns
11269
+ });
11270
+ yield finish("looping");
11271
+ return;
11272
+ }
10346
11273
  const toolCalls = result.toolCalls ?? [];
10347
11274
  const truncated = result.finishReason === "length" && toolCalls.length === 0;
10348
11275
  if (truncated) {
@@ -10375,7 +11302,7 @@ ${finalText}` };
10375
11302
  );
10376
11303
  if (wantContinue && turn < maxTurns) {
10377
11304
  messages.push(
10378
- { role: "assistant", content: finalText || "(\u65E0\u8F93\u51FA)" },
11305
+ { role: "assistant", content: finalText || NO_OUTPUT_TEXT },
10379
11306
  {
10380
11307
  role: "user",
10381
11308
  content: systemNote(
@@ -10385,6 +11312,28 @@ ${finalText}` };
10385
11312
  );
10386
11313
  continue;
10387
11314
  }
11315
+ if (!todoNudged && turn < maxTurns) {
11316
+ const open = this.config.pendingTodos?.(input.sessionId);
11317
+ if (open !== void 0 && open > 0) {
11318
+ todoNudged = true;
11319
+ messages.push(
11320
+ { role: "assistant", content: finalText || NO_OUTPUT_TEXT },
11321
+ {
11322
+ role: "user",
11323
+ content: systemNote(unfinishedTodoNote(open))
11324
+ }
11325
+ );
11326
+ continue;
11327
+ }
11328
+ }
11329
+ if (!idleNudged && turn < maxTurns && !truncated && announcedNextStep(finalText)) {
11330
+ idleNudged = true;
11331
+ messages.push(
11332
+ { role: "assistant", content: finalText || NO_OUTPUT_TEXT },
11333
+ { role: "user", content: systemNote(announcedButIdleNote()) }
11334
+ );
11335
+ continue;
11336
+ }
10388
11337
  this.hooks.fire("stop", {
10389
11338
  sessionId: input.sessionId,
10390
11339
  response: finalText,
@@ -10418,7 +11367,10 @@ ${finalText}` };
10418
11367
  (onOutput) => this.toolExecutor.execute(tc, input.sessionId, input.signal, runSpan, onOutput)
10419
11368
  );
10420
11369
  executions.push(exec);
10421
- this.stallDetector.recordAction(exec.toolName, exec.args);
11370
+ this.stallDetector.recordAction(exec.toolName, exec.args, {
11371
+ success: exec.success,
11372
+ output: exec.output
11373
+ });
10422
11374
  for (const d of exec.diffs ?? []) {
10423
11375
  yield {
10424
11376
  type: "diff",
@@ -10543,7 +11495,11 @@ ${finalText}` };
10543
11495
  }
10544
11496
  async maybeCompress(messages, lastPromptTokens, totalTokens2) {
10545
11497
  const engine = this.config.contextEngine;
10546
- if (!engine?.shouldCompress(lastPromptTokens, this.config.contextLength)) return { messages };
11498
+ if (!engine) return { messages };
11499
+ const oversize = isPromptOversize(lastPromptTokens, this.config.contextLength);
11500
+ if (!oversize && !engine.shouldCompress(lastPromptTokens, this.config.contextLength)) {
11501
+ return { messages };
11502
+ }
10547
11503
  return this.compressNow(messages, { totalTokens: totalTokens2, promptTokens: lastPromptTokens });
10548
11504
  }
10549
11505
  async compactNow(messages, instruction) {
@@ -10643,6 +11599,14 @@ var CompressionSchema = z.object({
10643
11599
  enabled: z.boolean().default(true),
10644
11600
  threshold: z.number().gt(0).max(1).default(DEFAULT_COMPRESSION_THRESHOLD)
10645
11601
  });
11602
+ var DEFAULT_MEMORY_CHAR_LIMIT = 2e4;
11603
+ var DEFAULT_USER_CHAR_LIMIT = 6e3;
11604
+ var DEFAULT_MEMORY_INDEX_CHAR_LIMIT = 4e3;
11605
+ var MemorySchema = z.object({
11606
+ memoryCharLimit: z.int().min(1).default(DEFAULT_MEMORY_CHAR_LIMIT),
11607
+ userCharLimit: z.int().min(1).default(DEFAULT_USER_CHAR_LIMIT),
11608
+ indexCharLimit: z.int().min(1).default(DEFAULT_MEMORY_INDEX_CHAR_LIMIT)
11609
+ });
10646
11610
  var SandboxSchema = z.object({
10647
11611
  terminal: z.boolean().default(true)
10648
11612
  });
@@ -10686,7 +11650,7 @@ var StatusLineSchema = z.object({
10686
11650
  command: z.string().min(1).optional(),
10687
11651
  intervalMs: z.int().min(500).max(3e5).optional()
10688
11652
  });
10689
- var DisplaySchema = z.object({
11653
+ var DisplaySchema2 = z.object({
10690
11654
  language: z.enum(LANGS).optional()
10691
11655
  });
10692
11656
  var SidebarMenuItemSchema = z.object({
@@ -10754,6 +11718,7 @@ var EpochConfigSchema = z.object({
10754
11718
  tools: ToolsSchema.prefault({}),
10755
11719
  skills: SkillsSchema.optional(),
10756
11720
  compression: CompressionSchema.prefault({}),
11721
+ memory: MemorySchema.prefault({}),
10757
11722
  artifacts: ArtifactsSchema.prefault({}),
10758
11723
  budget: BudgetSchema.optional(),
10759
11724
  compliance: ComplianceSchema.optional(),
@@ -10762,7 +11727,7 @@ var EpochConfigSchema = z.object({
10762
11727
  search: SearchSchema.optional(),
10763
11728
  sessionSearch: SessionSearchSchema.optional(),
10764
11729
  statusLine: StatusLineSchema.optional(),
10765
- display: DisplaySchema.optional(),
11730
+ display: DisplaySchema2.optional(),
10766
11731
  brand: z.string().min(1).optional(),
10767
11732
  sidebarMenu: z.array(SidebarMenuItemSchema).min(1).refine((items) => new Set(items.map((item) => item.id)).size === items.length, {
10768
11733
  message: "sidebarMenu: id must be unique"
@@ -11447,64 +12412,6 @@ function fileStamp(path) {
11447
12412
  function parseKeys(raw) {
11448
12413
  return raw.split(",").map((s) => s.trim()).filter(Boolean).map((k) => ({ apiKey: k }));
11449
12414
  }
11450
- // src/provider/errors.ts
11451
- var ProviderError = class extends Error {
11452
- constructor(message, code, recoverable, recovery = "abort") {
11453
- super(message);
11454
- this.code = code;
11455
- this.recoverable = recoverable;
11456
- this.recovery = recovery;
11457
- this.name = "ProviderError";
11458
- }
11459
- code;
11460
- recoverable;
11461
- recovery;
11462
- };
11463
- var PROVIDER_NO_CREDENTIAL = 1001;
11464
- function extractStatus(err) {
11465
- if (!err || typeof err !== "object") return void 0;
11466
- const e = err;
11467
- for (const candidate of [e.statusCode, e.status, e.response?.status]) {
11468
- if (typeof candidate === "number" && candidate >= 100 && candidate < 600) return candidate;
11469
- }
11470
- return void 0;
11471
- }
11472
- function extractStatusFromMessage(msg) {
11473
- const leading = msg.match(/^\s*\[?(4\d{2}|5\d{2})\b/);
11474
- if (leading?.[1]) return Number(leading[1]);
11475
- const labelled = msg.match(/(?:status(?:\s*code)?|http|code)\D{0,3}(4\d{2}|5\d{2})\b/i);
11476
- if (labelled?.[1]) return Number(labelled[1]);
11477
- return void 0;
11478
- }
11479
- function classifyProviderError(err) {
11480
- const msg = err instanceof Error ? err.message : String(err);
11481
- const status = extractStatus(err);
11482
- if (/context.?length|context.?window|maximum.*token|token.?limit|too.?many.?tokens|prompt.*too.?long|reduce.*(length|tokens)/i.test(
11483
- msg
11484
- ))
11485
- return { code: 1030, recovery: "abort" };
11486
- if (/invalid.*api.?key|unauthorized|authentication|permission.?denied|forbidden/i.test(msg))
11487
- return { code: 1001, recovery: "rotate_credential" };
11488
- if (/billing|quota.*exceeded|insufficient.*(balance|credit)|payment.?required/i.test(msg))
11489
- return { code: 1012, recovery: "switch_provider" };
11490
- if (/rate.?limit|too.?many.?requests/i.test(msg)) return { code: 1010, recovery: "retry" };
11491
- if (/model.?not.?found|model.*does.*not.*exist|unknown.?model|no.?such.?model/i.test(msg))
11492
- return { code: 1040, recovery: "switch_model" };
11493
- if (/overloaded|server.?error|bad.?gateway|service.?unavailable/i.test(msg))
11494
- return { code: 1020, recovery: "retry" };
11495
- if (/timeout|timed.?out|etimedout|econnreset|enotfound|socket.?hang.?up/i.test(msg))
11496
- return { code: 1041, recovery: "retry" };
11497
- const code = status ?? extractStatusFromMessage(msg);
11498
- if (code !== void 0) {
11499
- if (code === 401 || code === 403) return { code: 1001, recovery: "rotate_credential" };
11500
- if (code === 402) return { code: 1012, recovery: "switch_provider" };
11501
- if (code === 404) return { code: 1040, recovery: "switch_model" };
11502
- if (code === 429) return { code: 1010, recovery: "retry" };
11503
- if (code >= 500) return { code: 1020, recovery: "retry" };
11504
- if (code >= 400) return { code: 1050, recovery: "abort" };
11505
- }
11506
- return { code: 9999, recovery: "retry" };
11507
- }
11508
12415
  // src/provider/fallback.ts
11509
12416
  var DEFAULT_MODELS = {
11510
12417
  openai: "gpt-4o-mini",
@@ -12256,6 +13163,7 @@ var ProviderRouter = class {
12256
13163
  const { code, recovery } = classifyProviderError(err);
12257
13164
  const message = err instanceof Error ? err.message : String(err);
12258
13165
  const stay = { kind: "continue", ct: state.ct, cm: state.cm };
13166
+ if (code === PROVIDER_ABORTED) return { kind: "give-up", message, code };
12259
13167
  if (recovery === "rotate_credential" && this.rotateCredential())
12260
13168
  return { ...stay, attempt: state.attempt + 1 };
12261
13169
  if (recovery === "retry") {
@@ -12326,6 +13234,7 @@ var ProviderRouter = class {
12326
13234
  system: input.system,
12327
13235
  stopWhen: isStepCount(input.maxSteps ?? 1),
12328
13236
  ...input.maxTokens ? { maxOutputTokens: input.maxTokens } : {},
13237
+ ...input.signal ? { abortSignal: input.signal } : {},
12329
13238
  output,
12330
13239
  providerOptions: toSdkProviderOptions(input.providerOptions)
12331
13240
  });
@@ -12382,6 +13291,7 @@ var ProviderRouter = class {
12382
13291
  system: input.system,
12383
13292
  stopWhen: isStepCount(input.maxSteps ?? 1),
12384
13293
  ...input.maxTokens ? { maxOutputTokens: input.maxTokens } : {},
13294
+ ...input.signal ? { abortSignal: input.signal } : {},
12385
13295
  output,
12386
13296
  providerOptions: toSdkProviderOptions(input.providerOptions)
12387
13297
  });
@@ -12661,6 +13571,9 @@ var SETTING_WRITE_KEYS = [
12661
13571
  "contextLength",
12662
13572
  "compression.enabled",
12663
13573
  "compression.threshold",
13574
+ "memory.memoryCharLimit",
13575
+ "memory.userCharLimit",
13576
+ "memory.indexCharLimit",
12664
13577
  "budget.maxCostUsd",
12665
13578
  "budget.maxTokens",
12666
13579
  "budget.warnAtPercent",
@@ -12719,6 +13632,9 @@ var VALUE_SCHEMA = {
12719
13632
  contextLength: EpochConfigSchema.shape.contextLength.unwrap(),
12720
13633
  "compression.enabled": EpochConfigSchema.shape.compression.unwrap().shape.enabled.unwrap(),
12721
13634
  "compression.threshold": EpochConfigSchema.shape.compression.unwrap().shape.threshold.unwrap(),
13635
+ "memory.memoryCharLimit": EpochConfigSchema.shape.memory.unwrap().shape.memoryCharLimit.unwrap(),
13636
+ "memory.userCharLimit": EpochConfigSchema.shape.memory.unwrap().shape.userCharLimit.unwrap(),
13637
+ "memory.indexCharLimit": EpochConfigSchema.shape.memory.unwrap().shape.indexCharLimit.unwrap(),
12722
13638
  "budget.maxCostUsd": EpochConfigSchema.shape.budget.unwrap().shape.maxCostUsd.unwrap(),
12723
13639
  "budget.maxTokens": EpochConfigSchema.shape.budget.unwrap().shape.maxTokens.unwrap(),
12724
13640
  "budget.warnAtPercent": EpochConfigSchema.shape.budget.unwrap().shape.warnAtPercent.unwrap(),
@@ -13131,16 +14047,16 @@ function buildSearchQuery(query) {
13131
14047
  }
13132
14048
  const latinPart = cleaned.replace(CJK_RUN, " ");
13133
14049
  for (const w of latinPart.split(/\s+/)) {
13134
- const t76 = w.trim();
13135
- if (t76.length >= 2) terms.add(t76);
14050
+ const t78 = w.trim();
14051
+ if (t78.length >= 2) terms.add(t78);
13136
14052
  }
13137
14053
  if (terms.size === 0) return null;
13138
- return [...terms].map((t76) => `"${t76.replace(/"/g, "")}"`).join(" OR ");
14054
+ return [...terms].map((t78) => `"${t78.replace(/"/g, "")}"`).join(" OR ");
13139
14055
  }
13140
14056
  // src/memory/db.ts
13141
- var NS = "memory";
14057
+ var NS2 = "memory";
13142
14058
  var FTS_SCHEMA_VERSION = 2;
13143
- var MIGRATIONS = [
14059
+ var MIGRATIONS2 = [
13144
14060
  {
13145
14061
  id: "memory-baseline",
13146
14062
  version: 1,
@@ -13177,7 +14093,7 @@ var MemoryDB = class {
13177
14093
  constructor(dbPath) {
13178
14094
  this.path = dbPath;
13179
14095
  this.db = openDatabase(this.path);
13180
- migrate(this.db, NS, MIGRATIONS);
14096
+ migrate(this.db, NS2, MIGRATIONS2);
13181
14097
  this.migrateFts();
13182
14098
  for (const target of ["memory", "user"]) {
13183
14099
  const row = this.db.prepare(
@@ -13348,12 +14264,12 @@ var MemoryDB = class {
13348
14264
  }
13349
14265
  };
13350
14266
  // src/memory/types.ts
13351
- var DEFAULT_CONFIG = {
13352
- memoryCharLimit: 2200,
13353
- userCharLimit: 1375
13354
- };
13355
14267
  function defaultMemoryConfig() {
13356
- return { ...DEFAULT_CONFIG };
14268
+ return {
14269
+ memoryCharLimit: DEFAULT_MEMORY_CHAR_LIMIT,
14270
+ userCharLimit: DEFAULT_USER_CHAR_LIMIT,
14271
+ indexCharLimit: DEFAULT_MEMORY_INDEX_CHAR_LIMIT
14272
+ };
13357
14273
  }
13358
14274
  // src/memory/manager.ts
13359
14275
  var DB_PATH = join(homedir(), ".epoch", "memory.db");
@@ -13361,6 +14277,8 @@ var MEM_DIR = join(homedir(), ".epoch", "memories");
13361
14277
  var ENTRY_DELIMITER = "\n\xA7 ";
13362
14278
  var MAX_ENTRY_CHARS = 4e3;
13363
14279
  var MAX_CONSOLIDATION_FAILURES_PER_TURN = 3;
14280
+ var SHORT_ID_HEX = 6;
14281
+ var INDEX_SUMMARY_CHARS = 80;
13364
14282
  var INJECTION_PATTERNS = [
13365
14283
  { pattern: /<\|im_start\|>/i, label: "role injection" },
13366
14284
  { pattern: /<\|im_end\|>/i, label: "role injection" },
@@ -13370,27 +14288,36 @@ var INJECTION_PATTERNS = [
13370
14288
  { pattern: /^assistant:/im, label: "role prefix" },
13371
14289
  { pattern: /^user:/im, label: "role prefix" }
13372
14290
  ];
14291
+ function contentKey(target, content) {
14292
+ return `${target}\0${content}`;
14293
+ }
14294
+ function stripUndefined(obj) {
14295
+ if (!obj) return {};
14296
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
14297
+ }
13373
14298
  var MemoryManager = class {
13374
14299
  db;
13375
14300
  config;
13376
14301
  memDir;
13377
- snapshot = { memory: "", user: "" };
14302
+ snapshot = "";
14303
+ ids = /* @__PURE__ */ new Map();
14304
+ idByContent = /* @__PURE__ */ new Map();
13378
14305
  initialized = false;
13379
14306
  consolidationFailures = 0;
13380
14307
  reviewIssues = [];
13381
- constructor(dbPath, memDir) {
14308
+ constructor(dbPath, memDir, config) {
13382
14309
  const dp = dbPath ?? DB_PATH;
13383
14310
  const md = memDir ?? MEM_DIR;
13384
14311
  this.memDir = md;
13385
14312
  mkdirSync(dirname(dp), { recursive: true });
13386
14313
  mkdirSync(this.memDir, { recursive: true });
13387
14314
  this.db = new MemoryDB(dp);
13388
- this.config = defaultMemoryConfig();
14315
+ this.config = { ...defaultMemoryConfig(), ...stripUndefined(config) };
13389
14316
  }
13390
14317
  async initialize(_sessionId) {
13391
14318
  this.initialized = true;
13392
14319
  await this.reloadFromDisk();
13393
- return this.buildSnapshotBlock();
14320
+ return this.snapshot;
13394
14321
  }
13395
14322
  async prefetch(userQuery) {
13396
14323
  if (!this.initialized) return "";
@@ -13412,10 +14339,24 @@ var MemoryManager = class {
13412
14339
  const safe = deduped.map((c) => this.scanInjection(c)).filter((c) => c.length > 0);
13413
14340
  this.syncTarget(target, safe);
13414
14341
  }
13415
- this.snapshot = {
13416
- memory: this.renderBlock("memory", this.db.getAllContent("memory")),
13417
- user: this.renderBlock("user", this.db.getAllContent("user"))
13418
- };
14342
+ this.assignIds();
14343
+ this.snapshot = this.renderIndexBlock();
14344
+ }
14345
+ assignIds() {
14346
+ this.ids.clear();
14347
+ this.idByContent.clear();
14348
+ for (const target of ["memory", "user"]) {
14349
+ const prefix = target === "memory" ? "m" : "u";
14350
+ for (const content of this.db.getAllContent(target)) {
14351
+ const hash = createHash("sha256").update(content).digest("hex");
14352
+ let id = `${prefix}:${hash.slice(0, SHORT_ID_HEX)}`;
14353
+ for (let len = SHORT_ID_HEX + 4; this.ids.has(id) && len <= hash.length; len += 4) {
14354
+ id = `${prefix}:${hash.slice(0, len)}`;
14355
+ }
14356
+ this.ids.set(id, { target, content });
14357
+ this.idByContent.set(contentKey(target, content), id);
14358
+ }
14359
+ }
13419
14360
  }
13420
14361
  syncTarget(target, desired) {
13421
14362
  const existing = new Map(this.db.getAll(target).map((e) => [e.content, e]));
@@ -13446,6 +14387,7 @@ var MemoryManager = class {
13446
14387
  async flushToDisk() {
13447
14388
  writeFileSync(join(this.memDir, "MEMORY.md"), this.renderFile("memory"), "utf-8");
13448
14389
  writeFileSync(join(this.memDir, "USER.md"), this.renderFile("user"), "utf-8");
14390
+ this.assignIds();
13449
14391
  }
13450
14392
  renderFile(target) {
13451
14393
  const entries = this.db.getAllContent(target);
@@ -13456,7 +14398,7 @@ var MemoryManager = class {
13456
14398
  const results = this.db.searchFts(query, void 0, limit);
13457
14399
  for (const r of results) this.db.markAccessed(r.id);
13458
14400
  return results.map((r) => ({
13459
- id: r.id,
14401
+ id: this.idByContent.get(contentKey(r.target, r.content)) ?? r.id,
13460
14402
  content: r.content,
13461
14403
  target: r.target,
13462
14404
  score: r.score
@@ -13465,6 +14407,22 @@ var MemoryManager = class {
13465
14407
  async keywordSearch(query, limit) {
13466
14408
  return this.search(query, limit);
13467
14409
  }
14410
+ async read(ids) {
14411
+ const found = [];
14412
+ const missing = [];
14413
+ for (const raw of ids) {
14414
+ const id = raw.trim();
14415
+ const hit = this.ids.get(id);
14416
+ if (!hit) {
14417
+ missing.push(id);
14418
+ continue;
14419
+ }
14420
+ found.push({ id, target: hit.target, content: hit.content });
14421
+ const entry = this.db.findByContent(hit.target, hit.content);
14422
+ if (entry) this.db.markAccessed(entry.id);
14423
+ }
14424
+ return { found, missing };
14425
+ }
13468
14426
  getAllEntries() {
13469
14427
  return [
13470
14428
  ...this.db.getAllContent("memory").map((c) => ({ content: c })),
@@ -13516,9 +14474,9 @@ var MemoryManager = class {
13516
14474
  return { success: true, done: true };
13517
14475
  }
13518
14476
  async replace(target, oldText, content) {
13519
- const existing = this.db.findByContent(target, oldText);
14477
+ const existing = this.locate(target, oldText);
13520
14478
  if (!existing) {
13521
- return { success: false, done: true, error: "Entry not found" };
14479
+ return { success: false, done: true, error: this.notFoundHint(target, oldText) };
13522
14480
  }
13523
14481
  const budget = this.getBudget()[target];
13524
14482
  const delta = content.length - existing.charLength;
@@ -13533,7 +14491,7 @@ var MemoryManager = class {
13533
14491
  consolidateHint: this.buildConsolidateHint(target)
13534
14492
  };
13535
14493
  }
13536
- this.db.replace(target, oldText, {
14494
+ this.db.replace(target, existing.content, {
13537
14495
  ...existing,
13538
14496
  content,
13539
14497
  charLength: content.length,
@@ -13544,8 +14502,24 @@ var MemoryManager = class {
13544
14502
  return { success: true, done: true };
13545
14503
  }
13546
14504
  async remove(target, oldText) {
13547
- this.db.remove(target, oldText);
14505
+ const existing = this.locate(target, oldText);
14506
+ if (!existing) return false;
14507
+ this.db.remove(target, existing.content);
14508
+ this.consolidationFailures = 0;
13548
14509
  await this.flushToDisk();
14510
+ return true;
14511
+ }
14512
+ locate(target, ref) {
14513
+ const key = ref.trim();
14514
+ const byId = this.ids.get(key);
14515
+ if (byId && byId.target === target) {
14516
+ return this.db.findByContent(target, byId.content);
14517
+ }
14518
+ return this.db.findByContent(target, key);
14519
+ }
14520
+ notFoundHint(target, ref) {
14521
+ const looksLikeId = /^[mu]:[0-9a-f]{6,}$/.test(ref.trim());
14522
+ return looksLikeId ? `No entry with id "${ref}" in ${target}. The index is frozen at session start; this entry may have been changed or removed since. Use memory(action="search") to find it again.` : `No entry in ${target} matches that exact text. \u26A0\uFE0F old_text must be the FULL text of an entry \u2014 the MEMORY INDEX only shows a truncated first line, so copying from it will not match. Pass the entry's id instead (e.g. id="m:a1b2c3"), or memory(action="read") it first to get the full text.`;
13549
14523
  }
13550
14524
  getBudget() {
13551
14525
  const mem = this.db.getBudget("memory");
@@ -13602,35 +14576,101 @@ var MemoryManager = class {
13602
14576
  }
13603
14577
  return content;
13604
14578
  }
13605
- renderBlock(target, entries) {
13606
- if (entries.length === 0) return "";
13607
- const content = entries.join(ENTRY_DELIMITER);
13608
- const budget = this.getBudget()[target];
13609
- const header = target === "user" ? `USER PROFILE (who the user is) [${budget.percent}% \u2014 ${budget.current.toLocaleString()}/${budget.limit.toLocaleString()} chars]` : `MEMORY (your personal notes) [${budget.percent}% \u2014 ${budget.current.toLocaleString()}/${budget.limit.toLocaleString()} chars]`;
14579
+ indexEntries(target) {
14580
+ const out = [];
14581
+ for (const content of this.db.getAllContent(target)) {
14582
+ const id = this.idByContent.get(contentKey(target, content));
14583
+ if (!id) continue;
14584
+ const firstLine4 = (content.split("\n")[0] ?? "").trim();
14585
+ const summary = firstLine4.length > INDEX_SUMMARY_CHARS ? `${firstLine4.slice(0, INDEX_SUMMARY_CHARS)}\u2026` : firstLine4;
14586
+ out.push({ id, target, summary, charLength: content.length });
14587
+ }
14588
+ return out;
14589
+ }
14590
+ renderIndexBlock() {
14591
+ const all = [...this.indexEntries("memory"), ...this.indexEntries("user")];
14592
+ if (all.length === 0) return "";
14593
+ const order = new Map(all.map((e, i) => [e.id, i]));
14594
+ const heat = /* @__PURE__ */ new Map();
14595
+ for (const row of this.db.getAll()) {
14596
+ const id = this.idByContent.get(contentKey(row.target, row.content));
14597
+ if (id) heat.set(id, { count: row.accessCount, at: row.lastAccessedAt?.getTime() ?? 0 });
14598
+ }
14599
+ const rank = (id) => heat.get(id) ?? { count: 0, at: 0 };
14600
+ const ranked = [...all].sort(
14601
+ (a, b) => rank(b.id).count - rank(a.id).count || rank(b.id).at - rank(a.id).at || order.get(a.id) - order.get(b.id)
14602
+ );
14603
+ const kept = [];
14604
+ let used = 0;
14605
+ for (const e of ranked) {
14606
+ const line = this.indexLine(e);
14607
+ if (used + line.length + 1 > this.config.indexCharLimit) continue;
14608
+ used += line.length + 1;
14609
+ kept.push(e);
14610
+ }
14611
+ kept.sort((a, b) => order.get(a.id) - order.get(b.id));
14612
+ const omitted = all.length - kept.length;
14613
+ const sections = [];
14614
+ for (const target of ["memory", "user"]) {
14615
+ const rows = kept.filter((e) => e.target === target);
14616
+ if (rows.length === 0) continue;
14617
+ const budget = this.getBudget()[target];
14618
+ const label = target === "user" ? "USER PROFILE (who the user is)" : "MEMORY (your personal notes)";
14619
+ sections.push(
14620
+ [
14621
+ `${label} [${rows.length} entries \xB7 ${budget.current.toLocaleString()}/${budget.limit.toLocaleString()} chars]`,
14622
+ ...rows.map((e) => this.indexLine(e))
14623
+ ].join("\n")
14624
+ );
14625
+ }
13610
14626
  const separator = "\u2550".repeat(46);
13611
- const note = "[System note: The following is recalled memory context, NOT new user input. Treat as authoritative reference data.]";
13612
- return `${separator}
13613
- ${header}
13614
- ${separator}
13615
- ${note}
13616
- <memory-context>
13617
- \xA7 ${content}
13618
- </memory-context>`;
13619
- }
13620
- buildSnapshotBlock() {
13621
- const parts = [];
13622
- if (this.snapshot.memory) parts.push(this.snapshot.memory);
13623
- if (this.snapshot.user) parts.push(this.snapshot.user);
13624
- return parts.join("\n\n");
14627
+ const note = "[System note: This is an INDEX of your stored memory, NOT new user input. Only one-line summaries are listed here \u2014 the full text is not in this prompt.]";
14628
+ const how = [
14629
+ 'To read an entry in full: memory(action="read", ids=["m:a1b2c3"]).',
14630
+ 'To find something not listed: memory(action="search", query="...").'
14631
+ ];
14632
+ if (omitted > 0) {
14633
+ how.unshift(
14634
+ `\u26A0\uFE0F ${omitted} more ${omitted === 1 ? "entry is" : "entries are"} stored but NOT listed above (index budget). Do not conclude something is absent from memory without searching first.`
14635
+ );
14636
+ }
14637
+ return [
14638
+ separator,
14639
+ "MEMORY INDEX",
14640
+ separator,
14641
+ note,
14642
+ "<memory-index>",
14643
+ sections.join("\n\n"),
14644
+ "</memory-index>",
14645
+ ...how
14646
+ ].join("\n");
14647
+ }
14648
+ indexLine(e) {
14649
+ return `${e.id} ${e.summary} (${e.charLength} chars)`;
13625
14650
  }
13626
14651
  buildConsolidateHint(target) {
13627
14652
  const budget = this.getBudget()[target];
14653
+ const heat = /* @__PURE__ */ new Map();
14654
+ for (const row of this.db.getAll(target)) {
14655
+ const id = this.idByContent.get(contentKey(row.target, row.content));
14656
+ if (id) heat.set(id, row.accessCount);
14657
+ }
14658
+ const candidates2 = this.indexEntries(target).sort((a, b) => (heat.get(a.id) ?? 0) - (heat.get(b.id) ?? 0) || b.charLength - a.charLength).slice(0, 5);
13628
14659
  return [
13629
14660
  `Memory at ${budget.current.toLocaleString()}/${budget.limit.toLocaleString()} chars (${budget.percent}%) for ${target}. Cannot add new entry.`,
13630
- "To make room, use replace() to combine or shorten existing entries. For example:",
13631
- "- Merge '\u7528\u6237\u7528 pnpm' and '\u7528\u6237\u7528 pnpm v9' into '\u7528\u6237\u4F7F\u7528 pnpm v9'",
13632
- "- Shorten verbose entries to their essence",
13633
14661
  "",
14662
+ "To make room, work by id \u2014 NOT by old_text. The MEMORY INDEX only shows a truncated",
14663
+ "first line, so text copied from it will never match an entry exactly.",
14664
+ "",
14665
+ ` memory(action="remove", target="${target}", id="m:a1b2c3") drop an obsolete entry`,
14666
+ ` memory(action="replace", target="${target}", id="m:a1b2c3", content="...") shorten or merge one`,
14667
+ ' memory(action="read", ids=["m:a1b2c3"]) see the full text first',
14668
+ "",
14669
+ ...candidates2.length > 0 ? [
14670
+ "Least-used entries in this store (cheapest to drop or shorten):",
14671
+ ...candidates2.map((e) => ` ${e.id} ${e.summary} (${e.charLength} chars)`),
14672
+ ""
14673
+ ] : [],
13634
14674
  `Per-turn consolidation limit: ${MAX_CONSOLIDATION_FAILURES_PER_TURN} attempts. After that, stop and proceed.`
13635
14675
  ].join("\n");
13636
14676
  }
@@ -14115,7 +15155,7 @@ function encodeTurnFacts(facts) {
14115
15155
  return facts ? JSON.stringify(facts) : null;
14116
15156
  }
14117
15157
  // src/session/db.ts
14118
- var NS2 = "session";
15158
+ var NS3 = "session";
14119
15159
  var log2 = createLogger("session-db");
14120
15160
  var META_COL_MAP = {
14121
15161
  displayName: "display_name",
@@ -14401,13 +15441,13 @@ var SessionDB = class {
14401
15441
  constructor(dbPath$1) {
14402
15442
  this.path = dbPath$1 ?? dbPath();
14403
15443
  this.db = openDatabase(this.path);
14404
- migrate(this.db, NS2, SESSION_MIGRATIONS);
15444
+ migrate(this.db, NS3, SESSION_MIGRATIONS);
14405
15445
  this.migrateFtsIndex();
14406
15446
  this.refreshMsgId();
14407
15447
  }
14408
15448
  migrateFtsIndex() {
14409
15449
  try {
14410
- migrate(this.db, NS2, FTS_EXTERNAL_MIGRATIONS);
15450
+ migrate(this.db, NS3, FTS_EXTERNAL_MIGRATIONS);
14411
15451
  } catch (err) {
14412
15452
  log2.warn(
14413
15453
  "FTS index rebuild (v8) failed; keeping the legacy schema for now (content stored three times, ~36% larger). Will retry on next start.",
@@ -14892,7 +15932,7 @@ function checkFtsHealth(db) {
14892
15932
  messages,
14893
15933
  tables,
14894
15934
  missingTriggers,
14895
- healthy: missingTriggers.length === 0 && tables.every((t76) => t76.error === null && t76.drift === null && t76.rows === messages),
15935
+ healthy: missingTriggers.length === 0 && tables.every((t78) => t78.error === null && t78.drift === null && t78.rows === messages),
14896
15936
  indexBytes: indexBytesOf(db),
14897
15937
  space: spaceOf(db)
14898
15938
  };
@@ -15707,18 +16747,7 @@ function checkCodeExec(isolation) {
15707
16747
  }
15708
16748
  function checkAuto(operation, isolation) {
15709
16749
  if (operation.type === "code_exec") return checkCodeExec(isolation);
15710
- if (operation.type === "file_read") return { allowed: true, code: "level-default" };
15711
- if (operation.type === "file_write") return { allowed: true, code: "level-default" };
15712
- if (operation.type === "network") return { allowed: true, code: "level-default" };
15713
- if (operation.type === "command" && isReadOnlyCommand(operation.detail)) {
15714
- return { allowed: true, code: "level-default" };
15715
- }
15716
- return {
15717
- allowed: false,
15718
- requireApproval: true,
15719
- reason: t("permission_reason.write_command", { command: operation.detail.slice(0, 50) }),
15720
- code: "level-default"
15721
- };
16750
+ return { allowed: true, code: "level-default" };
15722
16751
  }
15723
16752
  function checkPlan(operation) {
15724
16753
  if (operation.type === "code_exec") {
@@ -15825,9 +16854,17 @@ function isSensitivePath(target) {
15825
16854
  function checkHardLimits(operation) {
15826
16855
  const danger = checkDangerousCommand(operation.detail);
15827
16856
  if (danger.dangerous) {
16857
+ if (danger.hardline) {
16858
+ return {
16859
+ allowed: false,
16860
+ reason: t("permission_reason.dangerous", { desc: danger.desc ?? "" }),
16861
+ code: "dangerous-command"
16862
+ };
16863
+ }
15828
16864
  return {
15829
16865
  allowed: false,
15830
- reason: t("permission_reason.dangerous", { desc: danger.desc ?? "" }),
16866
+ requireApproval: true,
16867
+ reason: t("permission_reason.dangerous_recoverable", { desc: danger.desc ?? "" }),
15831
16868
  code: "dangerous-command"
15832
16869
  };
15833
16870
  }
@@ -15960,11 +16997,11 @@ var ApprovalCache = class {
15960
16997
  return !rel.startsWith("..") && !isAbsolute(rel);
15961
16998
  }
15962
16999
  normalizeTarget(target) {
15963
- const t76 = target.trim();
15964
- if (!t76) return "";
15965
- const unix = t76.replace(/\\/g, "/");
17000
+ const t78 = target.trim();
17001
+ if (!t78) return "";
17002
+ const unix = t78.replace(/\\/g, "/");
15966
17003
  if (unix.startsWith("/") || /^[A-Za-z]:\//.test(unix)) return resolve(unix);
15967
- return t76;
17004
+ return t78;
15968
17005
  }
15969
17006
  load() {
15970
17007
  try {
@@ -16067,9 +17104,9 @@ function createSharedPermissionState(cache) {
16067
17104
  };
16068
17105
  }
16069
17106
  function cacheKeyFor(raw) {
16070
- const t76 = raw.trim();
16071
- if (!t76) return null;
16072
- return t76.slice(0, 500);
17107
+ const t78 = raw.trim();
17108
+ if (!t78) return null;
17109
+ return t78.slice(0, 500);
16073
17110
  }
16074
17111
  // src/permission/manager.ts
16075
17112
  var log3 = createLogger("permission");
@@ -16164,12 +17201,9 @@ var PermissionManager = class _PermissionManager {
16164
17201
  decide(operation) {
16165
17202
  const hard = checkHardLimits(operation);
16166
17203
  if (hard) {
16167
- if (hard.requireApproval && this.shared.nonInteractive) {
16168
- return {
16169
- allowed: false,
16170
- reason: t("permission_reason.non_interactive_denied", { reason: hard.reason ?? "" }),
16171
- code: hard.code
16172
- };
17204
+ if (hard.requireApproval) {
17205
+ const settled = this.settleUnaskable(operation, hard);
17206
+ if (settled) return settled;
16173
17207
  }
16174
17208
  return hard;
16175
17209
  }
@@ -16177,6 +17211,58 @@ var PermissionManager = class _PermissionManager {
16177
17211
  if (decision.allowed || !decision.requireApproval) return decision;
16178
17212
  return this.applyPreauthorization(operation, decision);
16179
17213
  }
17214
+ settleUnaskable(operation, hard) {
17215
+ if (this.level === "plan") {
17216
+ return {
17217
+ allowed: false,
17218
+ reason: t("permission_reason.plan_no_write"),
17219
+ code: hard.code
17220
+ };
17221
+ }
17222
+ if (this.level === "bypass" && hard.code === "obfuscated") {
17223
+ return {
17224
+ allowed: true,
17225
+ reason: t("permission_reason.bypass_unaskable", { reason: hard.reason ?? "" }),
17226
+ code: hard.code
17227
+ };
17228
+ }
17229
+ const cached = this.cachedDecision(operation);
17230
+ if (cached) return cached;
17231
+ if (this.shared.nonInteractive) {
17232
+ return {
17233
+ allowed: false,
17234
+ reason: t("permission_reason.non_interactive_denied", { reason: hard.reason ?? "" }),
17235
+ code: hard.code
17236
+ };
17237
+ }
17238
+ return null;
17239
+ }
17240
+ approvalCacheActive() {
17241
+ return this.level !== "bypass";
17242
+ }
17243
+ rememberFlag() {
17244
+ return this.approvalCacheActive() ? {} : { rememberable: false };
17245
+ }
17246
+ cachedDecision(operation) {
17247
+ const cacheTool = operation.toolName ?? operation.type;
17248
+ const cacheKey = cacheKeyFor(operation.target || operation.detail);
17249
+ const cached = cacheKey ? this.shared.cache.get(cacheTool, cacheKey) : null;
17250
+ if (cached === "allow-always" || cached === "allow-session") {
17251
+ return {
17252
+ allowed: true,
17253
+ reason: t("permission_reason.cached_allow"),
17254
+ code: "approval-cache"
17255
+ };
17256
+ }
17257
+ if (cached === "deny") {
17258
+ return {
17259
+ allowed: false,
17260
+ reason: t("permission_reason.cached_deny"),
17261
+ code: "approval-cache"
17262
+ };
17263
+ }
17264
+ return null;
17265
+ }
16180
17266
  checkRulesAndCache(operation) {
16181
17267
  const listMatch = matchRuleLists(this.shared.ruleLists, operation);
16182
17268
  if (listMatch?.verdict === "deny") {
@@ -16191,34 +17277,18 @@ var PermissionManager = class _PermissionManager {
16191
17277
  if (ruleDecision && !ruleDecision.allowed && !ruleDecision.requireApproval) {
16192
17278
  return ruleDecision;
16193
17279
  }
16194
- if (this.level !== "bypass") {
16195
- const cacheTool = operation.toolName ?? operation.type;
16196
- const cacheKey = cacheKeyFor(operation.target || operation.detail);
16197
- const cached = cacheKey ? this.shared.cache.get(cacheTool, cacheKey) : null;
16198
- if (cached === "allow-always" || cached === "allow-session") {
16199
- return {
16200
- allowed: true,
16201
- reason: t("permission_reason.cached_allow"),
16202
- code: "approval-cache"
16203
- };
16204
- }
16205
- if (cached === "deny") {
16206
- return {
16207
- allowed: false,
16208
- reason: t("permission_reason.cached_deny"),
16209
- code: "approval-cache"
16210
- };
16211
- }
16212
- }
17280
+ const cached = this.approvalCacheActive() ? this.cachedDecision(operation) : null;
17281
+ if (cached) return cached;
16213
17282
  if (listMatch?.verdict === "ask") {
16214
17283
  return {
16215
17284
  allowed: false,
16216
17285
  requireApproval: true,
16217
17286
  reason: t("permission_reason.rule_ask", { rule: listMatch.rule }),
16218
- code: "rule-match"
17287
+ code: "rule-match",
17288
+ ...this.rememberFlag()
16219
17289
  };
16220
17290
  }
16221
- if (ruleDecision?.requireApproval) return ruleDecision;
17291
+ if (ruleDecision?.requireApproval) return { ...ruleDecision, ...this.rememberFlag() };
16222
17292
  if (listMatch?.verdict === "allow") {
16223
17293
  return {
16224
17294
  allowed: true,
@@ -16405,8 +17475,8 @@ function suggestRuleFromApproval(request) {
16405
17475
  ].join("\n")
16406
17476
  };
16407
17477
  }
16408
- var NS3 = "schedule";
16409
- var MIGRATIONS2 = [
17478
+ var NS4 = "schedule";
17479
+ var MIGRATIONS3 = [
16410
17480
  {
16411
17481
  id: "schedule-baseline",
16412
17482
  version: 1,
@@ -16480,7 +17550,7 @@ var ScheduleStore = class {
16480
17550
  constructor(dbPath) {
16481
17551
  this.path = dbPath;
16482
17552
  this.db = openDatabase(this.path);
16483
- migrate(this.db, NS3, MIGRATIONS2);
17553
+ migrate(this.db, NS4, MIGRATIONS3);
16484
17554
  }
16485
17555
  get connection() {
16486
17556
  return this.db;
@@ -20916,73 +21986,6 @@ function trunc(stdout, stderr) {
20916
21986
  const skipped = out.length - TRUNCATE_HEAD - TRUNCATE_TAIL;
20917
21987
  return `${head}${nl}...[\u5DF2\u622A\u65AD ${(skipped / 1024).toFixed(1)} KB]...${nl}${tail}`;
20918
21988
  }
20919
- var NS4 = "tracker";
20920
- var MIGRATIONS3 = [
20921
- {
20922
- id: "tracker-baseline",
20923
- version: 1,
20924
- description: "TODO \u8868\uFF08\u57FA\u7EBF\uFF09",
20925
- up: (db) => {
20926
- db.exec(`
20927
- CREATE TABLE IF NOT EXISTS todos (
20928
- id TEXT PRIMARY KEY,
20929
- session_id TEXT NOT NULL,
20930
- content TEXT NOT NULL,
20931
- status TEXT NOT NULL DEFAULT 'pending'
20932
- CHECK(status IN ('pending','in_progress','completed','cancelled')),
20933
- created_at TEXT NOT NULL,
20934
- updated_at TEXT NOT NULL
20935
- );
20936
- CREATE INDEX IF NOT EXISTS idx_todos_session ON todos(session_id);
20937
- `);
20938
- }
20939
- },
20940
- {
20941
- id: "todos-reason-column",
20942
- version: 2,
20943
- description: "TODO \u8868\u52A0 reason \u5217\uFF08\u53D6\u6D88\u7406\u7531\uFF0C\u53EA\u6709 cancelled \u90A3\u4E00\u6863\u4F1A\u6709\uFF09",
20944
- up: (db) => {
20945
- db.exec(`ALTER TABLE todos ADD COLUMN reason TEXT;`);
20946
- }
20947
- }
20948
- ];
20949
- var TrackerDB = class {
20950
- db;
20951
- path;
20952
- constructor(dbPath) {
20953
- this.path = dbPath;
20954
- this.db = openDatabase(this.path);
20955
- migrate(this.db, NS4, MIGRATIONS3);
20956
- }
20957
- listBySession(sessionId) {
20958
- const rows = this.db.prepare(
20959
- "SELECT id, content, status, reason, created_at, updated_at FROM todos WHERE session_id = ? ORDER BY updated_at ASC"
20960
- ).all(sessionId);
20961
- return rows.map(({ reason: reason9, ...rest }) => reason9 ? { ...rest, reason: reason9 } : rest);
20962
- }
20963
- replaceAll(sessionId, todos) {
20964
- const insertStmt = this.db.prepare(
20965
- "INSERT INTO todos (id, session_id, content, status, reason, created_at, updated_at) VALUES (@id, @session_id, @content, @status, @reason, @created_at, @updated_at)"
20966
- );
20967
- this.db.transaction(() => {
20968
- this.db.prepare("DELETE FROM todos WHERE session_id = ?").run(sessionId);
20969
- for (const t76 of todos) {
20970
- insertStmt.run({
20971
- id: t76.id,
20972
- session_id: sessionId,
20973
- content: t76.content,
20974
- status: t76.status,
20975
- reason: t76.reason || null,
20976
- created_at: t76.created_at,
20977
- updated_at: t76.updated_at
20978
- });
20979
- }
20980
- })();
20981
- }
20982
- close() {
20983
- releaseDatabase(this.path);
20984
- }
20985
- };
20986
21989
  /**
20987
21990
  * @license
20988
21991
  * Copyright 2025 Google LLC
@@ -21028,4 +22031,4 @@ var TrackerDB = class {
21028
22031
  * `--tree` 的命令帮助里,让用户自己知道 `--tree` 意味着什么。
21029
22032
  */
21030
22033
 
21031
- export { AGENT_ROLE_DIAG_MODULE, ARTIFACT_MAX_TOTAL_BYTES, ARTIFACT_RETENTION_DEFAULTS, AgentLoop, ApprovalCache, BLOCKED_TOOLS, BudgetGuard, BudgetStore, CHECKPOINT_MAX_TOTAL_BYTES, CLAUDE_EVENT_NAMES, CLAUDE_TOOL_NAMES, CODE_MODE_LIMITS, CODE_MODE_SDK_TAG, CONFIG_KEYS, CURRENT_CONFIG_VERSION, CheckpointManager, CheckpointStore, CodeSandbox, ComplianceEngine, ContextCompressor, DEFAULT_COMPLIANCE_ACTIONS, DEFAULT_COMPLIANCE_SETTINGS, DEFAULT_GOAL_MAX_ROUNDS, DEFAULT_MAX_HOLD_CHARS, DelegateManager, EMPTY_RULES, EPOCH_DISK_LEDGER, EpochConfigSchema, FTS_TABLES, FTS_TRIGGERS, GOAL_BLOCK_CODES, GoalService, GoalStore, HEADLESS_AUDIT_CODES, HookManager, INSTRUCTION_BUDGET_CHARS, INSTRUCTION_FILES, ImageLoadError, ImportTrustStore, LAUNCHD_LABEL_PREFIX, LOCK_HEARTBEAT_MS, LOCK_STALE_MS, MARKETPLACE_FILE, MAX_AUDIT_ENTRIES, MAX_COMMAND_DEPTH, MAX_GOAL_MAX_ROUNDS, MAX_IMPORT_DEPTH, MAX_OBJECTIVE_CHARS, MAX_REASON_CHARS, MAX_RECENT_WORKSPACES, MODEL_GIVE_UP_THRESHOLD, MULTIMODAL_DEFAULTS, ManagedPolicyError, ManagedSettingsSchema, MemoryManager, MemoryReviewer, ModelFailureTracker, NOMINAL_TOTAL_BYTES, NO_REVEALED_TOOLS, PERMISSION_AUDIT_CODES, PLUGIN_LAYOUT, PLUGIN_MANIFEST_FILE, PLUGIN_NAME_PATTERN, PLUGIN_ROOT_ENV, PLUGIN_ROOT_TOKEN, POLICY_DECISIONS, PROJECT_AGENTS_SUBDIR, PROVIDER_NO_CREDENTIAL, PermissionManager, PlanModeState, PluginStore, PolicyRuleSchema, ProviderError, ProviderRouter, RECORDING_KEEP_RUNS, RECORDING_MAX_BYTES, ROLE_FRONTMATTER_SCHEMA, RUN_CODE_TOOL, RecentWorkspaces, SANDBOX_COVERS, SANDBOX_EXCLUDES, SCALAR_KEYS, SCHEDULE_DEFAULTS, SCHEDULE_DEFAULT_ALLOWLIST, SCHEDULE_ISSUE_CODES, SCHTASKS_FOLDER, SESSION_MESSAGES_READ_TOOL, SESSION_MESSAGES_SEARCH_TOOL, SESSION_QUERY_TOOLS, SESSION_SEARCH_TOOL, SESSION_TRACE_TOOL, SETTINGS_LAYER_ORDER, SETTING_WRITE_APPLY, SETTING_WRITE_KEYS, SETTING_WRITE_LAYERS, SKILL_CREATE_TOOL, SKILL_EDIT_TOOL, SKILL_PATCH_TOOL, SKILL_VIEW_TOOL, SKILL_WRITE_FILE_TOOL, SLASH_COMMANDS_DIAG_MODULE, ScheduleRecorder, ScheduleRegistrar, ScheduleStore, SessionManager, SessionQueries, SessionReferences, SettingsFileError, SettingsFileSchema, SkillLearner, SkillSystem, TOOL_SEARCH_TOOL, TaskNoticeTracker, ToolRegistry, TrackerDB, TrustManager, Workspace, acquireScheduleLock, addMarketplace, addUsage, admitArtifact, admitArtifacts, affectedPaths, artifactLaunchArgv, artifactOpenRefusal, assertLevelAllowed, blockedRoleTools, buildContextBreakdown, buildFireArgv, buildFtsMatchQuery, buildSchemaExports, buildSystemPrompt, buildTrigramMatchQuery, builtinRoles, checkEpochVersion, checkFtsHealth, checkShadowing, classifyProviderError, clearModelCache, coalesceRecording, codeModeWorkerPath, commandNameFromRelPath, compileRuleLists, compressionOptions, computeCost, computeCostMicros, countRules, createAllowAllGate, createAllowAllImportGate, createAskQuestionTool, createCodeExecTool, createDelegateTool, createDenyAllGate, createDenyAllImportGate, createGoalTool, createImportGate, createMemoryTool, createPlanModeTools, createRoleFile, createRoleScope, createRunCodeTool, createScheduleDraftTool, createSessionMessagesReadTool, createSessionMessagesSearchTool, createSessionSearchTool, createSessionSearchTools, createSessionTraceTool, createSkillViewTool, createSkillWriteTools, createTodoTool, createToolRevealState, createToolScope, createToolSearchTool, createTrustGate, currentEpochVersion, decideImport, decideTrust, defaultConfig, defaultModelOf, describeArtifactRefusal, describeBackgroundTask, describeHeadlessPolicy, describeIsolation, describeJsonError, diagnoseSchedules, discoverMarkdown, discoverModels, discoverProject, draftCommandLine, draftWidening, escapeRuleContent, estimateImageTokens, estimateRunTokens, estimateTokens, evaluateSelection, expandCommand, expandImports, explainSetting, findInstructionFiles, findProjectInstructions, findRole, formatBytes, formatSkillIndex, formatUsd, gateForTool, getCapability, getConfigIssues, getConfigOrigins, getImportGate, getTrustGate, hasPromptCaching, hasProviderCredentials, imageFromBase64, imageFromPath, imagePartTokens, importSkills, installPlugin, instructionDirs, instructionNotices, intervalSlots, inventoryTotal, isBlockCode, isCodeModeDeferred, isEmptyRules, isGoalPhase, isNonInteractive, isReadOnlySubCall, isSettingWriteLayer, isValidPluginName, isWithinWindow, isWritableSettingKey, labelSlug, layerRank, listSessionCandidates, listWorkspaceFiles, loadCommandDefinitions, loadCommandFile, loadCommands, loadConfig, loadHookSources, loadKeybindings, loadLexiconDir, loadLexiconFiles, loadPlugins, loadPolicyFiles, loadPolicyRules, loadRoleDefinitions, loadRoleDir, loadedImports, localDateString, managedSwitchNotes, mapClaudeToolName, mapEpochToolName, mapOperationType, matchPreauthorization, matchRuleLists, matchesHook, mentionsPluginRoot, mergeHeadlessPolicy, mergeRoles, mergeSchedulePatch, microsToUsd, modelListCandidates, needsAllowlist, nextFallbackModel, nextRunAt, noConfigOrigins, noManagedPolicy, noPlugins, openArtifact, originOf, parseClaudeSettingsHooks, parseClock, parseDate, parseFrontmatter, parseHooksConfig, parseHooksConfigVerbose, parseImportLine, parseModelList, parseRoleDefinition, parseScheduleDraft, parseSkillFrontmatter, parseTrigger, pendingImageTokens, persistParts, persistToolResults, persistedUsage, previewSkillImport, pruneArtifacts, pruneRecordings, pruneSchedules, readImageSize, readMarketplaces, readPluginManifest, readPluginRecords, readProviderKeyEnv, readRecording, readSessionSurface, reclaimFtsSpace, redactLine, refreshOpenRouterMetadata, removeMarketplace, removeSessionArtifacts, renderImportTree, renderProjectContext, renderSdkDeclaration, repairSchedules, resolveArtifactRetention, resolveCodeModeWorker, resolveComplianceDirs, resolveEntrySource, resolveInWorkspace, resolveMarketplaceRef, resolveMentions, resolveMultimodalPolicy, resolvePolicyDirs, resolveProjectRoot, resolveScheduleBackend, resolveSessionReference, resolveSessionVisibility, resolveSettings, resolveSkillDirs, resolveSource, resolveWorkspace, restoreParts, revealScopedProvider, roleScopedProvider, roleSourceLabel, ruleCovers, ruleFromString, ruleToString, runCode, runCommand2 as runCommand, sanitizeArgNames, sanitizeCommand, sanitizeFtsQuery, sanitizePath, sanitizeToolOutput, scanInstructions, scanPluginDir, scanSkillDirs, searchMarketplaces, setImportGate, setPluginEnabled, setTrustGate, settingValueChoices, settingValueKind, settingWriteLayers, settingWriteTargets, skillIndexDescription, skillIndexResidency, skillIndexStats, sniffMediaType, splitFrontmatter, staticToolProvider, stripArtifactData, substitutePluginRoot, suggestRuleFromApproval, systemOpenArgv, systemPromptSegments, toPosix, toolDefinitionTokens, toolMatches, toolTableTokens, totalTokens, traceSession, triggerPeriodMs, unescapeRuleContent, uninstallPlugin, unknownRoleSkills, unknownRoleTools, unloadedInstructionFiles, updateMarketplace, updatePlugin, usdToMicros, validateSchedule, validateTrigger, validateWindow, withGoalCompression, withGoalPrompt, workspaceDisplayPath, writeSettingValue };
22034
+ export { AGENT_ROLE_DIAG_MODULE, ARTIFACT_MAX_TOTAL_BYTES, ARTIFACT_RETENTION_DEFAULTS, AgentLoop, ApprovalCache, BLOCKED_TOOLS, BudgetGuard, BudgetStore, CHECKPOINT_MAX_TOTAL_BYTES, CLAUDE_EVENT_NAMES, CLAUDE_TOOL_NAMES, CODE_MODE_LIMITS, CODE_MODE_SDK_TAG, CONFIG_KEYS, CURRENT_CONFIG_VERSION, CheckpointManager, CheckpointStore, CodeSandbox, ComplianceEngine, ContextCompressor, DEFAULT_COMPLIANCE_ACTIONS, DEFAULT_COMPLIANCE_SETTINGS, DEFAULT_COMPRESSION_THRESHOLD, DEFAULT_GOAL_MAX_ROUNDS, DEFAULT_MAX_HOLD_CHARS, DelegateManager, EMPTY_RULES, EPOCH_DISK_LEDGER, EpochConfigSchema, FTS_TABLES, FTS_TRIGGERS, GOAL_BLOCK_CODES, GoalService, GoalStore, HEADLESS_AUDIT_CODES, HookManager, INSTRUCTION_BUDGET_CHARS, INSTRUCTION_FILES, ImageLoadError, ImportTrustStore, LAUNCHD_LABEL_PREFIX, LOCK_HEARTBEAT_MS, LOCK_STALE_MS, MARKETPLACE_DISPLAY_FIELDS, MARKETPLACE_FILE, MAX_AUDIT_ENTRIES, MAX_COMMAND_DEPTH, MAX_GOAL_MAX_ROUNDS, MAX_IMPORT_DEPTH, MAX_OBJECTIVE_CHARS, MAX_REASON_CHARS, MAX_RECENT_WORKSPACES, MODEL_GIVE_UP_THRESHOLD, MULTIMODAL_DEFAULTS, ManagedPolicyError, ManagedSettingsSchema, MemoryManager, MemoryReviewer, ModelFailureTracker, NOMINAL_TOTAL_BYTES, NO_REVEALED_TOOLS, PACK_EXCLUDES, PACK_MAX_BYTES, PACK_MAX_FILES, PERMISSION_AUDIT_CODES, PLUGIN_LAYOUT, PLUGIN_MANIFEST_FILE, PLUGIN_NAME_PATTERN, PLUGIN_ROOT_ENV, PLUGIN_ROOT_TOKEN, POLICY_DECISIONS, PROJECT_AGENTS_SUBDIR, PROVIDER_NO_CREDENTIAL, PermissionManager, PlanModeState, PluginAuthorSchema, PluginStore, PolicyRuleSchema, ProviderError, ProviderRouter, RECORDING_KEEP_RUNS, RECORDING_MAX_BYTES, ROLE_FRONTMATTER_SCHEMA, RUN_CODE_TOOL, RecentWorkspaces, SANDBOX_COVERS, SANDBOX_EXCLUDES, SCALAR_KEYS, SCHEDULE_DEFAULTS, SCHEDULE_DEFAULT_ALLOWLIST, SCHEDULE_ISSUE_CODES, SCHTASKS_FOLDER, SESSION_MESSAGES_READ_TOOL, SESSION_MESSAGES_SEARCH_TOOL, SESSION_QUERY_TOOLS, SESSION_SEARCH_TOOL, SESSION_TRACE_TOOL, SETTINGS_LAYER_ORDER, SETTING_WRITE_APPLY, SETTING_WRITE_KEYS, SETTING_WRITE_LAYERS, SKILL_CREATE_TOOL, SKILL_EDIT_TOOL, SKILL_PATCH_TOOL, SKILL_VIEW_TOOL, SKILL_WRITE_FILE_TOOL, SLASH_COMMANDS_DIAG_MODULE, ScheduleRecorder, ScheduleRegistrar, ScheduleStore, SessionManager, SessionQueries, SessionReferences, SettingsFileError, SettingsFileSchema, SkillLearner, SkillSystem, TOOL_SEARCH_TOOL, TaskNoticeTracker, ToolRegistry, TrackerDB, TrustManager, Workspace, acquireScheduleLock, addMarketplace, addUsage, admitArtifact, admitArtifacts, affectedPaths, artifactLaunchArgv, artifactOpenRefusal, assertLevelAllowed, blockedRoleTools, buildContextBreakdown, buildFireArgv, buildFtsMatchQuery, buildSchemaExports, buildSystemPrompt, buildTrigramMatchQuery, builtinRoles, checkEpochVersion, checkFtsHealth, checkShadowing, classifyProviderError, clearModelCache, coalesceRecording, codeModeWorkerPath, commandNameFromRelPath, compileRuleLists, compressionOptions, computeCost, computeCostMicros, countRules, createAllowAllGate, createAllowAllImportGate, createAskQuestionTool, createCodeExecTool, createDelegateTool, createDenyAllGate, createDenyAllImportGate, createGoalTool, createImportGate, createMemoryTool, createPlanModeTools, createRoleFile, createRoleScope, createRunCodeTool, createScheduleDraftTool, createSessionMessagesReadTool, createSessionMessagesSearchTool, createSessionSearchTool, createSessionSearchTools, createSessionTraceTool, createSkillViewTool, createSkillWriteTools, createTodoTool, createToolRevealState, createToolScope, createToolSearchTool, createTrustGate, currentEpochVersion, decideImport, decideTrust, defaultConfig, defaultModelOf, describeArtifactRefusal, describeBackgroundTask, describeHeadlessPolicy, describeIsolation, describeJsonError, diagnoseSchedules, discoverMarkdown, discoverModels, discoverProject, draftCommandLine, draftWidening, escapeRuleContent, estimateImageTokens, estimateRunTokens, estimateTokens, evaluateSelection, expandCommand, expandImports, explainSetting, extractArchive, findInstructionFiles, findProjectInstructions, findRole, formatBytes, formatSkillIndex, formatUsd, gateForTool, getCapability, getConfigIssues, getConfigOrigins, getImportGate, getTrustGate, hasPromptCaching, hasProviderCredentials, imageFromBase64, imageFromPath, imagePartTokens, importSkills, installPlugin, instructionDirs, instructionNotices, intervalSlots, inventoryTotal, isBlockCode, isCodeModeDeferred, isEmptyRules, isGoalPhase, isNonInteractive, isReadOnlySubCall, isSettingWriteLayer, isValidPluginName, isWithinWindow, isWritableSettingKey, labelSlug, layerRank, listSessionCandidates, listWorkspaceFiles, loadCommandDefinitions, loadCommandFile, loadCommands, loadConfig, loadHookSources, loadKeybindings, loadLexiconDir, loadLexiconFiles, loadPlugins, loadPolicyFiles, loadPolicyRules, loadRoleDefinitions, loadRoleDir, loadedImports, localDateString, managedSwitchNotes, mapClaudeToolName, mapEpochToolName, mapOperationType, matchPreauthorization, matchRuleLists, matchesHook, mentionsPluginRoot, mergeHeadlessPolicy, mergeRoles, mergeSchedulePatch, microsToUsd, modelListCandidates, needsAllowlist, nextFallbackModel, nextRunAt, noConfigOrigins, noManagedPolicy, noPlugins, openArtifact, originOf, packFileName, packPlugin, parseClaudeSettingsHooks, parseClock, parseDate, parseFrontmatter, parseHooksConfig, parseHooksConfigVerbose, parseImportLine, parseModelList, parseRoleDefinition, parseScheduleDraft, parseSkillFrontmatter, parseTrigger, pendingImageTokens, persistParts, persistToolResults, persistedUsage, planPack, previewSkillImport, projectEntryForDisplay, pruneArtifacts, pruneRecordings, pruneSchedules, readImageSize, readMarketplaces, readPluginManifest, readPluginRecords, readProviderKeyEnv, readRecording, readSessionSurface, reclaimFtsSpace, redactLine, refreshOpenRouterMetadata, removeMarketplace, removeSessionArtifacts, renderImportTree, renderProjectContext, renderSdkDeclaration, repairSchedules, resolveArtifactRetention, resolveCodeModeWorker, resolveComplianceDirs, resolveEntrySource, resolveInWorkspace, resolveMarketplaceRef, resolveMentions, resolveMultimodalPolicy, resolvePolicyDirs, resolveProjectRoot, resolveScheduleBackend, resolveSessionReference, resolveSessionVisibility, resolveSettings, resolveSkillDirs, resolveSource, resolveTar, resolveWorkspace, restoreParts, revealScopedProvider, roleScopedProvider, roleSourceLabel, ruleCovers, ruleFromString, ruleToString, runCode, runCommand2 as runCommand, sanitizeArgNames, sanitizeCommand, sanitizeFtsQuery, sanitizePath, sanitizeToolOutput, scanInstructions, scanPluginDir, scanSkillDirs, searchMarketplaces, setImportGate, setPluginEnabled, setTrustGate, settingValueChoices, settingValueKind, settingWriteLayers, settingWriteTargets, skillIndexDescription, skillIndexResidency, skillIndexStats, sniffMediaType, splitFrontmatter, staticToolProvider, stripArtifactData, substitutePluginRoot, suggestRuleFromApproval, systemOpenArgv, systemPromptSegments, toPosix, toolDefinitionTokens, toolMatches, toolTableTokens, totalTokens, traceSession, triggerPeriodMs, unescapeRuleContent, uninstallPlugin, unknownRoleSkills, unknownRoleTools, unloadedInstructionFiles, updateMarketplace, updatePlugin, usdToMicros, validateSchedule, validateTrigger, validateWindow, withGoalCompression, withGoalPrompt, workspaceDisplayPath, writeSettingValue };