jinzd-ai-cli 0.4.268 → 0.4.270

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/{batch-2N2YMRX2.js → batch-IMLMUXJL.js} +2 -2
  2. package/dist/{chunk-UT2WAJJG.js → chunk-75SPJKDY.js} +1 -1
  3. package/dist/{chunk-RRQZRSXW.js → chunk-BOMB6XCF.js} +1 -1
  4. package/dist/{chunk-74BK22BM.js → chunk-DIJIYWQE.js} +4 -4
  5. package/dist/{chunk-VGVKJTHF.js → chunk-F26YTEMB.js} +9 -1
  6. package/dist/{chunk-Z7CZ72RP.js → chunk-FXCDD7JS.js} +3 -3
  7. package/dist/{chunk-YLSM7RF6.js → chunk-HOT76ATM.js} +3 -3
  8. package/dist/{chunk-EPVJS4F6.js → chunk-JJVVCWO4.js} +1 -1
  9. package/dist/{chunk-R7GERZDA.js → chunk-JT6UN5C6.js} +26 -5
  10. package/dist/{chunk-2FQGMR34.js → chunk-M3GDYEUH.js} +1 -1
  11. package/dist/{chunk-7US7K3CD.js → chunk-PLFXDRNX.js} +1 -1
  12. package/dist/{chunk-WWLXW4SY.js → chunk-TG4PF7FZ.js} +1 -1
  13. package/dist/{chunk-XDITAGE5.js → chunk-UDUE4DAE.js} +1 -1
  14. package/dist/{chunk-VNCBY4KY.js → chunk-X3JPBJ6C.js} +163 -3
  15. package/dist/{chunk-WNG3YVZX.js → chunk-YHLTSZVH.js} +1 -1
  16. package/dist/{ci-3RWVJSZ3.js → ci-HQVXWJ53.js} +4 -4
  17. package/dist/{ci-format-M6HVCXCG.js → ci-format-762IL4OM.js} +2 -2
  18. package/dist/{constants-G6VN7SJL.js → constants-4TPE3JSV.js} +1 -1
  19. package/dist/{doctor-cli-Y3SVVJBO.js → doctor-cli-3ES6NFVL.js} +4 -4
  20. package/dist/electron-server.js +300 -55
  21. package/dist/{hub-5T5HKOAR.js → hub-QXRWPBJP.js} +1 -1
  22. package/dist/index.js +68 -155
  23. package/dist/{persistent-memory-XVSSR277.js → persistent-memory-F4JPWYZJ.js} +2 -2
  24. package/dist/{persistent-memory-YBWIZWQP.js → persistent-memory-URIJJJ3N.js} +2 -2
  25. package/dist/{pr-NF5IWLH3.js → pr-HVVHZOJ2.js} +4 -4
  26. package/dist/{run-tests-Y44SCLIO.js → run-tests-UZG6SVJ5.js} +2 -2
  27. package/dist/{run-tests-33X3D7PV.js → run-tests-WM7TMO2N.js} +2 -2
  28. package/dist/{server-3LERX42X.js → server-E5OBXT33.js} +5 -5
  29. package/dist/{server-NJ3ZNPQW.js → server-HPRLFLOI.js} +112 -35
  30. package/dist/{task-orchestrator-JN2WFZSF.js → task-orchestrator-HJUUL55Y.js} +5 -5
  31. package/dist/{usage-IFTNXTWC.js → usage-L3DUPHD6.js} +11 -4
  32. package/dist/web/client/actions.js.br +0 -0
  33. package/dist/web/client/actions.js.gz +0 -0
  34. package/dist/web/client/api.js.br +0 -0
  35. package/dist/web/client/api.js.gz +0 -0
  36. package/dist/web/client/app.js +3 -6
  37. package/dist/web/client/app.js.br +0 -0
  38. package/dist/web/client/app.js.gz +0 -0
  39. package/dist/web/client/dom.js.br +0 -0
  40. package/dist/web/client/dom.js.gz +0 -0
  41. package/dist/web/client/icon.svg.br +0 -0
  42. package/dist/web/client/icon.svg.gz +0 -0
  43. package/dist/web/client/index.html.br +0 -0
  44. package/dist/web/client/index.html.gz +0 -0
  45. package/dist/web/client/sidebar-tabs.js.br +0 -0
  46. package/dist/web/client/sidebar-tabs.js.gz +0 -0
  47. package/dist/web/client/state.js.br +0 -0
  48. package/dist/web/client/state.js.gz +0 -0
  49. package/dist/web/client/style.css.br +0 -0
  50. package/dist/web/client/style.css.gz +0 -0
  51. package/dist/web/client/sw.js.br +0 -0
  52. package/dist/web/client/sw.js.gz +0 -0
  53. package/dist/web/client/templates.js.br +0 -0
  54. package/dist/web/client/templates.js.gz +0 -0
  55. package/dist/web/client/util.js +22 -0
  56. package/dist/web/client/util.js.br +0 -0
  57. package/dist/web/client/util.js.gz +0 -0
  58. package/dist/web/client/vendor/daisyui-full.min.css.br +0 -0
  59. package/dist/web/client/vendor/daisyui-full.min.css.gz +0 -0
  60. package/dist/web/client/vendor/github-dark.min.css.br +0 -0
  61. package/dist/web/client/vendor/github-dark.min.css.gz +0 -0
  62. package/dist/web/client/vendor/github.min.css.br +0 -0
  63. package/dist/web/client/vendor/github.min.css.gz +0 -0
  64. package/dist/web/client/vendor/highlight.min.js.br +0 -0
  65. package/dist/web/client/vendor/highlight.min.js.gz +0 -0
  66. package/dist/web/client/vendor/marked.min.js.br +0 -0
  67. package/dist/web/client/vendor/marked.min.js.gz +0 -0
  68. package/dist/web/client/vendor/tailwind.js.br +0 -0
  69. package/dist/web/client/vendor/tailwind.js.gz +0 -0
  70. package/package.json +1 -1
@@ -26,7 +26,9 @@ import {
26
26
  getGitContext,
27
27
  getGitRoot,
28
28
  isMemoryExpired,
29
+ isMemoryStale,
29
30
  listMemoryEntries,
31
+ listOrphanedProjectMemories,
30
32
  loadMemoryEntries,
31
33
  memoryStorePath,
32
34
  parseWritableScope,
@@ -34,14 +36,14 @@ import {
34
36
  touchMemoryReferences,
35
37
  updateMemoryApproval,
36
38
  updateMemoryEntry
37
- } from "./chunk-RRQZRSXW.js";
39
+ } from "./chunk-BOMB6XCF.js";
38
40
  import {
39
41
  redactJson,
40
42
  scanString
41
43
  } from "./chunk-YVTASHS5.js";
42
44
  import {
43
45
  runTestsTool
44
- } from "./chunk-XDITAGE5.js";
46
+ } from "./chunk-UDUE4DAE.js";
45
47
  import {
46
48
  AGENTIC_BEHAVIOR_GUIDELINE,
47
49
  APP_NAME,
@@ -74,7 +76,7 @@ import {
74
76
  SUBAGENT_MAX_ROUNDS_LIMIT,
75
77
  VERSION,
76
78
  buildUserIdentityPrompt
77
- } from "./chunk-EPVJS4F6.js";
79
+ } from "./chunk-JJVVCWO4.js";
78
80
  import {
79
81
  hasSemanticIndex,
80
82
  semanticSearch
@@ -92,8 +94,8 @@ import {
92
94
  import express from "express";
93
95
  import { createServer } from "http";
94
96
  import { WebSocketServer } from "ws";
95
- import { join as join26, dirname as dirname10, resolve as resolve10, relative as relative6, sep as sep4 } from "path";
96
- import { existsSync as existsSync31, readFileSync as readFileSync25, readdirSync as readdirSync15, statSync as statSync14, realpathSync as realpathSync3 } from "fs";
97
+ import { join as join26, dirname as dirname10, resolve as resolve10, relative as relative6, sep as sep4, extname as extname3, resolve as resolvePath } from "path";
98
+ import { existsSync as existsSync31, readFileSync as readFileSync25, readdirSync as readdirSync15, statSync as statSync14, realpathSync as realpathSync3, createReadStream } from "fs";
97
99
  import { networkInterfaces } from "os";
98
100
 
99
101
  // src/config/config-manager.ts
@@ -185,6 +187,14 @@ var ConfigSchema = z.object({
185
187
  wordWrap: z.number().int().min(0).default(0),
186
188
  /** Skill 文件过大警告阈值(字符数)。超过此值的 skill 在启动加载时打印警告。0 = 静默。默认 10000 */
187
189
  skillSizeWarn: z.number().int().min(0).default(1e4),
190
+ /**
191
+ * 成本显示币种:usd(默认)| cny。
192
+ *
193
+ * 只影响**展示**——存储、monthlyBudget、cost-history.json 恒为美元。
194
+ * 人民币按 pricing.ts 的记账汇率换算并在输出里标注,避免造出一个
195
+ * 看起来像实时汇率的假数字(2026-08-14 审计 P3-02)。
196
+ */
197
+ currency: z.enum(["usd", "cny"]).default("usd"),
188
198
  /** 颜色主题:'dark'(默认)| 'light' | 'custom' */
189
199
  theme: z.enum(["dark", "light", "custom"]).default("dark"),
190
200
  /** 自定义颜色覆盖(仅在 theme='custom' 时生效)。值为 chalk 颜色名或 '#hex',支持 'bold.cyan' 组合 */
@@ -715,13 +725,13 @@ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
715
725
  import { join as join2 } from "path";
716
726
 
717
727
  // src/core/pricing.ts
718
- var DEEPSEEK_CNY_PER_USD = 6.9;
728
+ var CNY_PER_USD = 6.9;
719
729
  var DEEPSEEK_TIME_PRICING_EFFECTIVE_AT = /* @__PURE__ */ new Date("2026-08-16T16:00:00.000Z");
720
730
  function deepSeekCnyPricing(cacheRead, input, output) {
721
731
  return {
722
- cacheRead: cacheRead / DEEPSEEK_CNY_PER_USD,
723
- input: input / DEEPSEEK_CNY_PER_USD,
724
- output: output / DEEPSEEK_CNY_PER_USD
732
+ cacheRead: cacheRead / CNY_PER_USD,
733
+ input: input / CNY_PER_USD,
734
+ output: output / CNY_PER_USD
725
735
  };
726
736
  }
727
737
  function getDeepSeekV4Pricing(model, at) {
@@ -849,7 +859,21 @@ function computeCost(provider, model, usage, at = /* @__PURE__ */ new Date()) {
849
859
  const cacheRead = (usage.cacheReadTokens ?? 0) * (p.cacheRead ?? p.input);
850
860
  return (input + output + cacheWrite + cacheRead) / 1e6;
851
861
  }
852
- function formatCost(amount) {
862
+ var displayCurrency = "usd";
863
+ function setCostCurrency(currency) {
864
+ displayCurrency = currency === "cny" ? "cny" : "usd";
865
+ }
866
+ function getCostCurrency() {
867
+ return displayCurrency;
868
+ }
869
+ function formatCost(amount, currency = displayCurrency) {
870
+ if (currency === "cny") {
871
+ const cny = amount * CNY_PER_USD;
872
+ if (cny === 0) return "\xA50.0000";
873
+ if (cny < 0.01) return `\xA5${cny.toFixed(4)}`;
874
+ if (cny < 1) return `\xA5${cny.toFixed(3)}`;
875
+ return `\xA5${cny.toFixed(2)}`;
876
+ }
853
877
  if (amount === 0) return "$0.0000";
854
878
  if (amount < 0.01) return `$${amount.toFixed(4)}`;
855
879
  if (amount < 1) return `$${amount.toFixed(3)}`;
@@ -5258,9 +5282,9 @@ var SessionManager = class {
5258
5282
  try {
5259
5283
  const data = JSON.parse(readFileSync4(filePath, "utf-8"));
5260
5284
  const messages = data.messages ?? [];
5261
- const matches = [];
5285
+ const matches2 = [];
5262
5286
  for (const msg of messages) {
5263
- if (matches.length >= 3) break;
5287
+ if (matches2.length >= 3) break;
5264
5288
  let text = "";
5265
5289
  if (typeof msg.content === "string") {
5266
5290
  text = msg.content;
@@ -5273,10 +5297,10 @@ var SessionManager = class {
5273
5297
  const start = Math.max(0, idx - 30);
5274
5298
  const end = Math.min(text.length, idx + query.length + 60);
5275
5299
  const snippet = (start > 0 ? "\u2026" : "") + text.slice(start, end).replace(/\n/g, " ") + (end < text.length ? "\u2026" : "");
5276
- matches.push({ role: msg.role, snippet });
5300
+ matches2.push({ role: msg.role, snippet });
5277
5301
  }
5278
5302
  }
5279
- if (matches.length > 0) {
5303
+ if (matches2.length > 0) {
5280
5304
  results.push({
5281
5305
  sessionMeta: {
5282
5306
  id: data.id,
@@ -5287,7 +5311,7 @@ var SessionManager = class {
5287
5311
  updated: safeDate(data.updated),
5288
5312
  title: data.title
5289
5313
  },
5290
- matches
5314
+ matches: matches2
5291
5315
  });
5292
5316
  }
5293
5317
  } catch (err) {
@@ -9395,20 +9419,20 @@ Results sorted by most recent modification time. Automatically skips node_module
9395
9419
  if (!pattern) throw new ToolError("glob_files", "pattern is required");
9396
9420
  if (!existsSync16(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
9397
9421
  const regex = globToRegex(pattern);
9398
- const matches = [];
9399
- collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
9400
- if (matches.length === 0) {
9422
+ const matches2 = [];
9423
+ collectMatchingFiles(rootPath, rootPath, regex, matches2, maxResults);
9424
+ if (matches2.length === 0) {
9401
9425
  return `No files matched pattern: ${pattern}
9402
9426
  Searched in: ${rootPath}`;
9403
9427
  }
9404
- matches.sort((a, b) => b.mtime - a.mtime);
9428
+ matches2.sort((a, b) => b.mtime - a.mtime);
9405
9429
  const lines = [
9406
- `Found ${matches.length} file(s) matching: ${pattern}`,
9430
+ `Found ${matches2.length} file(s) matching: ${pattern}`,
9407
9431
  `Searched in: ${rootPath}`,
9408
9432
  "",
9409
- ...matches.map((m) => m.relPath)
9433
+ ...matches2.map((m) => m.relPath)
9410
9434
  ];
9411
- if (matches.length >= maxResults) {
9435
+ if (matches2.length >= maxResults) {
9412
9436
  lines.push("");
9413
9437
  lines.push(`(Results truncated at ${maxResults}. Use max_results or narrow your pattern.)`);
9414
9438
  }
@@ -10927,10 +10951,10 @@ function trustPlugin(configDir, name) {
10927
10951
  }
10928
10952
  function getInstalledPlugin(configDir, name) {
10929
10953
  const plugins = listInstalledPlugins(configDir);
10930
- const matches = plugins.filter((p) => p.name === name || p.name.startsWith(name));
10931
- if (matches.length === 0) throw new Error(`plugin not found: ${name}`);
10932
- if (matches.length > 1) throw new Error(`plugin name is ambiguous: ${name}`);
10933
- return matches[0];
10954
+ const matches2 = plugins.filter((p) => p.name === name || p.name.startsWith(name));
10955
+ if (matches2.length === 0) throw new Error(`plugin not found: ${name}`);
10956
+ if (matches2.length > 1) throw new Error(`plugin name is ambiguous: ${name}`);
10957
+ return matches2[0];
10934
10958
  }
10935
10959
  function listInstalledPlugins(configDir) {
10936
10960
  const root = pluginRoot(configDir);
@@ -11451,8 +11475,8 @@ function summarizeContent(content) {
11451
11475
  function extractEvidence(content, toolNames) {
11452
11476
  const evidence = /* @__PURE__ */ new Set();
11453
11477
  if (toolNames.length > 0) evidence.add(`Tools: ${toolNames.join(", ")}`);
11454
- const matches = content.match(/(?:[\w.-]+[\\/])+[\w.-]+\.(?:ts|tsx|js|jsx|json|md|css|html|py|go|rs|yml|yaml)/g) ?? [];
11455
- for (const match of matches.slice(0, 8)) evidence.add(match);
11478
+ const matches2 = content.match(/(?:[\w.-]+[\\/])+[\w.-]+\.(?:ts|tsx|js|jsx|json|md|css|html|py|go|rs|yml|yaml)/g) ?? [];
11479
+ for (const match of matches2.slice(0, 8)) evidence.add(match);
11456
11480
  return [...evidence].slice(0, 10);
11457
11481
  }
11458
11482
  function formatAgentResult(input) {
@@ -14194,6 +14218,80 @@ function evaluateContextPressure(input) {
14194
14218
  }
14195
14219
  var AUTO_COMPACT_INSTRUCTION = "Preserve key context, compress old content, ensure seamless continuation";
14196
14220
 
14221
+ // src/core/model-router.ts
14222
+ var TAG_REGEX = /(?:^|\s)#([a-zA-Z][\w-]{0,31})\b/g;
14223
+ function extractTags(message) {
14224
+ const tags = /* @__PURE__ */ new Set();
14225
+ let m;
14226
+ TAG_REGEX.lastIndex = 0;
14227
+ while ((m = TAG_REGEX.exec(message)) !== null) {
14228
+ tags.add(m[1].toLowerCase());
14229
+ }
14230
+ return tags;
14231
+ }
14232
+ function matches(message, matcher) {
14233
+ const trimmed = message.trim();
14234
+ const lower = trimmed.toLowerCase();
14235
+ if (matcher.tag) {
14236
+ const tags = extractTags(trimmed);
14237
+ if (!tags.has(matcher.tag.toLowerCase())) return false;
14238
+ }
14239
+ if (matcher.contains && matcher.contains.length > 0) {
14240
+ const hit = matcher.contains.some((kw) => lower.includes(kw.toLowerCase()));
14241
+ if (!hit) return false;
14242
+ }
14243
+ if (typeof matcher.maxLength === "number") {
14244
+ if (trimmed.length > matcher.maxLength) return false;
14245
+ }
14246
+ if (typeof matcher.minLength === "number") {
14247
+ if (trimmed.length < matcher.minLength) return false;
14248
+ }
14249
+ const hasAnyCondition = !!matcher.tag || matcher.contains && matcher.contains.length > 0 || typeof matcher.maxLength === "number" || typeof matcher.minLength === "number";
14250
+ return !!hasAnyCondition;
14251
+ }
14252
+ function pickModel(message, currentModel, config, availableModels = []) {
14253
+ if (!config.enabled || config.rules.length === 0) {
14254
+ return { model: currentModel, reason: "routing disabled", overridden: false };
14255
+ }
14256
+ const isAvailable = (m) => availableModels.length === 0 || availableModels.includes(m);
14257
+ for (let i = 0; i < config.rules.length; i++) {
14258
+ const rule = config.rules[i];
14259
+ if (!matches(message, rule.match)) continue;
14260
+ if (!isAvailable(rule.model)) continue;
14261
+ if (rule.model === currentModel) {
14262
+ return {
14263
+ model: currentModel,
14264
+ reason: `rule "${rule.name ?? `#${i}`}" matched (same as current)`,
14265
+ overridden: false,
14266
+ ruleIdx: i
14267
+ };
14268
+ }
14269
+ return {
14270
+ model: rule.model,
14271
+ reason: `rule "${rule.name ?? `#${i}`}" matched`,
14272
+ overridden: true,
14273
+ ruleIdx: i
14274
+ };
14275
+ }
14276
+ if (config.fallback && config.fallback !== currentModel && isAvailable(config.fallback)) {
14277
+ return {
14278
+ model: config.fallback,
14279
+ reason: "fallback",
14280
+ overridden: true
14281
+ };
14282
+ }
14283
+ return { model: currentModel, reason: "no rule matched", overridden: false };
14284
+ }
14285
+ function stripRoutingTags(message) {
14286
+ return message.replace(/(?:^|\s)#(fast|deep|default)\b/gi, " ").replace(/\s{2,}/g, " ").trim();
14287
+ }
14288
+ function decideModel(input) {
14289
+ if (!input.routingConfig || !input.routingConfig.enabled) {
14290
+ return { model: input.currentModel, reason: "routing disabled", overridden: false };
14291
+ }
14292
+ return pickModel(input.userInput, input.currentModel, input.routingConfig, input.availableModels);
14293
+ }
14294
+
14197
14295
  // src/core/context-files.ts
14198
14296
  import { existsSync as existsSync25, readFileSync as readFileSync20 } from "fs";
14199
14297
  import { join as join19, relative as relative4, resolve as resolve8 } from "path";
@@ -14544,6 +14642,84 @@ Output the Markdown content directly, do not wrap the entire file in a code bloc
14544
14642
  return parts.join("\n");
14545
14643
  }
14546
14644
 
14645
+ // src/core/memory-filters.ts
14646
+ function parseFlags(items) {
14647
+ const flags = {};
14648
+ const text = [];
14649
+ for (let i = 0; i < items.length; i++) {
14650
+ const item = items[i];
14651
+ if (item.startsWith("--")) {
14652
+ const key = item.slice(2);
14653
+ const next = items[i + 1];
14654
+ if (next && !next.startsWith("--")) {
14655
+ flags[key] = next;
14656
+ i++;
14657
+ } else {
14658
+ flags[key] = true;
14659
+ }
14660
+ } else {
14661
+ text.push(item);
14662
+ }
14663
+ }
14664
+ return { flags, text: text.join(" ").trim() };
14665
+ }
14666
+ var SCOPES = /* @__PURE__ */ new Set(["personal", "project", "session", "team"]);
14667
+ var BOOLEAN_FLAGS = ["pending", "orphaned"];
14668
+ function parseMemoryShowFilters(args) {
14669
+ const { flags, text } = parseFlags(args);
14670
+ const strayTerms = [];
14671
+ for (const key of BOOLEAN_FLAGS) {
14672
+ const v = flags[key];
14673
+ if (typeof v === "string") {
14674
+ strayTerms.push(v);
14675
+ flags[key] = true;
14676
+ }
14677
+ }
14678
+ const scopeRaw = flags["scope"];
14679
+ const freeText = [text, ...strayTerms].filter(Boolean).join(" ").trim();
14680
+ const searchRaw = typeof flags["search"] === "string" ? flags["search"] : freeText || void 0;
14681
+ const staleRaw = flags["stale"];
14682
+ let staleDays;
14683
+ let invalidStale = false;
14684
+ if (staleRaw !== void 0) {
14685
+ const n = typeof staleRaw === "string" ? Number(staleRaw) : NaN;
14686
+ if (!Number.isFinite(n) || n <= 0) invalidStale = true;
14687
+ else staleDays = n;
14688
+ }
14689
+ return {
14690
+ pending: flags["pending"] === true,
14691
+ orphaned: flags["orphaned"] === true,
14692
+ scope: typeof scopeRaw === "string" && SCOPES.has(scopeRaw) ? scopeRaw : void 0,
14693
+ search: searchRaw?.toLowerCase(),
14694
+ staleDays,
14695
+ invalidStale
14696
+ };
14697
+ }
14698
+ function filterMemoryEntries(entries, filters) {
14699
+ let out = entries;
14700
+ const applied = [];
14701
+ if (filters.orphaned) applied.push("orphaned");
14702
+ if (filters.pending) {
14703
+ out = out.filter((m) => !m.approved && !isMemoryExpired(m));
14704
+ applied.push("pending");
14705
+ }
14706
+ if (filters.scope) {
14707
+ out = out.filter((m) => m.scope === filters.scope);
14708
+ applied.push(`scope:${filters.scope}`);
14709
+ }
14710
+ if (filters.search) {
14711
+ const q = filters.search;
14712
+ out = out.filter((m) => m.content.toLowerCase().includes(q) || m.id.startsWith(q));
14713
+ applied.push(`search:"${q}"`);
14714
+ }
14715
+ if (filters.staleDays !== void 0) {
14716
+ const days = filters.staleDays;
14717
+ out = out.filter((m) => isMemoryStale(m, days));
14718
+ applied.push(`stale>${days}d`);
14719
+ }
14720
+ return { entries: out, applied };
14721
+ }
14722
+
14547
14723
  // src/hub/convergence.ts
14548
14724
  function convergenceThreshold(total) {
14549
14725
  if (total <= 0) return Infinity;
@@ -15863,7 +16039,7 @@ Cache: write=${cacheCreate} read=${cacheRead}` : "";
15863
16039
  Cost: ${formatCost(cost)}` : "";
15864
16040
  let memoryLine = "";
15865
16041
  try {
15866
- const { countPendingMemories } = await import("./persistent-memory-YBWIZWQP.js");
16042
+ const { countPendingMemories } = await import("./persistent-memory-URIJJJ3N.js");
15867
16043
  const pending = countPendingMemories(ctx.config.getConfigDir());
15868
16044
  if (pending > 0) memoryLine = `
15869
16045
  Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
@@ -15967,14 +16143,14 @@ async function handleSession(args, ctx) {
15967
16143
  return;
15968
16144
  }
15969
16145
  const list2 = ctx.sessions.listSessions();
15970
- const matches = list2.filter((s) => s.messageCount === filterMessages);
15971
- if (matches.length === 0) {
16146
+ const matches2 = list2.filter((s) => s.messageCount === filterMessages);
16147
+ if (matches2.length === 0) {
15972
16148
  ctx.send({ type: "info", message: `No sessions with ${filterMessages} message(s) found.` });
15973
16149
  return;
15974
16150
  }
15975
16151
  let deleted = 0;
15976
16152
  let failed = 0;
15977
- for (const s of matches) {
16153
+ for (const s of matches2) {
15978
16154
  if (ctx.sessions.deleteSession(s.id)) {
15979
16155
  deleted++;
15980
16156
  } else {
@@ -16676,7 +16852,7 @@ async function handleTest(args, ctx) {
16676
16852
  const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
16677
16853
  testArgs = isCommand ? { command: argStr } : { filter: argStr };
16678
16854
  }
16679
- const runTests = ctx.runTests ?? (await import("./run-tests-Y44SCLIO.js")).executeTests;
16855
+ const runTests = ctx.runTests ?? (await import("./run-tests-UZG6SVJ5.js")).executeTests;
16680
16856
  const report = await runTests(testArgs);
16681
16857
  ctx.send({ type: "info", message: report });
16682
16858
  } catch (err) {
@@ -16934,7 +17110,7 @@ async function handleMemory(args, ctx) {
16934
17110
  ctx.handleMemoryManage(sub, args[1], sub === "expire" ? args[2] : void 0);
16935
17111
  } else if (sub === "export") {
16936
17112
  if (args[1] === "md") {
16937
- const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-YBWIZWQP.js");
17113
+ const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-URIJJJ3N.js");
16938
17114
  ctx.send({
16939
17115
  type: "export_data",
16940
17116
  format: "md",
@@ -16954,7 +17130,8 @@ async function handleMemory(args, ctx) {
16954
17130
  });
16955
17131
  }
16956
17132
  } else {
16957
- ctx.memoryShow();
17133
+ const isShow = sub === "show" || sub === "view" || sub === "list";
17134
+ ctx.memoryShow(isShow ? args.slice(1) : args);
16958
17135
  }
16959
17136
  }
16960
17137
  async function handleAddDir(args, ctx) {
@@ -17382,11 +17559,11 @@ async function handleSearch(args, ctx) {
17382
17559
  }
17383
17560
  const lines = [`\u{1F50D} Found ${results.length} session(s) containing "${query}"`, ""];
17384
17561
  for (const r of results) {
17385
- const { sessionMeta, matches } = r;
17562
+ const { sessionMeta, matches: matches2 } = r;
17386
17563
  const dateStr = sessionMeta.updated instanceof Date ? sessionMeta.updated.toLocaleDateString() : String(sessionMeta.updated);
17387
17564
  lines.push(`**${sessionMeta.id.slice(0, 8)}** [${dateStr}] ${sessionMeta.provider} / ${sessionMeta.model}`);
17388
17565
  if (sessionMeta.title) lines.push(` ${sessionMeta.title}`);
17389
- for (const m of matches) {
17566
+ for (const m of matches2) {
17390
17567
  const icon = m.role === "user" ? "\u{1F464}" : "\u{1F916}";
17391
17568
  lines.push(` ${icon} ${m.snippet}`);
17392
17569
  }
@@ -17517,17 +17694,18 @@ var SessionHandler = class {
17517
17694
  sessionAccruedCostUsd = 0;
17518
17695
  sessionCostIncomplete = false;
17519
17696
  /** Accumulate a TokenUsage (with optional cache fields) into sessionTokenUsage. */
17520
- addWebSessionUsage(u) {
17697
+ addWebSessionUsage(u, modelOverride) {
17698
+ const usageModel = modelOverride ?? this.currentModel;
17521
17699
  this.sessionTokenUsage.inputTokens += u.inputTokens;
17522
17700
  this.sessionTokenUsage.outputTokens += u.outputTokens;
17523
17701
  this.sessionTokenUsage.cacheCreationTokens += u.cacheCreationTokens ?? 0;
17524
17702
  this.sessionTokenUsage.cacheReadTokens += u.cacheReadTokens ?? 0;
17525
- const priced = this.costTracker.addCost(this.currentProvider, this.currentModel, u);
17703
+ const priced = this.costTracker.addCost(this.currentProvider, usageModel, u);
17526
17704
  if (priced === null) this.sessionCostIncomplete = true;
17527
17705
  else this.sessionAccruedCostUsd += priced;
17528
17706
  const totalTokens = u.inputTokens + u.outputTokens + (u.cacheCreationTokens ?? 0) + (u.cacheReadTokens ?? 0);
17529
- if (totalTokens > 0 && !getPricing(this.currentProvider, this.currentModel)) {
17530
- const key = `${this.currentProvider}/${this.currentModel}`;
17707
+ if (totalTokens > 0 && !getPricing(this.currentProvider, usageModel)) {
17708
+ const key = `${this.currentProvider}/${usageModel}`;
17531
17709
  if (!this.unknownPricingWarnings.has(key)) {
17532
17710
  this.unknownPricingWarnings.add(key);
17533
17711
  this.send({ type: "info", message: `Cost notice: pricing is unknown for ${key}. Tokens are tracked, but cost is excluded from totals. Run /cost or aicli usage to review.` });
@@ -17663,6 +17841,8 @@ var SessionHandler = class {
17663
17841
  permissionProfile: this.config.get("defaultPermissionProfile") ?? "legacy",
17664
17842
  tokenUsage: { ...this.sessionTokenUsage },
17665
17843
  costUsd,
17844
+ costCurrency: getCostCurrency(),
17845
+ costCnyPerUsd: CNY_PER_USD,
17666
17846
  costPricingUnknown,
17667
17847
  providers: providerList,
17668
17848
  branches,
@@ -17975,6 +18155,16 @@ var SessionHandler = class {
17975
18155
  } else {
17976
18156
  msgContent = content;
17977
18157
  }
18158
+ const routingDecision = this.computeRoutingDecision(typeof msgContent === "string" ? msgContent : content);
18159
+ if (typeof msgContent === "string") {
18160
+ msgContent = stripRoutingTags(msgContent);
18161
+ } else if (msgContent.length > 0 && msgContent[msgContent.length - 1]?.type === "text") {
18162
+ const last = msgContent[msgContent.length - 1];
18163
+ msgContent = [...msgContent.slice(0, -1), { type: "text", text: stripRoutingTags(last.text) }];
18164
+ }
18165
+ if (routingDecision.overridden) {
18166
+ this.send({ type: "info", message: `\u2192 Routed to ${routingDecision.model} (${routingDecision.reason})` });
18167
+ }
17978
18168
  const userMessage = { role: "user", content: msgContent, timestamp: /* @__PURE__ */ new Date() };
17979
18169
  session.addMessage(userMessage);
17980
18170
  const provider = this.providers.get(this.currentProvider);
@@ -17986,11 +18176,13 @@ var SessionHandler = class {
17986
18176
  const hasToolSupport = typeof provider.chatWithTools === "function";
17987
18177
  const { toolDefs, mcpBudgetNote } = hasToolSupport ? this.getFilteredToolDefs() : { toolDefs: [], mcpBudgetNote: null };
17988
18178
  if (hasToolSupport && toolDefs.length > 0) {
17989
- await this.handleChatWithTools(provider, session.messages, toolDefs, mcpBudgetNote);
18179
+ await this.handleChatWithTools(provider, session.messages, toolDefs, mcpBudgetNote, routingDecision.model);
17990
18180
  } else {
17991
- await this.handleChatSimple(provider, session.messages);
18181
+ await this.handleChatSimple(provider, session.messages, routingDecision.model);
17992
18182
  }
17993
18183
  await this.checkContextPressure();
18184
+ const budgetWarning = this.costTracker.checkBudget(this.config.get("monthlyBudget"));
18185
+ if (budgetWarning) this.send({ type: "info", message: `\u26A0 ${budgetWarning}` });
17994
18186
  } catch (err) {
17995
18187
  const parts = [];
17996
18188
  let current = err;
@@ -18009,8 +18201,9 @@ var SessionHandler = class {
18009
18201
  this.sendSessionList();
18010
18202
  }
18011
18203
  }
18012
- async handleChatSimple(provider, messages) {
18204
+ async handleChatSimple(provider, messages, modelOverride) {
18013
18205
  const session = this.sessions.current;
18206
+ const effectiveModel = modelOverride ?? this.currentModel;
18014
18207
  const modelParams = this.getModelParams();
18015
18208
  const ac = new AbortController();
18016
18209
  this.abortController = ac;
@@ -18018,7 +18211,7 @@ var SessionHandler = class {
18018
18211
  const { stable: simplStable, volatile: simplVolatile } = this.buildSystemPrompt();
18019
18212
  const stream = provider.chatStream({
18020
18213
  messages,
18021
- model: this.currentModel,
18214
+ model: effectiveModel,
18022
18215
  systemPrompt: simplStable,
18023
18216
  systemPromptVolatile: simplVolatile,
18024
18217
  stream: true,
@@ -18037,7 +18230,7 @@ var SessionHandler = class {
18037
18230
  if (chunk.done) {
18038
18231
  this.send({ type: "response_done", content: fullContent, usage: chunk.usage });
18039
18232
  if (chunk.usage) {
18040
- this.addWebSessionUsage(chunk.usage);
18233
+ this.addWebSessionUsage(chunk.usage, effectiveModel);
18041
18234
  session.addTokenUsage(chunk.usage);
18042
18235
  }
18043
18236
  if (chunk.reasoningContent) simpleReasoning = chunk.reasoningContent;
@@ -18062,8 +18255,9 @@ var SessionHandler = class {
18062
18255
  this.abortController = null;
18063
18256
  }
18064
18257
  }
18065
- async handleChatWithTools(provider, messages, toolDefs, mcpBudgetNote) {
18258
+ async handleChatWithTools(provider, messages, toolDefs, mcpBudgetNote, modelOverride) {
18066
18259
  const session = this.sessions.current;
18260
+ const effectiveModel = modelOverride ?? this.currentModel;
18067
18261
  const apiMessages = [...messages];
18068
18262
  const maxToolRounds = this.config.get("maxToolRounds") ?? DEFAULT_MAX_TOOL_ROUNDS;
18069
18263
  const autoPauseIntervalRaw = this.config.get("autoPauseInterval");
@@ -18093,7 +18287,7 @@ ${mcpBudgetNote}` : "");
18093
18287
  callModel: async (_round, extraMessages) => {
18094
18288
  const chatRequest = {
18095
18289
  messages: apiMessages,
18096
- model: this.currentModel,
18290
+ model: effectiveModel,
18097
18291
  systemPrompt,
18098
18292
  systemPromptVolatile,
18099
18293
  stream: false,
@@ -18138,7 +18332,7 @@ Details: ${errMsg.split("\n")[0]}
18138
18332
  const summaryResult = await provider.chatWithTools(
18139
18333
  {
18140
18334
  messages: apiMessages,
18141
- model: this.currentModel,
18335
+ model: effectiveModel,
18142
18336
  systemPrompt,
18143
18337
  systemPromptVolatile,
18144
18338
  stream: false,
@@ -18154,7 +18348,7 @@ Details: ${errMsg.split("\n")[0]}
18154
18348
  executeTools: async (toolCalls) => {
18155
18349
  googleSearchContext.configManager = this.config;
18156
18350
  spawnAgentContext.provider = provider;
18157
- spawnAgentContext.model = this.currentModel;
18351
+ spawnAgentContext.model = effectiveModel;
18158
18352
  spawnAgentContext.systemPrompt = systemPromptVolatile ? `${systemPrompt}
18159
18353
 
18160
18354
  ---
@@ -18528,6 +18722,24 @@ ${summaryContent}`,
18528
18722
  * 到达上下文压力阈值时自动压缩(与 REPL 同一判据,见 core/context-pressure.ts)。
18529
18723
  * 上下文窗口未知时不动——不基于猜测改用户的对话历史。
18530
18724
  */
18725
+ /**
18726
+ * 智能模型路由决策(审计附录 D-5)。候选限定在当前 provider 注册的模型内——
18727
+ * 跨 provider 换模型会破坏 API key 与对话连续性。
18728
+ */
18729
+ computeRoutingDecision(userInput) {
18730
+ let availableModels = [];
18731
+ try {
18732
+ availableModels = this.providers.get(this.currentProvider).info.models.map((m) => m.id);
18733
+ } catch {
18734
+ availableModels = [];
18735
+ }
18736
+ return decideModel({
18737
+ userInput,
18738
+ currentModel: this.currentModel,
18739
+ routingConfig: this.config.get("routing"),
18740
+ availableModels
18741
+ });
18742
+ }
18531
18743
  async checkContextPressure() {
18532
18744
  const contextWindow = this.getContextWindowSize();
18533
18745
  const session = this.sessions.current;
@@ -18690,18 +18902,27 @@ ${summaryContent}`,
18690
18902
  });
18691
18903
  }
18692
18904
  }
18693
- memoryShow() {
18905
+ memoryShow(args = []) {
18694
18906
  const configDir = this.config.getConfigDir();
18695
- const entries = listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
18696
- const content = entries.length === 0 ? "(empty \u2014 no persistent memory entries yet)" : entries.map((entry) => {
18907
+ const filters = parseMemoryShowFilters(args);
18908
+ if (filters.invalidStale) {
18909
+ this.send({ type: "error", message: "Usage: /memory show --stale <days> (positive number)" });
18910
+ return;
18911
+ }
18912
+ const source = filters.orphaned ? listOrphanedProjectMemories(configDir) : listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
18913
+ const { entries, applied } = filterMemoryEntries(source, filters);
18914
+ const filterNote = applied.length > 0 ? ` (filtered: ${applied.join(", ")})` : "";
18915
+ const content = entries.length === 0 ? applied.length > 0 ? `(no entries match [${applied.join(", ")}])` : "(empty \u2014 no persistent memory entries yet)" : `Governed persistent memory${filterNote}
18916
+
18917
+ ` + entries.map((entry) => {
18697
18918
  const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
18698
- const source = [
18919
+ const source2 = [
18699
18920
  entry.source ?? "unknown",
18700
18921
  entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : "",
18701
18922
  entry.sourceProject ? `project ${entry.sourceProject}` : ""
18702
18923
  ].filter(Boolean).join(" \xB7 ");
18703
18924
  return `## ${entry.id.slice(0, 8)} \xB7 ${status} \xB7 scope:${entry.scope} \xB7 sensitivity:${entry.sensitivity}
18704
- Source: ${source}
18925
+ Source: ${source2}
18705
18926
  ${entry.content}`;
18706
18927
  }).join("\n\n");
18707
18928
  this.send({
@@ -19422,6 +19643,7 @@ async function startWebServer(options = {}) {
19422
19643
  console.log(` Skills: ${count} available`);
19423
19644
  }
19424
19645
  }
19646
+ setCostCurrency(config.get("ui").currency);
19425
19647
  const shared = {
19426
19648
  config,
19427
19649
  costTracker: new CostTracker(config.getConfigDir()),
@@ -19501,6 +19723,29 @@ async function startWebServer(options = {}) {
19501
19723
  clientDir = join26(process.cwd(), "src", "web", "client");
19502
19724
  }
19503
19725
  console.log(` Static files: ${clientDir}`);
19726
+ const PRECOMPRESSED_TYPES = {
19727
+ ".js": "text/javascript; charset=utf-8",
19728
+ ".css": "text/css; charset=utf-8",
19729
+ ".html": "text/html; charset=utf-8",
19730
+ ".json": "application/json; charset=utf-8",
19731
+ ".svg": "image/svg+xml",
19732
+ ".map": "application/json; charset=utf-8"
19733
+ };
19734
+ app.get(/.*/, (req, res, next) => {
19735
+ const ext = extname3(req.path).toLowerCase();
19736
+ const contentType = PRECOMPRESSED_TYPES[ext];
19737
+ if (!contentType) return next();
19738
+ const accept = String(req.headers["accept-encoding"] ?? "");
19739
+ const encoding = /\bbr\b/.test(accept) ? "br" : /\bgzip\b/.test(accept) ? "gzip" : null;
19740
+ if (!encoding) return next();
19741
+ const target = resolvePath(clientDir, `.${req.path}`) + (encoding === "br" ? ".br" : ".gz");
19742
+ if (!target.startsWith(clientDir) || !existsSync31(target)) return next();
19743
+ res.setHeader("Content-Type", contentType);
19744
+ res.setHeader("Content-Encoding", encoding);
19745
+ res.setHeader("Vary", "Accept-Encoding");
19746
+ if (req.path.startsWith("/vendor/")) res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
19747
+ createReadStream(target).pipe(res);
19748
+ });
19504
19749
  app.use("/vendor", express.static(join26(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
19505
19750
  app.use(express.static(clientDir));
19506
19751
  app.get("/api/status", (_req, res) => {
@@ -158,7 +158,7 @@ ${content}`);
158
158
  }
159
159
  }
160
160
  async function runTaskMode(config, providers, configManager, topic) {
161
- const { TaskOrchestrator } = await import("./task-orchestrator-JN2WFZSF.js");
161
+ const { TaskOrchestrator } = await import("./task-orchestrator-HJUUL55Y.js");
162
162
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
163
163
  let interrupted = false;
164
164
  const onSigint = () => {