jinzd-ai-cli 0.4.268 → 0.4.269

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 (31) hide show
  1. package/dist/{batch-2N2YMRX2.js → batch-HUOYPVJQ.js} +2 -2
  2. package/dist/{chunk-UT2WAJJG.js → chunk-3TOMHXDW.js} +1 -1
  3. package/dist/{chunk-EPVJS4F6.js → chunk-4HTJMLFY.js} +1 -1
  4. package/dist/{chunk-VGVKJTHF.js → chunk-5IUFUCIT.js} +1 -1
  5. package/dist/{chunk-2FQGMR34.js → chunk-5PRWBJCI.js} +1 -1
  6. package/dist/{chunk-YLSM7RF6.js → chunk-6PROC2JO.js} +3 -3
  7. package/dist/{chunk-7US7K3CD.js → chunk-AAERHPBN.js} +1 -1
  8. package/dist/{chunk-74BK22BM.js → chunk-DJNGUPM3.js} +4 -4
  9. package/dist/{chunk-RRQZRSXW.js → chunk-KIHNZBEA.js} +1 -1
  10. package/dist/{chunk-XDITAGE5.js → chunk-NVN3FHXI.js} +1 -1
  11. package/dist/{chunk-WNG3YVZX.js → chunk-QPPUYCRF.js} +1 -1
  12. package/dist/{chunk-VNCBY4KY.js → chunk-S3F5LOV3.js} +162 -2
  13. package/dist/{chunk-WWLXW4SY.js → chunk-V6KQ67VF.js} +1 -1
  14. package/dist/{chunk-Z7CZ72RP.js → chunk-XJAGGJVU.js} +3 -3
  15. package/dist/{ci-3RWVJSZ3.js → ci-4QS2J7BG.js} +4 -4
  16. package/dist/{ci-format-M6HVCXCG.js → ci-format-6L6M67WC.js} +2 -2
  17. package/dist/{constants-G6VN7SJL.js → constants-IO5VJLEE.js} +1 -1
  18. package/dist/{doctor-cli-Y3SVVJBO.js → doctor-cli-ZU4E2DDL.js} +4 -4
  19. package/dist/electron-server.js +245 -48
  20. package/dist/{hub-5T5HKOAR.js → hub-AMDWXSKZ.js} +1 -1
  21. package/dist/index.js +64 -153
  22. package/dist/{persistent-memory-YBWIZWQP.js → persistent-memory-52SE5K44.js} +2 -2
  23. package/dist/{persistent-memory-XVSSR277.js → persistent-memory-H4DNDIOQ.js} +2 -2
  24. package/dist/{pr-NF5IWLH3.js → pr-Z4O5H6I3.js} +4 -4
  25. package/dist/{run-tests-33X3D7PV.js → run-tests-FZSYHWYX.js} +2 -2
  26. package/dist/{run-tests-Y44SCLIO.js → run-tests-PYKKYMPA.js} +2 -2
  27. package/dist/{server-NJ3ZNPQW.js → server-WTJSM2MT.js} +79 -31
  28. package/dist/{server-3LERX42X.js → server-ZSTVYSYC.js} +5 -5
  29. package/dist/{task-orchestrator-JN2WFZSF.js → task-orchestrator-PNLI3V36.js} +5 -5
  30. package/dist/{usage-IFTNXTWC.js → usage-GVFUVYUG.js} +2 -2
  31. 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-KIHNZBEA.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-NVN3FHXI.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-4HTJMLFY.js";
78
80
  import {
79
81
  hasSemanticIndex,
80
82
  semanticSearch
@@ -5258,9 +5260,9 @@ var SessionManager = class {
5258
5260
  try {
5259
5261
  const data = JSON.parse(readFileSync4(filePath, "utf-8"));
5260
5262
  const messages = data.messages ?? [];
5261
- const matches = [];
5263
+ const matches2 = [];
5262
5264
  for (const msg of messages) {
5263
- if (matches.length >= 3) break;
5265
+ if (matches2.length >= 3) break;
5264
5266
  let text = "";
5265
5267
  if (typeof msg.content === "string") {
5266
5268
  text = msg.content;
@@ -5273,10 +5275,10 @@ var SessionManager = class {
5273
5275
  const start = Math.max(0, idx - 30);
5274
5276
  const end = Math.min(text.length, idx + query.length + 60);
5275
5277
  const snippet = (start > 0 ? "\u2026" : "") + text.slice(start, end).replace(/\n/g, " ") + (end < text.length ? "\u2026" : "");
5276
- matches.push({ role: msg.role, snippet });
5278
+ matches2.push({ role: msg.role, snippet });
5277
5279
  }
5278
5280
  }
5279
- if (matches.length > 0) {
5281
+ if (matches2.length > 0) {
5280
5282
  results.push({
5281
5283
  sessionMeta: {
5282
5284
  id: data.id,
@@ -5287,7 +5289,7 @@ var SessionManager = class {
5287
5289
  updated: safeDate(data.updated),
5288
5290
  title: data.title
5289
5291
  },
5290
- matches
5292
+ matches: matches2
5291
5293
  });
5292
5294
  }
5293
5295
  } catch (err) {
@@ -9395,20 +9397,20 @@ Results sorted by most recent modification time. Automatically skips node_module
9395
9397
  if (!pattern) throw new ToolError("glob_files", "pattern is required");
9396
9398
  if (!existsSync16(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
9397
9399
  const regex = globToRegex(pattern);
9398
- const matches = [];
9399
- collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
9400
- if (matches.length === 0) {
9400
+ const matches2 = [];
9401
+ collectMatchingFiles(rootPath, rootPath, regex, matches2, maxResults);
9402
+ if (matches2.length === 0) {
9401
9403
  return `No files matched pattern: ${pattern}
9402
9404
  Searched in: ${rootPath}`;
9403
9405
  }
9404
- matches.sort((a, b) => b.mtime - a.mtime);
9406
+ matches2.sort((a, b) => b.mtime - a.mtime);
9405
9407
  const lines = [
9406
- `Found ${matches.length} file(s) matching: ${pattern}`,
9408
+ `Found ${matches2.length} file(s) matching: ${pattern}`,
9407
9409
  `Searched in: ${rootPath}`,
9408
9410
  "",
9409
- ...matches.map((m) => m.relPath)
9411
+ ...matches2.map((m) => m.relPath)
9410
9412
  ];
9411
- if (matches.length >= maxResults) {
9413
+ if (matches2.length >= maxResults) {
9412
9414
  lines.push("");
9413
9415
  lines.push(`(Results truncated at ${maxResults}. Use max_results or narrow your pattern.)`);
9414
9416
  }
@@ -10927,10 +10929,10 @@ function trustPlugin(configDir, name) {
10927
10929
  }
10928
10930
  function getInstalledPlugin(configDir, name) {
10929
10931
  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];
10932
+ const matches2 = plugins.filter((p) => p.name === name || p.name.startsWith(name));
10933
+ if (matches2.length === 0) throw new Error(`plugin not found: ${name}`);
10934
+ if (matches2.length > 1) throw new Error(`plugin name is ambiguous: ${name}`);
10935
+ return matches2[0];
10934
10936
  }
10935
10937
  function listInstalledPlugins(configDir) {
10936
10938
  const root = pluginRoot(configDir);
@@ -11451,8 +11453,8 @@ function summarizeContent(content) {
11451
11453
  function extractEvidence(content, toolNames) {
11452
11454
  const evidence = /* @__PURE__ */ new Set();
11453
11455
  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);
11456
+ const matches2 = content.match(/(?:[\w.-]+[\\/])+[\w.-]+\.(?:ts|tsx|js|jsx|json|md|css|html|py|go|rs|yml|yaml)/g) ?? [];
11457
+ for (const match of matches2.slice(0, 8)) evidence.add(match);
11456
11458
  return [...evidence].slice(0, 10);
11457
11459
  }
11458
11460
  function formatAgentResult(input) {
@@ -14194,6 +14196,80 @@ function evaluateContextPressure(input) {
14194
14196
  }
14195
14197
  var AUTO_COMPACT_INSTRUCTION = "Preserve key context, compress old content, ensure seamless continuation";
14196
14198
 
14199
+ // src/core/model-router.ts
14200
+ var TAG_REGEX = /(?:^|\s)#([a-zA-Z][\w-]{0,31})\b/g;
14201
+ function extractTags(message) {
14202
+ const tags = /* @__PURE__ */ new Set();
14203
+ let m;
14204
+ TAG_REGEX.lastIndex = 0;
14205
+ while ((m = TAG_REGEX.exec(message)) !== null) {
14206
+ tags.add(m[1].toLowerCase());
14207
+ }
14208
+ return tags;
14209
+ }
14210
+ function matches(message, matcher) {
14211
+ const trimmed = message.trim();
14212
+ const lower = trimmed.toLowerCase();
14213
+ if (matcher.tag) {
14214
+ const tags = extractTags(trimmed);
14215
+ if (!tags.has(matcher.tag.toLowerCase())) return false;
14216
+ }
14217
+ if (matcher.contains && matcher.contains.length > 0) {
14218
+ const hit = matcher.contains.some((kw) => lower.includes(kw.toLowerCase()));
14219
+ if (!hit) return false;
14220
+ }
14221
+ if (typeof matcher.maxLength === "number") {
14222
+ if (trimmed.length > matcher.maxLength) return false;
14223
+ }
14224
+ if (typeof matcher.minLength === "number") {
14225
+ if (trimmed.length < matcher.minLength) return false;
14226
+ }
14227
+ const hasAnyCondition = !!matcher.tag || matcher.contains && matcher.contains.length > 0 || typeof matcher.maxLength === "number" || typeof matcher.minLength === "number";
14228
+ return !!hasAnyCondition;
14229
+ }
14230
+ function pickModel(message, currentModel, config, availableModels = []) {
14231
+ if (!config.enabled || config.rules.length === 0) {
14232
+ return { model: currentModel, reason: "routing disabled", overridden: false };
14233
+ }
14234
+ const isAvailable = (m) => availableModels.length === 0 || availableModels.includes(m);
14235
+ for (let i = 0; i < config.rules.length; i++) {
14236
+ const rule = config.rules[i];
14237
+ if (!matches(message, rule.match)) continue;
14238
+ if (!isAvailable(rule.model)) continue;
14239
+ if (rule.model === currentModel) {
14240
+ return {
14241
+ model: currentModel,
14242
+ reason: `rule "${rule.name ?? `#${i}`}" matched (same as current)`,
14243
+ overridden: false,
14244
+ ruleIdx: i
14245
+ };
14246
+ }
14247
+ return {
14248
+ model: rule.model,
14249
+ reason: `rule "${rule.name ?? `#${i}`}" matched`,
14250
+ overridden: true,
14251
+ ruleIdx: i
14252
+ };
14253
+ }
14254
+ if (config.fallback && config.fallback !== currentModel && isAvailable(config.fallback)) {
14255
+ return {
14256
+ model: config.fallback,
14257
+ reason: "fallback",
14258
+ overridden: true
14259
+ };
14260
+ }
14261
+ return { model: currentModel, reason: "no rule matched", overridden: false };
14262
+ }
14263
+ function stripRoutingTags(message) {
14264
+ return message.replace(/(?:^|\s)#(fast|deep|default)\b/gi, " ").replace(/\s{2,}/g, " ").trim();
14265
+ }
14266
+ function decideModel(input) {
14267
+ if (!input.routingConfig || !input.routingConfig.enabled) {
14268
+ return { model: input.currentModel, reason: "routing disabled", overridden: false };
14269
+ }
14270
+ return pickModel(input.userInput, input.currentModel, input.routingConfig, input.availableModels);
14271
+ }
14272
+
14197
14273
  // src/core/context-files.ts
14198
14274
  import { existsSync as existsSync25, readFileSync as readFileSync20 } from "fs";
14199
14275
  import { join as join19, relative as relative4, resolve as resolve8 } from "path";
@@ -14544,6 +14620,84 @@ Output the Markdown content directly, do not wrap the entire file in a code bloc
14544
14620
  return parts.join("\n");
14545
14621
  }
14546
14622
 
14623
+ // src/core/memory-filters.ts
14624
+ function parseFlags(items) {
14625
+ const flags = {};
14626
+ const text = [];
14627
+ for (let i = 0; i < items.length; i++) {
14628
+ const item = items[i];
14629
+ if (item.startsWith("--")) {
14630
+ const key = item.slice(2);
14631
+ const next = items[i + 1];
14632
+ if (next && !next.startsWith("--")) {
14633
+ flags[key] = next;
14634
+ i++;
14635
+ } else {
14636
+ flags[key] = true;
14637
+ }
14638
+ } else {
14639
+ text.push(item);
14640
+ }
14641
+ }
14642
+ return { flags, text: text.join(" ").trim() };
14643
+ }
14644
+ var SCOPES = /* @__PURE__ */ new Set(["personal", "project", "session", "team"]);
14645
+ var BOOLEAN_FLAGS = ["pending", "orphaned"];
14646
+ function parseMemoryShowFilters(args) {
14647
+ const { flags, text } = parseFlags(args);
14648
+ const strayTerms = [];
14649
+ for (const key of BOOLEAN_FLAGS) {
14650
+ const v = flags[key];
14651
+ if (typeof v === "string") {
14652
+ strayTerms.push(v);
14653
+ flags[key] = true;
14654
+ }
14655
+ }
14656
+ const scopeRaw = flags["scope"];
14657
+ const freeText = [text, ...strayTerms].filter(Boolean).join(" ").trim();
14658
+ const searchRaw = typeof flags["search"] === "string" ? flags["search"] : freeText || void 0;
14659
+ const staleRaw = flags["stale"];
14660
+ let staleDays;
14661
+ let invalidStale = false;
14662
+ if (staleRaw !== void 0) {
14663
+ const n = typeof staleRaw === "string" ? Number(staleRaw) : NaN;
14664
+ if (!Number.isFinite(n) || n <= 0) invalidStale = true;
14665
+ else staleDays = n;
14666
+ }
14667
+ return {
14668
+ pending: flags["pending"] === true,
14669
+ orphaned: flags["orphaned"] === true,
14670
+ scope: typeof scopeRaw === "string" && SCOPES.has(scopeRaw) ? scopeRaw : void 0,
14671
+ search: searchRaw?.toLowerCase(),
14672
+ staleDays,
14673
+ invalidStale
14674
+ };
14675
+ }
14676
+ function filterMemoryEntries(entries, filters) {
14677
+ let out = entries;
14678
+ const applied = [];
14679
+ if (filters.orphaned) applied.push("orphaned");
14680
+ if (filters.pending) {
14681
+ out = out.filter((m) => !m.approved && !isMemoryExpired(m));
14682
+ applied.push("pending");
14683
+ }
14684
+ if (filters.scope) {
14685
+ out = out.filter((m) => m.scope === filters.scope);
14686
+ applied.push(`scope:${filters.scope}`);
14687
+ }
14688
+ if (filters.search) {
14689
+ const q = filters.search;
14690
+ out = out.filter((m) => m.content.toLowerCase().includes(q) || m.id.startsWith(q));
14691
+ applied.push(`search:"${q}"`);
14692
+ }
14693
+ if (filters.staleDays !== void 0) {
14694
+ const days = filters.staleDays;
14695
+ out = out.filter((m) => isMemoryStale(m, days));
14696
+ applied.push(`stale>${days}d`);
14697
+ }
14698
+ return { entries: out, applied };
14699
+ }
14700
+
14547
14701
  // src/hub/convergence.ts
14548
14702
  function convergenceThreshold(total) {
14549
14703
  if (total <= 0) return Infinity;
@@ -15863,7 +16017,7 @@ Cache: write=${cacheCreate} read=${cacheRead}` : "";
15863
16017
  Cost: ${formatCost(cost)}` : "";
15864
16018
  let memoryLine = "";
15865
16019
  try {
15866
- const { countPendingMemories } = await import("./persistent-memory-YBWIZWQP.js");
16020
+ const { countPendingMemories } = await import("./persistent-memory-52SE5K44.js");
15867
16021
  const pending = countPendingMemories(ctx.config.getConfigDir());
15868
16022
  if (pending > 0) memoryLine = `
15869
16023
  Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
@@ -15967,14 +16121,14 @@ async function handleSession(args, ctx) {
15967
16121
  return;
15968
16122
  }
15969
16123
  const list2 = ctx.sessions.listSessions();
15970
- const matches = list2.filter((s) => s.messageCount === filterMessages);
15971
- if (matches.length === 0) {
16124
+ const matches2 = list2.filter((s) => s.messageCount === filterMessages);
16125
+ if (matches2.length === 0) {
15972
16126
  ctx.send({ type: "info", message: `No sessions with ${filterMessages} message(s) found.` });
15973
16127
  return;
15974
16128
  }
15975
16129
  let deleted = 0;
15976
16130
  let failed = 0;
15977
- for (const s of matches) {
16131
+ for (const s of matches2) {
15978
16132
  if (ctx.sessions.deleteSession(s.id)) {
15979
16133
  deleted++;
15980
16134
  } else {
@@ -16676,7 +16830,7 @@ async function handleTest(args, ctx) {
16676
16830
  const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
16677
16831
  testArgs = isCommand ? { command: argStr } : { filter: argStr };
16678
16832
  }
16679
- const runTests = ctx.runTests ?? (await import("./run-tests-Y44SCLIO.js")).executeTests;
16833
+ const runTests = ctx.runTests ?? (await import("./run-tests-PYKKYMPA.js")).executeTests;
16680
16834
  const report = await runTests(testArgs);
16681
16835
  ctx.send({ type: "info", message: report });
16682
16836
  } catch (err) {
@@ -16934,7 +17088,7 @@ async function handleMemory(args, ctx) {
16934
17088
  ctx.handleMemoryManage(sub, args[1], sub === "expire" ? args[2] : void 0);
16935
17089
  } else if (sub === "export") {
16936
17090
  if (args[1] === "md") {
16937
- const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-YBWIZWQP.js");
17091
+ const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-52SE5K44.js");
16938
17092
  ctx.send({
16939
17093
  type: "export_data",
16940
17094
  format: "md",
@@ -16954,7 +17108,8 @@ async function handleMemory(args, ctx) {
16954
17108
  });
16955
17109
  }
16956
17110
  } else {
16957
- ctx.memoryShow();
17111
+ const isShow = sub === "show" || sub === "view" || sub === "list";
17112
+ ctx.memoryShow(isShow ? args.slice(1) : args);
16958
17113
  }
16959
17114
  }
16960
17115
  async function handleAddDir(args, ctx) {
@@ -17382,11 +17537,11 @@ async function handleSearch(args, ctx) {
17382
17537
  }
17383
17538
  const lines = [`\u{1F50D} Found ${results.length} session(s) containing "${query}"`, ""];
17384
17539
  for (const r of results) {
17385
- const { sessionMeta, matches } = r;
17540
+ const { sessionMeta, matches: matches2 } = r;
17386
17541
  const dateStr = sessionMeta.updated instanceof Date ? sessionMeta.updated.toLocaleDateString() : String(sessionMeta.updated);
17387
17542
  lines.push(`**${sessionMeta.id.slice(0, 8)}** [${dateStr}] ${sessionMeta.provider} / ${sessionMeta.model}`);
17388
17543
  if (sessionMeta.title) lines.push(` ${sessionMeta.title}`);
17389
- for (const m of matches) {
17544
+ for (const m of matches2) {
17390
17545
  const icon = m.role === "user" ? "\u{1F464}" : "\u{1F916}";
17391
17546
  lines.push(` ${icon} ${m.snippet}`);
17392
17547
  }
@@ -17517,17 +17672,18 @@ var SessionHandler = class {
17517
17672
  sessionAccruedCostUsd = 0;
17518
17673
  sessionCostIncomplete = false;
17519
17674
  /** Accumulate a TokenUsage (with optional cache fields) into sessionTokenUsage. */
17520
- addWebSessionUsage(u) {
17675
+ addWebSessionUsage(u, modelOverride) {
17676
+ const usageModel = modelOverride ?? this.currentModel;
17521
17677
  this.sessionTokenUsage.inputTokens += u.inputTokens;
17522
17678
  this.sessionTokenUsage.outputTokens += u.outputTokens;
17523
17679
  this.sessionTokenUsage.cacheCreationTokens += u.cacheCreationTokens ?? 0;
17524
17680
  this.sessionTokenUsage.cacheReadTokens += u.cacheReadTokens ?? 0;
17525
- const priced = this.costTracker.addCost(this.currentProvider, this.currentModel, u);
17681
+ const priced = this.costTracker.addCost(this.currentProvider, usageModel, u);
17526
17682
  if (priced === null) this.sessionCostIncomplete = true;
17527
17683
  else this.sessionAccruedCostUsd += priced;
17528
17684
  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}`;
17685
+ if (totalTokens > 0 && !getPricing(this.currentProvider, usageModel)) {
17686
+ const key = `${this.currentProvider}/${usageModel}`;
17531
17687
  if (!this.unknownPricingWarnings.has(key)) {
17532
17688
  this.unknownPricingWarnings.add(key);
17533
17689
  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.` });
@@ -17975,6 +18131,16 @@ var SessionHandler = class {
17975
18131
  } else {
17976
18132
  msgContent = content;
17977
18133
  }
18134
+ const routingDecision = this.computeRoutingDecision(typeof msgContent === "string" ? msgContent : content);
18135
+ if (typeof msgContent === "string") {
18136
+ msgContent = stripRoutingTags(msgContent);
18137
+ } else if (msgContent.length > 0 && msgContent[msgContent.length - 1]?.type === "text") {
18138
+ const last = msgContent[msgContent.length - 1];
18139
+ msgContent = [...msgContent.slice(0, -1), { type: "text", text: stripRoutingTags(last.text) }];
18140
+ }
18141
+ if (routingDecision.overridden) {
18142
+ this.send({ type: "info", message: `\u2192 Routed to ${routingDecision.model} (${routingDecision.reason})` });
18143
+ }
17978
18144
  const userMessage = { role: "user", content: msgContent, timestamp: /* @__PURE__ */ new Date() };
17979
18145
  session.addMessage(userMessage);
17980
18146
  const provider = this.providers.get(this.currentProvider);
@@ -17986,11 +18152,13 @@ var SessionHandler = class {
17986
18152
  const hasToolSupport = typeof provider.chatWithTools === "function";
17987
18153
  const { toolDefs, mcpBudgetNote } = hasToolSupport ? this.getFilteredToolDefs() : { toolDefs: [], mcpBudgetNote: null };
17988
18154
  if (hasToolSupport && toolDefs.length > 0) {
17989
- await this.handleChatWithTools(provider, session.messages, toolDefs, mcpBudgetNote);
18155
+ await this.handleChatWithTools(provider, session.messages, toolDefs, mcpBudgetNote, routingDecision.model);
17990
18156
  } else {
17991
- await this.handleChatSimple(provider, session.messages);
18157
+ await this.handleChatSimple(provider, session.messages, routingDecision.model);
17992
18158
  }
17993
18159
  await this.checkContextPressure();
18160
+ const budgetWarning = this.costTracker.checkBudget(this.config.get("monthlyBudget"));
18161
+ if (budgetWarning) this.send({ type: "info", message: `\u26A0 ${budgetWarning}` });
17994
18162
  } catch (err) {
17995
18163
  const parts = [];
17996
18164
  let current = err;
@@ -18009,8 +18177,9 @@ var SessionHandler = class {
18009
18177
  this.sendSessionList();
18010
18178
  }
18011
18179
  }
18012
- async handleChatSimple(provider, messages) {
18180
+ async handleChatSimple(provider, messages, modelOverride) {
18013
18181
  const session = this.sessions.current;
18182
+ const effectiveModel = modelOverride ?? this.currentModel;
18014
18183
  const modelParams = this.getModelParams();
18015
18184
  const ac = new AbortController();
18016
18185
  this.abortController = ac;
@@ -18018,7 +18187,7 @@ var SessionHandler = class {
18018
18187
  const { stable: simplStable, volatile: simplVolatile } = this.buildSystemPrompt();
18019
18188
  const stream = provider.chatStream({
18020
18189
  messages,
18021
- model: this.currentModel,
18190
+ model: effectiveModel,
18022
18191
  systemPrompt: simplStable,
18023
18192
  systemPromptVolatile: simplVolatile,
18024
18193
  stream: true,
@@ -18037,7 +18206,7 @@ var SessionHandler = class {
18037
18206
  if (chunk.done) {
18038
18207
  this.send({ type: "response_done", content: fullContent, usage: chunk.usage });
18039
18208
  if (chunk.usage) {
18040
- this.addWebSessionUsage(chunk.usage);
18209
+ this.addWebSessionUsage(chunk.usage, effectiveModel);
18041
18210
  session.addTokenUsage(chunk.usage);
18042
18211
  }
18043
18212
  if (chunk.reasoningContent) simpleReasoning = chunk.reasoningContent;
@@ -18062,8 +18231,9 @@ var SessionHandler = class {
18062
18231
  this.abortController = null;
18063
18232
  }
18064
18233
  }
18065
- async handleChatWithTools(provider, messages, toolDefs, mcpBudgetNote) {
18234
+ async handleChatWithTools(provider, messages, toolDefs, mcpBudgetNote, modelOverride) {
18066
18235
  const session = this.sessions.current;
18236
+ const effectiveModel = modelOverride ?? this.currentModel;
18067
18237
  const apiMessages = [...messages];
18068
18238
  const maxToolRounds = this.config.get("maxToolRounds") ?? DEFAULT_MAX_TOOL_ROUNDS;
18069
18239
  const autoPauseIntervalRaw = this.config.get("autoPauseInterval");
@@ -18093,7 +18263,7 @@ ${mcpBudgetNote}` : "");
18093
18263
  callModel: async (_round, extraMessages) => {
18094
18264
  const chatRequest = {
18095
18265
  messages: apiMessages,
18096
- model: this.currentModel,
18266
+ model: effectiveModel,
18097
18267
  systemPrompt,
18098
18268
  systemPromptVolatile,
18099
18269
  stream: false,
@@ -18138,7 +18308,7 @@ Details: ${errMsg.split("\n")[0]}
18138
18308
  const summaryResult = await provider.chatWithTools(
18139
18309
  {
18140
18310
  messages: apiMessages,
18141
- model: this.currentModel,
18311
+ model: effectiveModel,
18142
18312
  systemPrompt,
18143
18313
  systemPromptVolatile,
18144
18314
  stream: false,
@@ -18154,7 +18324,7 @@ Details: ${errMsg.split("\n")[0]}
18154
18324
  executeTools: async (toolCalls) => {
18155
18325
  googleSearchContext.configManager = this.config;
18156
18326
  spawnAgentContext.provider = provider;
18157
- spawnAgentContext.model = this.currentModel;
18327
+ spawnAgentContext.model = effectiveModel;
18158
18328
  spawnAgentContext.systemPrompt = systemPromptVolatile ? `${systemPrompt}
18159
18329
 
18160
18330
  ---
@@ -18528,6 +18698,24 @@ ${summaryContent}`,
18528
18698
  * 到达上下文压力阈值时自动压缩(与 REPL 同一判据,见 core/context-pressure.ts)。
18529
18699
  * 上下文窗口未知时不动——不基于猜测改用户的对话历史。
18530
18700
  */
18701
+ /**
18702
+ * 智能模型路由决策(审计附录 D-5)。候选限定在当前 provider 注册的模型内——
18703
+ * 跨 provider 换模型会破坏 API key 与对话连续性。
18704
+ */
18705
+ computeRoutingDecision(userInput) {
18706
+ let availableModels = [];
18707
+ try {
18708
+ availableModels = this.providers.get(this.currentProvider).info.models.map((m) => m.id);
18709
+ } catch {
18710
+ availableModels = [];
18711
+ }
18712
+ return decideModel({
18713
+ userInput,
18714
+ currentModel: this.currentModel,
18715
+ routingConfig: this.config.get("routing"),
18716
+ availableModels
18717
+ });
18718
+ }
18531
18719
  async checkContextPressure() {
18532
18720
  const contextWindow = this.getContextWindowSize();
18533
18721
  const session = this.sessions.current;
@@ -18690,18 +18878,27 @@ ${summaryContent}`,
18690
18878
  });
18691
18879
  }
18692
18880
  }
18693
- memoryShow() {
18881
+ memoryShow(args = []) {
18694
18882
  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) => {
18883
+ const filters = parseMemoryShowFilters(args);
18884
+ if (filters.invalidStale) {
18885
+ this.send({ type: "error", message: "Usage: /memory show --stale <days> (positive number)" });
18886
+ return;
18887
+ }
18888
+ const source = filters.orphaned ? listOrphanedProjectMemories(configDir) : listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
18889
+ const { entries, applied } = filterMemoryEntries(source, filters);
18890
+ const filterNote = applied.length > 0 ? ` (filtered: ${applied.join(", ")})` : "";
18891
+ const content = entries.length === 0 ? applied.length > 0 ? `(no entries match [${applied.join(", ")}])` : "(empty \u2014 no persistent memory entries yet)" : `Governed persistent memory${filterNote}
18892
+
18893
+ ` + entries.map((entry) => {
18697
18894
  const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
18698
- const source = [
18895
+ const source2 = [
18699
18896
  entry.source ?? "unknown",
18700
18897
  entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : "",
18701
18898
  entry.sourceProject ? `project ${entry.sourceProject}` : ""
18702
18899
  ].filter(Boolean).join(" \xB7 ");
18703
18900
  return `## ${entry.id.slice(0, 8)} \xB7 ${status} \xB7 scope:${entry.scope} \xB7 sensitivity:${entry.sensitivity}
18704
- Source: ${source}
18901
+ Source: ${source2}
18705
18902
  ${entry.content}`;
18706
18903
  }).join("\n\n");
18707
18904
  this.send({
@@ -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-PNLI3V36.js");
162
162
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
163
163
  let interrupted = false;
164
164
  const onSigint = () => {