jinzd-ai-cli 0.4.307 → 0.4.309

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 (96) hide show
  1. package/README.md +2 -1
  2. package/README.zh-CN.md +1 -1
  3. package/THIRD_PARTY_NOTICES.md +3 -0
  4. package/dist/{agent-client-6GX6QQDU.js → agent-client-TIEAYZXV.js} +4 -1
  5. package/dist/{auth-PE3Z5OHS.js → auth-QTQNZ6IX.js} +2 -2
  6. package/dist/{batch-HMADXNOD.js → batch-H4WWSZQ3.js} +3 -3
  7. package/dist/{chat-index-2D5GK2DB.js → chat-index-7CLR4CBG.js} +4 -3
  8. package/dist/{chat-index-DGUOUUQW.js → chat-index-RLVLQRRC.js} +3 -2
  9. package/dist/{chunk-CIM2PV2F.js → chunk-2E5W2E64.js} +2 -2
  10. package/dist/chunk-2XMUCC42.js +67 -0
  11. package/dist/{chunk-VAV2IJ3R.js → chunk-3CNPJLVI.js} +1 -1
  12. package/dist/{chunk-KRCYR46A.js → chunk-3GEO2LPO.js} +264 -55
  13. package/dist/{chunk-OAXLMF3U.js → chunk-3YVNDYOY.js} +1 -1
  14. package/dist/{chunk-5GQL4TSZ.js → chunk-4BXTGXZ6.js} +2 -2
  15. package/dist/{chunk-RYQBBEMM.js → chunk-4PR5AVJ5.js} +115 -19
  16. package/dist/{chunk-GHWZVPUV.js → chunk-6JHACXB3.js} +7 -5
  17. package/dist/{chunk-IUVDZVZ4.js → chunk-AXTLYOA7.js} +24 -17
  18. package/dist/{chunk-XJOK5353.js → chunk-B3MPYI4E.js} +2 -2
  19. package/dist/chunk-BMLQPSHP.js +69 -0
  20. package/dist/{chunk-NRSYBKWA.js → chunk-C7CKDFJS.js} +1 -1
  21. package/dist/{chunk-SJFZPS65.js → chunk-EAVX3RLD.js} +1 -1
  22. package/dist/{chunk-VXEF2SWG.js → chunk-EHMMBIZT.js} +16 -12
  23. package/dist/chunk-FVXNEZEW.js +161 -0
  24. package/dist/{chunk-AHL6KOGM.js → chunk-HXINWG37.js} +2 -2
  25. package/dist/{chunk-3CKJOTVM.js → chunk-IW6Q66WS.js} +4 -4
  26. package/dist/chunk-JJFJWD35.js +162 -0
  27. package/dist/{chunk-NYNSYLFY.js → chunk-JL44HGZC.js} +1 -1
  28. package/dist/{chunk-3TBXHLSC.js → chunk-K4F7QEGW.js} +1 -1
  29. package/dist/{chunk-NUJTNH76.js → chunk-LBWKMTT3.js} +1 -1
  30. package/dist/{chunk-YKJO3SI4.js → chunk-LONB36SP.js} +16 -3
  31. package/dist/{chunk-WQ3OMEC4.js → chunk-M6HLYG33.js} +1 -1
  32. package/dist/{chunk-I4GQRMFV.js → chunk-M6YV5KSS.js} +6 -4
  33. package/dist/{chunk-KBDKKHXB.js → chunk-O6CGOVD4.js} +2 -2
  34. package/dist/{chunk-L2RJPVHV.js → chunk-O7MMYJAD.js} +266 -512
  35. package/dist/chunk-QMFMST2T.js +39 -0
  36. package/dist/{chunk-AQF6IXSR.js → chunk-RBCKX2FZ.js} +11 -6
  37. package/dist/{chunk-4W6VDVQU.js → chunk-RLERORUI.js} +246 -56
  38. package/dist/{chunk-MI4SFZGX.js → chunk-RMEJR3AW.js} +5 -2
  39. package/dist/{chunk-UFW4S3RU.js → chunk-RVMCY6DP.js} +1 -1
  40. package/dist/{chunk-LF67HWWR.js → chunk-RWRZVQCB.js} +1 -1
  41. package/dist/{chunk-NQJSPMY2.js → chunk-SCG2VBPB.js} +4 -4
  42. package/dist/chunk-SXIJLSVF.js +41 -0
  43. package/dist/{chunk-YFDQIUQ3.js → chunk-T42GXE7Y.js} +18 -18
  44. package/dist/chunk-TDQW2YHK.js +65 -0
  45. package/dist/{chunk-45UVJXFQ.js → chunk-W2UOVGRC.js} +1 -1
  46. package/dist/{ci-QD2Q7NWY.js → ci-H6GPHG4T.js} +7 -7
  47. package/dist/{ci-format-42XIZO5P.js → ci-format-U47GSGYU.js} +2 -2
  48. package/dist/{constants-5CMEFZQB.js → constants-GYJTB6KI.js} +1 -1
  49. package/dist/{constants-NMASHFDL.js → constants-SNJR6546.js} +1 -1
  50. package/dist/doctor-cli-J6LAQ5LU.js +18 -0
  51. package/dist/electron-server.js +922 -842
  52. package/dist/file-checkpoint-64A2AXHE.js +7 -0
  53. package/dist/{file-checkpoint-NKBHGC7L.js → file-checkpoint-VVPUJNCI.js} +2 -1
  54. package/dist/{hub-ZLF6GMQD.js → hub-G5FMMTVZ.js} +3 -3
  55. package/dist/{hub-server-CBKCP5VD.js → hub-server-JLC7CNP7.js} +55 -17
  56. package/dist/index.js +48 -42
  57. package/dist/{indexer-P3FPRTPJ.js → indexer-GKIWSSRP.js} +3 -3
  58. package/dist/indexer-VRXDZ74A.js +10 -0
  59. package/dist/{persist-S77WRVLD.js → persist-ZZXRWFBX.js} +4 -4
  60. package/dist/{persistent-memory-GJE7HKVK.js → persistent-memory-EXJBXL33.js} +3 -3
  61. package/dist/{persistent-memory-MBSBTBGY.js → persistent-memory-LEE454DW.js} +3 -3
  62. package/dist/{pr-SXRW24JG.js → pr-6PUHVJ6K.js} +7 -7
  63. package/dist/{project-trust-MUG325AW.js → project-trust-6CJB5ZMX.js} +1 -1
  64. package/dist/{project-trust-NKYHL3VZ.js → project-trust-KGG2BWKC.js} +1 -1
  65. package/dist/run-tests-2C7MWZBN.js +12 -0
  66. package/dist/run-tests-33ZVQE3Q.js +13 -0
  67. package/dist/{semantic-7GM5M4MU.js → semantic-N227CLSC.js} +4 -4
  68. package/dist/{semantic-QOBAXEQ4.js → semantic-TRQAZSRY.js} +4 -4
  69. package/dist/{server-GGEO7SA2.js → server-AJH5TBVO.js} +231 -103
  70. package/dist/{server-MO3EU2XD.js → server-CYAB56O3.js} +16 -14
  71. package/dist/{store-SIKHCUYJ.js → store-2N6PPPBP.js} +2 -2
  72. package/dist/{store-CMSSWZF7.js → store-76VVFDXM.js} +2 -2
  73. package/dist/{task-orchestrator-JGEEQ2OE.js → task-orchestrator-HNRV3YUD.js} +15 -14
  74. package/dist/{usage-B7RIAPPT.js → usage-ZN5DU6Z7.js} +4 -4
  75. package/dist/{vector-store-JAXWDZYW.js → vector-store-T2DVUMY4.js} +2 -2
  76. package/dist/{vector-store-PWIOQFTP.js → vector-store-V6UGEYWK.js} +2 -2
  77. package/dist/web/client/app.js +7 -0
  78. package/dist/web/client/app.js.br +0 -0
  79. package/dist/web/client/app.js.gz +0 -0
  80. package/dist/web/client/util.js +14 -23
  81. package/dist/web/client/util.js.br +0 -0
  82. package/dist/web/client/util.js.gz +0 -0
  83. package/dist/web/client/vendor/DOMPurify-LICENSE.txt +202 -0
  84. package/dist/web/client/vendor/purify.es.mjs +2722 -0
  85. package/dist/web/client/vendor/purify.es.mjs.br +0 -0
  86. package/dist/web/client/vendor/purify.es.mjs.gz +0 -0
  87. package/package.json +4 -2
  88. package/dist/chunk-4BKXL7SM.js +0 -98
  89. package/dist/chunk-6BUTA5VW.js +0 -38
  90. package/dist/chunk-IW3Q7AE5.js +0 -40
  91. package/dist/chunk-SKET65WZ.js +0 -96
  92. package/dist/doctor-cli-IOVSHBDI.js +0 -18
  93. package/dist/file-checkpoint-CGH6OJVI.js +0 -6
  94. package/dist/indexer-FPKY7LCT.js +0 -10
  95. package/dist/run-tests-EQCAMFQE.js +0 -10
  96. package/dist/run-tests-V3N7YV3P.js +0 -9
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  hasSemanticIndex,
3
3
  semanticSearch
4
- } from "./chunk-KBDKKHXB.js";
5
- import "./chunk-OAXLMF3U.js";
4
+ } from "./chunk-O6CGOVD4.js";
5
+ import "./chunk-3YVNDYOY.js";
6
6
  import {
7
7
  buildChatIndex,
8
8
  clearChatIndex,
@@ -15,17 +15,22 @@ import {
15
15
  searchCandidates,
16
16
  searchChatMemory,
17
17
  updateSessionInIndex
18
- } from "./chunk-I4GQRMFV.js";
18
+ } from "./chunk-M6YV5KSS.js";
19
19
  import "./chunk-JV5N65KN.js";
20
20
  import {
21
- fileCheckpoints
22
- } from "./chunk-SKET65WZ.js";
21
+ MAX_SNAPSHOT_TOTAL_BYTES,
22
+ assertReadablePath,
23
+ canonicalPath,
24
+ fileCheckpoints,
25
+ isCredentialVault,
26
+ readSnapshot
27
+ } from "./chunk-FVXNEZEW.js";
23
28
  import {
24
29
  indexProject
25
- } from "./chunk-UFW4S3RU.js";
30
+ } from "./chunk-RVMCY6DP.js";
26
31
  import {
27
32
  loadIndex
28
- } from "./chunk-45UVJXFQ.js";
33
+ } from "./chunk-W2UOVGRC.js";
29
34
  import {
30
35
  addMemoryEntry,
31
36
  backupMemoryStore,
@@ -50,23 +55,39 @@ import {
50
55
  touchMemoryReferences,
51
56
  updateMemoryApproval,
52
57
  updateMemoryEntry
53
- } from "./chunk-CIM2PV2F.js";
58
+ } from "./chunk-2E5W2E64.js";
54
59
  import {
55
60
  DEFAULT_PATTERNS,
56
61
  redactJson,
57
62
  scanString
58
63
  } from "./chunk-FSC6KEWU.js";
59
64
  import {
65
+ atomicWriteFile,
60
66
  atomicWriteFileSync
61
- } from "./chunk-6BUTA5VW.js";
67
+ } from "./chunk-2XMUCC42.js";
62
68
  import {
63
69
  formatGitContextForPrompt,
64
70
  getGitContext,
65
71
  getGitRoot
66
72
  } from "./chunk-CGLYJRFA.js";
67
73
  import {
68
- runTestsTool
69
- } from "./chunk-KRCYR46A.js";
74
+ IS_WINDOWS,
75
+ WIN_UTF8_PREAMBLE,
76
+ currentAbortSignal,
77
+ decodeWindowsBuffer,
78
+ killProcessTree,
79
+ looksLikeGbkMojibake,
80
+ recoverGbkMojibake,
81
+ runTestsTool,
82
+ shellInvocation
83
+ } from "./chunk-RLERORUI.js";
84
+ import {
85
+ getCurrentSessionKey,
86
+ getExecutionConfigDir,
87
+ runWithExecutionContext,
88
+ runWithSessionKey,
89
+ scopedState
90
+ } from "./chunk-QMFMST2T.js";
70
91
  import {
71
92
  AGENTIC_BEHAVIOR_GUIDELINE,
72
93
  APP_NAME,
@@ -101,7 +122,65 @@ import {
101
122
  SUBAGENT_MAX_ROUNDS_LIMIT,
102
123
  VERSION,
103
124
  buildUserIdentityPrompt
104
- } from "./chunk-SJFZPS65.js";
125
+ } from "./chunk-EAVX3RLD.js";
126
+
127
+ // src/web/access.ts
128
+ import { randomBytes, timingSafeEqual } from "crypto";
129
+ import { networkInterfaces } from "os";
130
+ function equalToken(a, b) {
131
+ if (typeof a !== "string" || a.length > 4096) return false;
132
+ const left = Buffer.from(a), right = Buffer.from(b);
133
+ return left.length === right.length && timingSafeEqual(left, right);
134
+ }
135
+ var WebAccess = class {
136
+ constructor(host, externalOrigin) {
137
+ this.externalOrigin = externalOrigin;
138
+ if (!["0.0.0.0", "::"].includes(host)) this.hosts.add(host.includes(":") ? `[${host}]` : host.toLowerCase());
139
+ else for (const interfaces of Object.values(networkInterfaces())) for (const entry of interfaces ?? []) {
140
+ this.hosts.add(entry.address.includes(":") ? `[${entry.address}]` : entry.address);
141
+ }
142
+ if (externalOrigin && new URL(externalOrigin).origin !== externalOrigin) throw new Error("External origin must be an exact http(s) origin");
143
+ if (externalOrigin && !/^https?:\/\//.test(externalOrigin)) throw new Error("External origin must use HTTP(S)");
144
+ }
145
+ externalOrigin;
146
+ localToken = randomBytes(32).toString("base64url");
147
+ hosts = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
148
+ accepts(req) {
149
+ const host = req.headers.host;
150
+ if (!host) return false;
151
+ try {
152
+ const url = new URL(`http://${host}`);
153
+ const internal = this.hosts.has(url.hostname.toLowerCase()) && Number(url.port || 80) === req.socket.localPort;
154
+ const external = this.externalOrigin && new URL(this.externalOrigin).host === host;
155
+ if (!internal && !external) return false;
156
+ const origin = req.headers.origin;
157
+ return !origin || origin === (external ? this.externalOrigin : `http://${host}`);
158
+ } catch {
159
+ return false;
160
+ }
161
+ }
162
+ };
163
+ var AuthRateLimiter = class {
164
+ ips = /* @__PURE__ */ new Map();
165
+ start = 0;
166
+ count = 0;
167
+ take(ip) {
168
+ const now = Date.now();
169
+ if (now - this.start >= 6e4) {
170
+ this.start = now;
171
+ this.count = 0;
172
+ }
173
+ if (++this.count > 120) return false;
174
+ for (const [key, value] of this.ips) if (now - value.start >= 6e4) this.ips.delete(key);
175
+ let bucket = this.ips.get(ip);
176
+ if (!bucket) {
177
+ if (this.ips.size >= 2048) return false;
178
+ bucket = { start: now, count: 0 };
179
+ this.ips.set(ip, bucket);
180
+ }
181
+ return ++bucket.count <= 30;
182
+ }
183
+ };
105
184
 
106
185
  // src/web/server.ts
107
186
  import express from "express";
@@ -125,9 +204,9 @@ function imageValidationError(images) {
125
204
  }
126
205
 
127
206
  // src/web/server.ts
128
- import { join as join28, dirname as dirname11, resolve as resolve12, relative as relative6, sep as sep5, extname as extname3, resolve as resolvePath } from "path";
129
- import { existsSync as existsSync32, readFileSync as readFileSync25, readdirSync as readdirSync15, statSync as statSync16, realpathSync as realpathSync3, createReadStream } from "fs";
130
- import { networkInterfaces } from "os";
207
+ import { join as join25, dirname as dirname11, resolve as resolve12, relative as relative6, sep as sep5, extname as extname3, resolve as resolvePath } from "path";
208
+ import { existsSync as existsSync31, readFileSync as readFileSync22, readdirSync as readdirSync15, statSync as statSync16, realpathSync as realpathSync3, createReadStream } from "fs";
209
+ import { networkInterfaces as networkInterfaces2 } from "os";
131
210
 
132
211
  // src/config/config-manager.ts
133
212
  import { readFileSync, existsSync, mkdirSync } from "fs";
@@ -604,8 +683,13 @@ ${err}`
604
683
  * @returns true if config was modified and should be persisted.
605
684
  */
606
685
  migrateLegacyModels() {
607
- const LEGACY_DEEPSEEK = /* @__PURE__ */ new Set(["deepseek-chat", "deepseek-reasoner"]);
608
- const DEEPSEEK_TARGET = "deepseek-v4-flash";
686
+ const LEGACY_DEEPSEEK = /* @__PURE__ */ new Set([
687
+ "deepseek-chat",
688
+ "deepseek-reasoner",
689
+ "deepseek-v4-flash",
690
+ "deepseek-v4-flash-vision-exp"
691
+ ]);
692
+ const DEEPSEEK_TARGET = "deepseek-flash";
609
693
  const LEGACY_KIMI = /* @__PURE__ */ new Set(["kimi-k2-0711-preview", "kimi-k2-turbo-preview"]);
610
694
  const KIMI_TARGET = "kimi-k2.6";
611
695
  let changed = false;
@@ -650,8 +734,8 @@ ${err}`
650
734
  return changed;
651
735
  }
652
736
  save() {
653
- mkdirSync(this.configDir, { recursive: true });
654
- atomicWriteFileSync(this.configPath, JSON.stringify(this.config, null, 2));
737
+ mkdirSync(this.configDir, { recursive: true, mode: 448 });
738
+ atomicWriteFileSync(this.configPath, JSON.stringify(this.config, null, 2), { mode: 384 });
655
739
  }
656
740
  getApiKey(providerId) {
657
741
  const envKey = EnvLoader.getApiKey(providerId);
@@ -804,15 +888,28 @@ function deepSeekCnyPricing(cacheRead, input, output) {
804
888
  output: output / CNY_PER_USD
805
889
  };
806
890
  }
891
+ var DEEPSEEK_V4_PRO_ROUTED_TO_FLASH_AT = /* @__PURE__ */ new Date("2026-09-14T04:00:00.000Z");
892
+ var DEEPSEEK_V41_FLASH_ROUTING_CONFIRMED_AT = /* @__PURE__ */ new Date("2026-09-11T00:00:00.000Z");
893
+ function isDeepSeekPeakHour(at) {
894
+ const weekday = at.getUTCDay();
895
+ if (weekday === 0 || weekday === 6) return false;
896
+ const hour = at.getUTCHours();
897
+ return hour >= 1 && hour < 4 || hour >= 6 && hour < 10;
898
+ }
899
+ function deepSeekFlash41Pricing(at) {
900
+ return isDeepSeekPeakHour(at) ? { cacheRead: 6e-3, input: 0.3, output: 1.2 } : { cacheRead: 3e-3, input: 0.15, output: 0.6 };
901
+ }
807
902
  function getDeepSeekV4Pricing(model, at) {
903
+ if (model.startsWith("deepseek-flash")) return deepSeekFlash41Pricing(at);
808
904
  const isPro = model.startsWith("deepseek-v4-pro");
809
905
  const isFlash = model.startsWith("deepseek-v4-flash");
810
906
  if (!isPro && !isFlash) return null;
907
+ if (isPro && at >= DEEPSEEK_V4_PRO_ROUTED_TO_FLASH_AT) return deepSeekFlash41Pricing(at);
908
+ if (isFlash && at >= DEEPSEEK_V41_FLASH_ROUTING_CONFIRMED_AT) return deepSeekFlash41Pricing(at);
811
909
  if (at < DEEPSEEK_TIME_PRICING_EFFECTIVE_AT) {
812
910
  return isPro ? deepSeekCnyPricing(0.025, 3, 6) : deepSeekCnyPricing(0.02, 1, 2);
813
911
  }
814
- const beijingHour = (at.getUTCHours() + 8) % 24;
815
- const isPeak = beijingHour >= 9 && beijingHour < 12 || beijingHour >= 14 && beijingHour < 18;
912
+ const isPeak = isDeepSeekPeakHour(at);
816
913
  if (isPro) {
817
914
  return isPeak ? deepSeekCnyPricing(0.3, 9, 27) : deepSeekCnyPricing(0.15, 4.5, 13.5);
818
915
  }
@@ -1293,6 +1390,7 @@ function getDangerLevel(toolName, args) {
1293
1390
  }
1294
1391
  if (toolName === "bash") {
1295
1392
  const cmd = String(args["command"] ?? "");
1393
+ if (/\bgit\s+(?:clean\b|reset\s+--hard\b|push\b[^\n]*(?:--force\b|\s-f\b))/i.test(cmd)) return "destructive";
1296
1394
  if (/\brm\s+[^\n]*(?:-\w*[rRfF]\w*|--recursive|--force)\b/.test(cmd)) return "destructive";
1297
1395
  if (/\brm\s+\S/.test(cmd)) return "destructive";
1298
1396
  if (/\brmdir\b|\bformat\b|\bmkfs\b|\bdd\s+if=|\bshred\b|\bfdisk\b|\bparted\b/.test(cmd)) return "destructive";
@@ -1308,8 +1406,10 @@ function getDangerLevel(toolName, args) {
1308
1406
  if (/\bSet-Content\b|\bOut-File\b|\bAdd-Content\b|\bCopy-Item\b|\bMove-Item\b|\bSet-ItemProperty\b|\bNew-ItemProperty\b/i.test(cmd)) return "write";
1309
1407
  if (/\b(python3?|node|deno|bun|perl|ruby|php|bash|sh|zsh|pwsh|powershell)(\.exe)?\b[^\n]*?\s-(?:Command|EncodedCommand|c|e|p|r)\b/i.test(cmd)) return "write";
1310
1408
  if (/\bcmd(\.exe)?\s+\/[ck]\b/i.test(cmd)) return "write";
1311
- return "safe";
1409
+ if (!/[;&|`$<>\n\r(){}]/.test(cmd) && (/^(?:pwd|ls|cat|head|tail|wc|Get-ChildItem|Get-Location|Get-Content)(?:\s+[\w ./\\:*?=,@'"-]+)?\s*$/i.test(cmd.trim()) || /^echo(?:\s+[\w .,'"-]+)?\s*$/.test(cmd.trim()) || /^(?:node|python3?)\s+(?:-v|--version)\s*$/.test(cmd.trim()) || /^git\s+(?:status|log)(?:\s+(?:--short|--porcelain|--oneline|-\d+))*\s*$/.test(cmd.trim()))) return "safe";
1410
+ return "write";
1312
1411
  }
1412
+ if (toolName === "run_tests") return "write";
1313
1413
  if (toolName === "write_file") return "write";
1314
1414
  if (toolName === "edit_file") return "write";
1315
1415
  if (toolName === "save_last_response") return "write";
@@ -1324,7 +1424,7 @@ function getDangerLevel(toolName, args) {
1324
1424
  if (toolName === "git_commit") return "write";
1325
1425
  if (toolName === "git_status" || toolName === "git_diff" || toolName === "git_log") return "safe";
1326
1426
  if (toolName === "notebook_edit") return "write";
1327
- if (toolName === "read_file" || toolName === "list_dir" || toolName === "grep_files" || toolName === "glob_files" || toolName === "web_fetch" || toolName === "save_memory" || toolName === "ask_user" || toolName === "write_todos" || toolName === "google_search" || toolName === "spawn_agent" || toolName === "run_tests") return "safe";
1427
+ if (toolName === "read_file" || toolName === "list_dir" || toolName === "grep_files" || toolName === "glob_files" || toolName === "web_fetch" || toolName === "save_memory" || toolName === "ask_user" || toolName === "write_todos" || toolName === "google_search" || toolName === "spawn_agent") return "safe";
1328
1428
  return "write";
1329
1429
  }
1330
1430
  function schemaToJsonSchema(schema) {
@@ -4084,30 +4184,34 @@ var DeepSeekProvider = class extends OpenAICompatibleProvider {
4084
4184
  info = {
4085
4185
  id: "deepseek",
4086
4186
  displayName: "DeepSeek",
4087
- defaultModel: "deepseek-v4-flash",
4187
+ defaultModel: "deepseek-flash",
4088
4188
  apiKeyEnvVar: "AICLI_API_KEY_DEEPSEEK",
4089
4189
  requiresApiKey: true,
4090
4190
  baseUrl: this.defaultBaseUrl,
4091
4191
  models: [
4092
- // ── V4 family (2026-04-23+):1M context,支持 Thinking / Non-Thinking 双模式 ──
4192
+ // ── V4.1 Flash (2026-09):1M context,原生多模态,Thinking / Non-Thinking 双模式 ──
4193
+ // model code 实测取自端点本身:`/v1/models` 只列 `deepseek-flash` 与 `deepseek-v4-pro`,
4194
+ // 传 `deepseek-v4.1-flash` 报 400 并把合法名字写在错误消息里。**不要**按发布公告里的
4195
+ // 版本号拼 code(v0.4.198 的 `glm-5.2[1m]` 就是这么栽的)。
4093
4196
  {
4094
- id: "deepseek-v4-pro",
4095
- displayName: "DeepSeek V4 Pro (0813, 1.6T MoE, 49B active)",
4197
+ id: "deepseek-flash",
4198
+ displayName: "DeepSeek V4.1 Flash (multimodal, 1M)",
4096
4199
  contextWindow: 1048576,
4097
4200
  supportsStreaming: true,
4098
4201
  supportsThinking: true
4099
4202
  },
4100
4203
  {
4101
- id: "deepseek-v4-flash",
4102
- displayName: "DeepSeek V4 Flash (0731, 284B MoE, 13B active)",
4204
+ id: "deepseek-v4-pro",
4205
+ displayName: "DeepSeek V4 Pro (text-only; routes to V4.1 Flash from 2026-09-14)",
4103
4206
  contextWindow: 1048576,
4104
4207
  supportsStreaming: true,
4105
4208
  supportsThinking: true
4106
4209
  }
4107
- // Legacy aliases (deepseek-chat / deepseek-reasoner) removed in v0.4.140 —
4108
- // they retire 2026-07-24 and DeepSeek already routes them to V4 Flash server-side.
4109
- // Pre-existing sessions were migrated to deepseek-v4-flash; pricing.ts keeps the
4110
- // old ids for any historical cost calculations.
4210
+ // Legacy aliases:`deepseek-v4-flash` / `deepseek-v4-flash-vision-exp` 的底层模型已退役,
4211
+ // 端点仍接受这两个名字并路由到 V4.1 Flash(实测传 `deepseek-v4-flash` 返回 200 且响应
4212
+ // `model` 字段回显 `deepseek-flash`)。它们不再列进推荐清单,config 里 pin 过的会被
4213
+ // `migrateLegacyModels()` 自愈到 `deepseek-flash`;pricing.ts 保留旧 id 供历史成本回算。
4214
+ // `deepseek-chat` / `deepseek-reasoner` 早在 v0.4.140 移除(2026-07-24 停用)。
4111
4215
  ]
4112
4216
  };
4113
4217
  /**
@@ -4678,7 +4782,7 @@ function clearModelCache(configDir, providerId) {
4678
4782
  delete cache.providers[providerId];
4679
4783
  saveModelCache(configDir, cache);
4680
4784
  }
4681
- function normalizeLiveModels(models, fetchedAt, source = "live") {
4785
+ function normalizeLiveModels(models, fetchedAt, source2 = "live") {
4682
4786
  const seen = /* @__PURE__ */ new Set();
4683
4787
  const normalized = [];
4684
4788
  for (const model of models) {
@@ -4690,7 +4794,7 @@ function normalizeLiveModels(models, fetchedAt, source = "live") {
4690
4794
  contextWindow: model.contextWindow || LIVE_DEFAULT_CONTEXT,
4691
4795
  supportsStreaming: model.supportsStreaming ?? true,
4692
4796
  ...model.supportsThinking !== void 0 ? { supportsThinking: model.supportsThinking } : {},
4693
- source,
4797
+ source: source2,
4694
4798
  available: true,
4695
4799
  metadataUnknown: model.metadataUnknown ?? !model.contextWindow,
4696
4800
  ...fetchedAt ? { fetchedAt } : {}
@@ -5463,26 +5567,33 @@ var SessionManager = class {
5463
5567
  }
5464
5568
  /** P1-PERF-03: `force` 跳过脏检查(用于进程退出、fork 等必须落盘场景) */
5465
5569
  async save(force = false) {
5466
- if (!this._current) return;
5467
- if (this._current.messages.length === 0) return;
5468
- if (!force && !this._current.dirty) return;
5570
+ const session = this._current;
5571
+ if (!session) return;
5572
+ if (session.messages.length === 0) return;
5573
+ if (!force && !session.dirty) return;
5469
5574
  mkdirSync3(this.historyDir, { recursive: true });
5470
- const filePath = join4(this.historyDir, `${this._current.id}.json`);
5471
- const raw = this._current.toJSON();
5575
+ const filePath = join4(this.historyDir, `${session.id}.json`);
5576
+ const raw = session.toJSON();
5472
5577
  const opts = this.redactOptionsForSave();
5473
5578
  const { value: payload, hits } = redactJson(raw, opts);
5474
5579
  this.lastRedactionHits = hits.length;
5475
- atomicWriteFileSync(filePath, JSON.stringify(payload, null, 2));
5476
- this._current.dirty = false;
5580
+ const serialized = JSON.stringify(payload, null, 2);
5581
+ session.dirty = false;
5582
+ try {
5583
+ await atomicWriteFile(filePath, serialized, { mode: 384 });
5584
+ } catch (error) {
5585
+ session.dirty = true;
5586
+ throw error;
5587
+ }
5477
5588
  try {
5478
5589
  updateSessionInIndex(this.historyDir, {
5479
- id: this._current.id,
5480
- provider: this._current.provider,
5481
- model: this._current.model,
5482
- title: this._current.title,
5483
- messageCount: this._current.messages.length,
5484
- created: this._current.created,
5485
- updated: this._current.updated
5590
+ id: session.id,
5591
+ provider: session.provider,
5592
+ model: session.model,
5593
+ title: session.title,
5594
+ messageCount: session.messages.length,
5595
+ created: session.created,
5596
+ updated: session.updated
5486
5597
  }, filePath);
5487
5598
  } catch {
5488
5599
  }
@@ -5633,16 +5744,22 @@ var SessionManager = class {
5633
5744
  };
5634
5745
 
5635
5746
  // src/tools/builtin/bash.ts
5636
- import { spawn as spawn3 } from "child_process";
5747
+ import { spawn as spawn2 } from "child_process";
5637
5748
  import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
5638
- import { platform as platform3 } from "os";
5749
+ import { platform as platform2 } from "os";
5639
5750
  import { resolve } from "path";
5640
5751
 
5641
5752
  // src/tools/undo-stack.ts
5642
- import { readFileSync as readFileSync5, writeFileSync, unlinkSync as unlinkSync3, rmdirSync, existsSync as existsSync5 } from "fs";
5753
+ import { writeFileSync, unlinkSync as unlinkSync3, rmdirSync, existsSync as existsSync5 } from "fs";
5643
5754
  var MAX_UNDO_DEPTH = 20;
5644
5755
  var UndoStack = class {
5645
- stack = [];
5756
+ stateKey = /* @__PURE__ */ Symbol("stack");
5757
+ get stack() {
5758
+ return scopedState(this.stateKey, () => ({ items: [] })).items;
5759
+ }
5760
+ set stack(items) {
5761
+ scopedState(this.stateKey, () => ({ items: [] })).items = items;
5762
+ }
5646
5763
  /**
5647
5764
  * 在执行文件写入操作之前调用,保存当前状态。
5648
5765
  * @param filePath 将要被写入的文件路径
@@ -5651,11 +5768,7 @@ var UndoStack = class {
5651
5768
  push(filePath, description) {
5652
5769
  let previousContent = null;
5653
5770
  if (existsSync5(filePath)) {
5654
- try {
5655
- previousContent = readFileSync5(filePath, "utf-8");
5656
- } catch {
5657
- return;
5658
- }
5771
+ previousContent = readSnapshot(filePath);
5659
5772
  }
5660
5773
  this.pushEntry({
5661
5774
  filePath,
@@ -5692,7 +5805,7 @@ var UndoStack = class {
5692
5805
  /** 内部统一入栈方法,含溢出裁剪 */
5693
5806
  pushEntry(entry) {
5694
5807
  this.stack.push(entry);
5695
- if (this.stack.length > MAX_UNDO_DEPTH) {
5808
+ while (this.stack.length > MAX_UNDO_DEPTH || this.stack.reduce((n, e) => n + Buffer.byteLength(e.previousContent ?? ""), 0) > MAX_SNAPSHOT_TOTAL_BYTES) {
5696
5809
  this.stack.shift();
5697
5810
  }
5698
5811
  }
@@ -5753,186 +5866,17 @@ var UndoStack = class {
5753
5866
  };
5754
5867
  var undoStack = new UndoStack();
5755
5868
 
5756
- // src/core/interrupt.ts
5757
- var controller = new AbortController();
5758
- function currentAbortSignal() {
5759
- return controller.signal;
5760
- }
5761
-
5762
- // src/tools/session-context.ts
5763
- import { AsyncLocalStorage } from "async_hooks";
5764
- var als = new AsyncLocalStorage();
5765
- var DEFAULT_SESSION_KEY = "__default__";
5766
- function getCurrentSessionKey() {
5767
- const ctx = als.getStore();
5768
- if (!ctx || !ctx.sessionKey) return DEFAULT_SESSION_KEY;
5769
- return ctx.sessionKey;
5770
- }
5771
- function runWithSessionKey(sessionKey, fn) {
5772
- const key = sessionKey && sessionKey.length > 0 ? sessionKey : DEFAULT_SESSION_KEY;
5773
- return als.run({ sessionKey: key }, fn);
5774
- }
5775
-
5776
- // src/tools/win-shell.ts
5777
- import { platform } from "os";
5778
- import { spawn } from "child_process";
5779
- var IS_WINDOWS = platform() === "win32";
5780
- var WIN_UTF8_PREAMBLE = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $OutputEncoding = [System.Text.Encoding]::UTF8; ";
5781
- function shellInvocation(command) {
5782
- if (IS_WINDOWS) {
5783
- return {
5784
- shell: "powershell.exe",
5785
- args: ["-NoProfile", "-NonInteractive", "-Command", WIN_UTF8_PREAMBLE + command]
5786
- };
5787
- }
5788
- return { shell: process.env["SHELL"] ?? "/bin/bash", args: ["-c", command] };
5789
- }
5790
- function decodeWindowsBuffer(buf) {
5791
- const utf8 = buf.toString("utf-8");
5792
- if (!utf8.includes("\uFFFD")) return utf8;
5793
- try {
5794
- return new TextDecoder("gbk").decode(buf);
5795
- } catch {
5796
- return utf8;
5797
- }
5798
- }
5799
- var GBK_MOJIBAKE_FINGERPRINTS = [
5800
- "\uFFFD",
5801
- "\u9215",
5802
- "\u951B",
5803
- "\u9983",
5804
- "\u9241",
5805
- "\u6D93",
5806
- "\u935B",
5807
- "\u9428",
5808
- "\u7EFE",
5809
- "\u95AB",
5810
- "\u9593",
5811
- "\u95BF",
5812
- "\u68E3",
5813
- "\u95B4",
5814
- "\u5A11",
5815
- "\u95B8",
5816
- "\u95BB",
5817
- "\u7F01",
5818
- "\u95C1"
5819
- ];
5820
- var gbkReverse = null;
5821
- function gbkReverseMap() {
5822
- if (gbkReverse) return gbkReverse;
5823
- const map = /* @__PURE__ */ new Map();
5824
- try {
5825
- const dec = new TextDecoder("gbk");
5826
- for (let hi = 129; hi <= 254; hi++) {
5827
- for (let lo = 64; lo <= 254; lo++) {
5828
- if (lo === 127) continue;
5829
- const ch = dec.decode(new Uint8Array([hi, lo]));
5830
- if (ch.length === 1 && ch !== "\uFFFD" && !map.has(ch)) map.set(ch, [hi, lo]);
5831
- }
5832
- }
5833
- } catch {
5834
- }
5835
- gbkReverse = map;
5836
- return map;
5837
- }
5838
- function decodeRunAsChinese(bytes) {
5839
- for (let start = 0; start <= 2 && start < bytes.length; start++) {
5840
- for (let end = 0; end <= 2 && bytes.length - end > start; end++) {
5841
- const slice = bytes.slice(start, bytes.length - end);
5842
- if (slice.length < 6) continue;
5843
- try {
5844
- const out = new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(slice));
5845
- if (/[一-鿿]/.test(out)) return out;
5846
- } catch {
5847
- }
5848
- }
5849
- }
5850
- return null;
5851
- }
5852
- function recoverGbkMojibake(text) {
5853
- if (!text) return null;
5854
- const map = gbkReverseMap();
5855
- if (map.size === 0) return null;
5856
- let run = [];
5857
- let best = null;
5858
- const flush2 = () => {
5859
- if (run.length >= 4) {
5860
- const bytes = [];
5861
- for (const p of run) bytes.push(p[0], p[1]);
5862
- const out = decodeRunAsChinese(bytes);
5863
- if (out && (!best || out.length > best.length)) best = out;
5864
- }
5865
- run = [];
5866
- };
5867
- for (const ch of text) {
5868
- const pair = ch.codePointAt(0) < 128 ? void 0 : map.get(ch);
5869
- if (pair) run.push(pair);
5870
- else flush2();
5871
- }
5872
- flush2();
5873
- return best;
5874
- }
5875
- function looksLikeGbkMojibake(text) {
5876
- if (recoverGbkMojibake(text) !== null) return true;
5877
- let hits = 0;
5878
- for (const ch of GBK_MOJIBAKE_FINGERPRINTS) {
5879
- if (text.includes(ch) && ++hits >= 2) return true;
5880
- }
5881
- return false;
5882
- }
5883
- function killProcessTree(proc) {
5884
- if (proc.killed || proc.exitCode !== null) return;
5885
- if (IS_WINDOWS && proc.pid) {
5886
- try {
5887
- const tk = spawn("taskkill", ["/PID", String(proc.pid), "/T", "/F"], {
5888
- windowsHide: true,
5889
- stdio: "ignore"
5890
- });
5891
- tk.on("exit", () => {
5892
- try {
5893
- proc.kill();
5894
- } catch {
5895
- }
5896
- });
5897
- tk.on("error", () => {
5898
- try {
5899
- proc.kill();
5900
- } catch {
5901
- }
5902
- });
5903
- } catch {
5904
- try {
5905
- proc.kill();
5906
- } catch {
5907
- }
5908
- }
5909
- return;
5910
- }
5911
- try {
5912
- proc.kill("SIGTERM");
5913
- } catch {
5914
- }
5915
- setTimeout(() => {
5916
- if (!proc.killed && proc.exitCode === null) {
5917
- try {
5918
- proc.kill("SIGKILL");
5919
- } catch {
5920
- }
5921
- }
5922
- }, 500).unref();
5923
- }
5924
-
5925
5869
  // src/tools/shell-pool.ts
5926
- import { spawn as spawn2 } from "child_process";
5927
- import { platform as platform2 } from "os";
5928
- var IS_WINDOWS2 = platform2() === "win32";
5870
+ import { spawn } from "child_process";
5871
+ import { platform } from "os";
5872
+ var IS_WINDOWS2 = platform() === "win32";
5929
5873
  var SHELL = IS_WINDOWS2 ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
5930
5874
  var pool = /* @__PURE__ */ new Map();
5931
5875
  var IDLE_EVICT_MS = 5 * 6e4;
5932
5876
  var evictionTimer = null;
5933
5877
  function spawnShell(cwd) {
5934
5878
  const shellArgs = IS_WINDOWS2 ? ["-NoProfile", "-NonInteractive", "-Command", "-"] : ["-s"];
5935
- const proc = spawn2(SHELL, shellArgs, {
5879
+ const proc = spawn(SHELL, shellArgs, {
5936
5880
  cwd,
5937
5881
  windowsHide: true,
5938
5882
  stdio: ["pipe", "pipe", "pipe"]
@@ -6123,7 +6067,7 @@ function cleanupPoolSession(sessionKey) {
6123
6067
  }
6124
6068
 
6125
6069
  // src/tools/builtin/bash.ts
6126
- var IS_WINDOWS3 = platform3() === "win32";
6070
+ var IS_WINDOWS3 = platform2() === "win32";
6127
6071
  var SHELL2 = IS_WINDOWS3 ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
6128
6072
  var cwdBySession = /* @__PURE__ */ new Map();
6129
6073
  function getCwd() {
@@ -6706,7 +6650,7 @@ function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
6706
6650
  function runShell(command, opts) {
6707
6651
  return new Promise((resolvePromise) => {
6708
6652
  const shellArgs = IS_WINDOWS3 ? ["-NoProfile", "-NonInteractive", "-Command", WIN_UTF8_PREAMBLE + command] : ["-c", command];
6709
- const child = spawn3(SHELL2, shellArgs, {
6653
+ const child = spawn2(SHELL2, shellArgs, {
6710
6654
  cwd: opts.cwd,
6711
6655
  env: opts.env,
6712
6656
  windowsHide: true,
@@ -6785,16 +6729,16 @@ function updateCwdFromCommand(command, baseCwd) {
6785
6729
  }
6786
6730
 
6787
6731
  // src/tools/builtin/read-file.ts
6788
- import { readFileSync as readFileSync6, existsSync as existsSync7, statSync as statSync3, readdirSync as readdirSync3 } from "fs";
6732
+ import { readFileSync as readFileSync5, existsSync as existsSync7, statSync as statSync3, readdirSync as readdirSync3 } from "fs";
6789
6733
  import { execFileSync } from "child_process";
6790
6734
  import { extname, resolve as resolve2, basename, sep, dirname as dirname2 } from "path";
6791
6735
  import { homedir as homedir2 } from "os";
6792
6736
 
6793
6737
  // src/tools/builtin/notebook-utils.ts
6794
- function cellSourceToString(source) {
6795
- if (!source) return "";
6796
- if (Array.isArray(source)) return source.join("");
6797
- return source;
6738
+ function cellSourceToString(source2) {
6739
+ if (!source2) return "";
6740
+ if (Array.isArray(source2)) return source2.join("");
6741
+ return source2;
6798
6742
  }
6799
6743
  function stringToCellSource(content) {
6800
6744
  if (content.length === 0) return [];
@@ -6849,16 +6793,16 @@ function renderNotebookAsText(nb, filePath) {
6849
6793
  nb.cells.forEach((cell, idx) => {
6850
6794
  const cellNum = idx + 1;
6851
6795
  const type = cell.cell_type;
6852
- const source = cellSourceToString(cell.source);
6796
+ const source2 = cellSourceToString(cell.source);
6853
6797
  if (type === "markdown") {
6854
6798
  lines.push(`## Cell ${cellNum} [markdown]`);
6855
- lines.push(source);
6799
+ lines.push(source2);
6856
6800
  lines.push("");
6857
6801
  } else if (type === "code") {
6858
6802
  const execLabel = cell.execution_count != null ? ` (execution #${cell.execution_count})` : "";
6859
6803
  lines.push(`## Cell ${cellNum} [code${execLabel}]`);
6860
6804
  lines.push("```" + language);
6861
- lines.push(source);
6805
+ lines.push(source2);
6862
6806
  lines.push("```");
6863
6807
  const outputText = extractOutputText(cell.outputs);
6864
6808
  if (outputText) {
@@ -6870,7 +6814,7 @@ function renderNotebookAsText(nb, filePath) {
6870
6814
  lines.push("");
6871
6815
  } else if (type === "raw") {
6872
6816
  lines.push(`## Cell ${cellNum} [raw]`);
6873
- lines.push(source);
6817
+ lines.push(source2);
6874
6818
  lines.push("");
6875
6819
  }
6876
6820
  });
@@ -6895,6 +6839,7 @@ function createCell(cellType, content) {
6895
6839
  // src/tools/builtin/read-file.ts
6896
6840
  var MAX_FILE_BYTES = 10 * 1024 * 1024;
6897
6841
  function getHardRefusal(normalizedPath) {
6842
+ if (isCredentialVault(normalizedPath)) return "[Refused] Credential vault files cannot be read by tools.";
6898
6843
  const home2 = homedir2();
6899
6844
  const p = normalizedPath.toLowerCase();
6900
6845
  const base = basename(normalizedPath).toLowerCase();
@@ -7069,7 +7014,7 @@ var readFileTool = {
7069
7014
  const offsetLine = typeof rawOffset === "number" && Number.isFinite(rawOffset) && rawOffset > 0 ? Math.floor(rawOffset) : void 0;
7070
7015
  const limitLines = typeof rawLimit === "number" && Number.isFinite(rawLimit) && rawLimit > 0 ? Math.floor(rawLimit) : void 0;
7071
7016
  if (!filePath) throw new ToolError("read_file", "path is required");
7072
- const normalizedPath = resolve2(filePath);
7017
+ const normalizedPath = canonicalPath(filePath);
7073
7018
  const refusal = getHardRefusal(normalizedPath);
7074
7019
  if (refusal) return refusal;
7075
7020
  if (!existsSync7(normalizedPath)) {
@@ -7136,7 +7081,7 @@ Suggestion: read existing text versions (.md / .txt) in the project, or install
7136
7081
  }
7137
7082
  if (ext === ".ipynb") {
7138
7083
  try {
7139
- const raw = readFileSync6(normalizedPath, "utf-8");
7084
+ const raw = readFileSync5(normalizedPath, "utf-8");
7140
7085
  const nb = parseNotebook(raw);
7141
7086
  return renderNotebookAsText(nb, filePath);
7142
7087
  } catch (err) {
@@ -7154,8 +7099,8 @@ Do NOT claim you inspected this image \u2014 you have not seen it.`;
7154
7099
  return `[Binary file: ${filePath} (${ext})]
7155
7100
  This is a binary file and cannot be read as text. If there is a text version (.md / .txt) in the project, please read that instead.`;
7156
7101
  }
7157
- const { readFile: readFile2 } = await import("fs/promises");
7158
- const buf = size > 1048576 ? await readFile2(normalizedPath) : readFileSync6(normalizedPath);
7102
+ const { readFile } = await import("fs/promises");
7103
+ const buf = size > 1048576 ? await readFile(normalizedPath) : readFileSync5(normalizedPath);
7159
7104
  if (encoding === "base64") {
7160
7105
  return `[File: ${filePath} | base64]
7161
7106
 
@@ -7193,12 +7138,12 @@ ${content}`;
7193
7138
  };
7194
7139
 
7195
7140
  // src/tools/builtin/write-file.ts
7196
- import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync10, statSync as statSync4 } from "fs";
7141
+ import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
7197
7142
  import { dirname as dirname5 } from "path";
7198
7143
 
7199
7144
  // src/tools/executor.ts
7200
7145
  import chalk3 from "chalk";
7201
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
7146
+ import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
7202
7147
  import { tmpdir } from "os";
7203
7148
 
7204
7149
  // src/core/readline-internal.ts
@@ -7438,7 +7383,7 @@ function simpleDiff(oldLines, newLines) {
7438
7383
  // src/tools/hooks.ts
7439
7384
  import { execSync } from "child_process";
7440
7385
  import { createHash } from "crypto";
7441
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7 } from "fs";
7386
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6 } from "fs";
7442
7387
  import { join as join5 } from "path";
7443
7388
  var TRUST_FILE = "hooks-trust.json";
7444
7389
  var DEFAULT_TIMEOUT_MS = 5e3;
@@ -7454,7 +7399,7 @@ function loadHookTrustStore(configDir) {
7454
7399
  const file = hookTrustPath(configDir);
7455
7400
  if (!existsSync8(file)) return { records: [] };
7456
7401
  try {
7457
- const parsed = JSON.parse(readFileSync7(file, "utf-8"));
7402
+ const parsed = JSON.parse(readFileSync6(file, "utf-8"));
7458
7403
  return { records: Array.isArray(parsed.records) ? parsed.records : [] };
7459
7404
  } catch {
7460
7405
  return { records: [] };
@@ -7468,9 +7413,9 @@ function hashHook(event, command) {
7468
7413
  return createHash("sha256").update(`${event}
7469
7414
  ${command}`, "utf-8").digest("hex");
7470
7415
  }
7471
- function isTrusted(source, event, hash, store) {
7472
- if (source === "user" || source === "managed") return true;
7473
- return store.records.some((r) => r.source === source && r.event === event && r.hash === hash);
7416
+ function isTrusted(source2, event, hash, store) {
7417
+ if (source2 === "user" || source2 === "managed") return true;
7418
+ return store.records.some((r) => r.source === source2 && r.event === event && r.hash === hash);
7474
7419
  }
7475
7420
  function listHooks(config, configDir) {
7476
7421
  if (!config || config.enabled === false) return [];
@@ -7478,15 +7423,15 @@ function listHooks(config, configDir) {
7478
7423
  const out = [];
7479
7424
  const push = (event, hook, index) => {
7480
7425
  if (!hook.command) return;
7481
- const source = hook.source ?? "user";
7426
+ const source2 = hook.source ?? "user";
7482
7427
  const hash = hashHook(event, hook.command);
7483
7428
  out.push({
7484
7429
  id: `${event}:${index}`,
7485
7430
  event,
7486
7431
  command: hook.command,
7487
- source,
7432
+ source: source2,
7488
7433
  hash,
7489
- trusted: isTrusted(source, event, hash, store),
7434
+ trusted: isTrusted(source2, event, hash, store),
7490
7435
  disabled: hook.disabled === true,
7491
7436
  description: hook.description
7492
7437
  });
@@ -7562,15 +7507,15 @@ function runLifecycleHooks(config, event, payload, opts) {
7562
7507
  for (let i = 0; i < hooks.length; i++) {
7563
7508
  const hook = hooks[i];
7564
7509
  if (!hook.command || hook.disabled) continue;
7565
- const source = hook.source ?? "user";
7510
+ const source2 = hook.source ?? "user";
7566
7511
  const hash = hashHook(event, hook.command);
7567
7512
  const descriptor = {
7568
7513
  id: `${event}:${i}`,
7569
7514
  event,
7570
7515
  command: hook.command,
7571
- source,
7516
+ source: source2,
7572
7517
  hash,
7573
- trusted: isTrusted(source, event, hash, store),
7518
+ trusted: isTrusted(source2, event, hash, store),
7574
7519
  disabled: false,
7575
7520
  description: hook.description
7576
7521
  };
@@ -7719,7 +7664,8 @@ var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
7719
7664
  "recall_memory"
7720
7665
  ]);
7721
7666
  function normalizePathForPermission(value) {
7722
- return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
7667
+ const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
7668
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
7723
7669
  }
7724
7670
  function canonicalizeWithNearestParent(value) {
7725
7671
  const missing = [];
@@ -7807,7 +7753,7 @@ function profileHardDecision(profileName, toolName, args, dangerLevel, ctx) {
7807
7753
  return { action: "deny", reason: "file write target is outside workspace/temp roots", profileName, hardBoundary: true };
7808
7754
  }
7809
7755
  }
7810
- if (toolName === "bash" || toolName === "run_interactive" || toolName === "task_create") {
7756
+ if (toolName === "bash" || toolName === "run_interactive" || toolName === "task_create" || toolName === "run_tests") {
7811
7757
  return {
7812
7758
  action: "confirm",
7813
7759
  reason: "shell/process writes cannot be proven to stay inside workspace/temp roots",
@@ -7909,9 +7855,9 @@ function checkNetworkPolicy(toolName, args, policy) {
7909
7855
  if (!policy?.enabled) return null;
7910
7856
  let action = networkToolAction(toolName, policy);
7911
7857
  let reason = "network access requires confirmation";
7912
- if (toolName === "bash") {
7858
+ if (["bash", "run_tests", "run_interactive", "task_create"].includes(toolName)) {
7913
7859
  const command = String(args["command"] ?? "");
7914
- if (!isShellNetworkCommand(command)) return null;
7860
+ if (toolName === "bash" && !isShellNetworkCommand(command) && dangerIsReadOnly(command)) return null;
7915
7861
  action = policy.tools?.shell ?? policy.defaultAction ?? "confirm";
7916
7862
  reason = "shell command may access the network";
7917
7863
  }
@@ -7947,6 +7893,9 @@ function checkNetworkPolicy(toolName, args, policy) {
7947
7893
  if (!permissionAction) return null;
7948
7894
  return { action: permissionAction, reason, profileName: "networkPolicy" };
7949
7895
  }
7896
+ function dangerIsReadOnly(command) {
7897
+ return /^(?:pwd|echo [\w .-]+|git status(?: --short)?)$/.test(command.trim());
7898
+ }
7950
7899
 
7951
7900
  // src/tools/truncate.ts
7952
7901
  var DEFAULT_MAX_TOOL_OUTPUT_CHARS = 12e3;
@@ -8173,8 +8122,7 @@ function getTopUsedTools(limit = 5) {
8173
8122
  }
8174
8123
 
8175
8124
  // src/tools/action-classifier.ts
8176
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
8177
- import { isAbsolute as isAbsolute2, join as join7, resolve as resolve4 } from "path";
8125
+ import { isAbsolute as isAbsolute2, resolve as resolve4 } from "path";
8178
8126
  var RECENT_DENIED_LIMIT = 50;
8179
8127
  var recentlyDenied = [];
8180
8128
  function recordRecentlyDeniedAutoAction(call, classification) {
@@ -8194,7 +8142,8 @@ function clearRecentlyDeniedAutoActions() {
8194
8142
  recentlyDenied.splice(0, recentlyDenied.length);
8195
8143
  }
8196
8144
  function normalizePath(value) {
8197
- return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
8145
+ const normalized = canonicalPath(value).replace(/\\/g, "/").replace(/\/+$/, "");
8146
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
8198
8147
  }
8199
8148
  function isInside(root, target) {
8200
8149
  const absoluteRoot = resolve4(root);
@@ -8207,56 +8156,6 @@ function getPathArg(call) {
8207
8156
  const raw = call.arguments.path ?? call.arguments.filePath ?? call.arguments.outputPath ?? call.arguments.file;
8208
8157
  return typeof raw === "string" && raw.trim() ? raw.trim() : null;
8209
8158
  }
8210
- function splitShellWords(command) {
8211
- const out = [];
8212
- const re = /"([^"]*)"|'([^']*)'|([^\s]+)/g;
8213
- let m;
8214
- while (m = re.exec(command)) out.push(m[1] ?? m[2] ?? m[3] ?? "");
8215
- return out.filter(Boolean);
8216
- }
8217
- function readPackageManifest(root) {
8218
- const p = join7(root, "package.json");
8219
- if (!existsSync10(p)) return null;
8220
- try {
8221
- return JSON.parse(readFileSync8(p, "utf-8"));
8222
- } catch {
8223
- return null;
8224
- }
8225
- }
8226
- function manifestHasDependency(root, name) {
8227
- const manifest = readPackageManifest(root);
8228
- if (!manifest) return false;
8229
- for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
8230
- const deps = manifest[key];
8231
- if (deps && typeof deps === "object" && Object.prototype.hasOwnProperty.call(deps, name)) return true;
8232
- }
8233
- return false;
8234
- }
8235
- function lockfileMentions(root, name) {
8236
- for (const file of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]) {
8237
- const p = join7(root, file);
8238
- if (!existsSync10(p)) continue;
8239
- try {
8240
- if (readFileSync8(p, "utf-8").includes(name)) return true;
8241
- } catch {
8242
- }
8243
- }
8244
- return false;
8245
- }
8246
- function getInstallPackages(command) {
8247
- const words = splitShellWords(command);
8248
- const cmd = words[0]?.toLowerCase();
8249
- const sub = words[1]?.toLowerCase();
8250
- if (!cmd || !sub) return [];
8251
- const isInstall = cmd === "npm" && (sub === "install" || sub === "i" || sub === "add") || (cmd === "pnpm" || cmd === "yarn") && (sub === "add" || sub === "install");
8252
- if (!isInstall) return [];
8253
- return words.slice(2).filter((w) => !w.startsWith("-")).filter((w) => w !== "." && w !== "./").map((w) => w.replace(/^@([^/]+)\/([^@]+)(?:@.+)?$/, "@$1/$2").replace(/^([^@]+)@.+$/, "$1"));
8254
- }
8255
- function isPlainGitPush(command) {
8256
- if (!/^\s*git\s+push\b/i.test(command)) return false;
8257
- if (/--force|-f\b|\+[^\s]+/i.test(command)) return false;
8258
- return !/[;&|`$<>\n\r]/.test(command);
8259
- }
8260
8159
  function hasSecretExfiltrationShape(command) {
8261
8160
  return /(?:\.env|config\.json|id_rsa|id_ed25519|AWS_SECRET|AICLI_API_KEY|OPENAI_API_KEY|ANTHROPIC_API_KEY)/i.test(command) && /\b(curl|wget|scp|rsync|nc|netcat|Invoke-WebRequest|Invoke-RestMethod)\b/i.test(command);
8262
8161
  }
@@ -8278,7 +8177,7 @@ var RuleBasedActionClassifier = class {
8278
8177
  }
8279
8178
  return { decision: "confirm", reason: "destructive actions require confirmation", ruleId: "auto.destructive-confirm", confidence: "high" };
8280
8179
  }
8281
- classifyShell(call, dangerLevel, ctx) {
8180
+ classifyShell(call, dangerLevel, _ctx) {
8282
8181
  const command = String(call.arguments.command ?? "");
8283
8182
  if (/\bcurl\b[^\n|;&]*\|\s*(?:sudo\s+)?(?:bash|sh|zsh)\b/i.test(command)) {
8284
8183
  return { decision: "deny", reason: "curl pipe shell execution is blocked in auto mode", ruleId: "deny.curl-pipe-shell", confidence: "high" };
@@ -8304,18 +8203,7 @@ var RuleBasedActionClassifier = class {
8304
8203
  if (/\b(npx|pnpm\s+dlx|yarn\s+dlx)\s+(claude|codex|aider|cursor-agent|gemini)\b/i.test(command)) {
8305
8204
  return { decision: "confirm", reason: "third-party agent loop requires confirmation", ruleId: "confirm.third-party-agent", confidence: "high" };
8306
8205
  }
8307
- if (isPlainGitPush(command)) {
8308
- return { decision: "auto-approve", reason: "plain git push without force", ruleId: "auto.git-push", confidence: "high" };
8309
- }
8310
- const pkgs = getInstallPackages(command);
8311
- if (pkgs.length > 0) {
8312
- const allDeclared = pkgs.every((p) => manifestHasDependency(ctx.workspaceRoot, p) || lockfileMentions(ctx.workspaceRoot, p));
8313
- return allDeclared ? { decision: "auto-approve", reason: "installing dependencies already declared in manifest/lockfile", ruleId: "auto.declared-dependency-install", confidence: "medium" } : { decision: "confirm", reason: "dependency install includes undeclared packages", ruleId: "confirm.undeclared-dependency-install", confidence: "medium" };
8314
- }
8315
- if (/\b(curl|wget|Invoke-WebRequest|Invoke-RestMethod)\b/i.test(command) && !/[>|;&`$]/.test(command)) {
8316
- return { decision: "auto-approve", reason: "read-only HTTP shell command", ruleId: "auto.readonly-http-shell", confidence: "medium" };
8317
- }
8318
- if (dangerLevel === "safe") {
8206
+ if (dangerLevel === "safe" && getDangerLevel(call.name, call.arguments) === "safe") {
8319
8207
  return { decision: "auto-approve", reason: "safe shell action", ruleId: "auto.safe-shell", confidence: "medium" };
8320
8208
  }
8321
8209
  return { decision: "confirm", reason: "shell action is not proven low-risk", ruleId: "confirm.shell-default", confidence: "medium" };
@@ -8784,10 +8672,10 @@ var ToolExecutor = class {
8784
8672
  if (call.name === "write_file") {
8785
8673
  const { path: filePath, content: chunk, append } = resolveWriteFileArgs(call.arguments);
8786
8674
  if (!filePath) return;
8787
- if (existsSync11(filePath)) {
8675
+ if (existsSync10(filePath)) {
8788
8676
  let oldContent;
8789
8677
  try {
8790
- oldContent = readFileSync9(filePath, "utf-8");
8678
+ oldContent = readFileSync7(filePath, "utf-8");
8791
8679
  } catch {
8792
8680
  return;
8793
8681
  }
@@ -8811,7 +8699,7 @@ var ToolExecutor = class {
8811
8699
  }
8812
8700
  } else if (call.name === "edit_file") {
8813
8701
  const filePath = String(call.arguments["path"] ?? "");
8814
- if (!filePath || !existsSync11(filePath)) return;
8702
+ if (!filePath || !existsSync10(filePath)) return;
8815
8703
  const oldStr = call.arguments["old_str"];
8816
8704
  const newStr = call.arguments["new_str"];
8817
8705
  if (oldStr !== void 0) {
@@ -8838,7 +8726,7 @@ var ToolExecutor = class {
8838
8726
  const to = Number(call.arguments["delete_to_line"] ?? from);
8839
8727
  let fileContent;
8840
8728
  try {
8841
- fileContent = readFileSync9(filePath, "utf-8");
8729
+ fileContent = readFileSync7(filePath, "utf-8");
8842
8730
  } catch {
8843
8731
  return;
8844
8732
  }
@@ -8934,7 +8822,12 @@ import { resolve as resolve5, sep as sep2, basename as basename3 } from "path";
8934
8822
  import { homedir as homedir4 } from "os";
8935
8823
  var home = homedir4();
8936
8824
  function norm(p) {
8937
- const abs = resolve5(p);
8825
+ let abs;
8826
+ try {
8827
+ abs = canonicalPath(p);
8828
+ } catch {
8829
+ abs = resolve5(p);
8830
+ }
8938
8831
  return process.platform === "win32" ? abs.toLowerCase() : abs;
8939
8832
  }
8940
8833
  function homeRel(p) {
@@ -8948,6 +8841,7 @@ function homeRel(p) {
8948
8841
  }
8949
8842
  function classifyWritePath(path3) {
8950
8843
  if (!path3) return { sensitive: false };
8844
+ if (isCredentialVault(path3)) return { sensitive: true, reason: "credential vault" };
8951
8845
  const abs = norm(path3);
8952
8846
  const base = basename3(abs);
8953
8847
  const rel = homeRel(path3);
@@ -9030,7 +8924,7 @@ function countTextLines(text) {
9030
8924
 
9031
8925
  // src/tools/builtin/write-file.ts
9032
8926
  function countFileLines(filePath) {
9033
- return countTextLines(readFileSync10(filePath, "utf-8"));
8927
+ return countTextLines(readFileSync8(filePath, "utf-8"));
9034
8928
  }
9035
8929
  var writeFileTool = {
9036
8930
  definition: {
@@ -9094,7 +8988,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
9094
8988
  const mode = appendMode ? "appended" : "written";
9095
8989
  void (async () => {
9096
8990
  try {
9097
- const { updateFile } = await import("./indexer-FPKY7LCT.js");
8991
+ const { updateFile } = await import("./indexer-VRXDZ74A.js");
9098
8992
  await updateFile(process.cwd(), filePath);
9099
8993
  } catch {
9100
8994
  }
@@ -9113,7 +9007,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
9113
9007
  };
9114
9008
 
9115
9009
  // src/tools/builtin/edit-file.ts
9116
- import { readFileSync as readFileSync11, existsSync as existsSync12 } from "fs";
9010
+ import { readFileSync as readFileSync9, existsSync as existsSync11 } from "fs";
9117
9011
 
9118
9012
  // src/tools/builtin/patch-apply.ts
9119
9013
  function parseUnifiedDiff(patch) {
@@ -9481,7 +9375,7 @@ Note: Path can be absolute or relative to cwd.`,
9481
9375
  const filePath = String(args["path"] ?? args["file"] ?? "");
9482
9376
  const encoding = args["encoding"] ?? "utf-8";
9483
9377
  if (!filePath) throw new ToolError("edit_file", "path is required");
9484
- if (!existsSync12(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
9378
+ if (!existsSync11(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
9485
9379
  const verdict = classifyWritePath(filePath);
9486
9380
  if (verdict.sensitive && subAgentGuard.active) {
9487
9381
  throw new ToolError(
@@ -9489,7 +9383,7 @@ Note: Path can be absolute or relative to cwd.`,
9489
9383
  `Refused: sub-agents cannot edit sensitive paths \u2014 ${verdict.reason}. If this is genuinely needed, ask the parent agent to perform the edit so the user can approve it.`
9490
9384
  );
9491
9385
  }
9492
- const original = readFileSync11(filePath, encoding);
9386
+ const original = readFileSync9(filePath, encoding);
9493
9387
  if (args["patch"] !== void 0) {
9494
9388
  const patchText = String(args["patch"] ?? "");
9495
9389
  const stopOnError = args["stop_on_error"] !== false;
@@ -9666,8 +9560,8 @@ function truncatePreview(str, maxLen = 80) {
9666
9560
  }
9667
9561
 
9668
9562
  // src/tools/builtin/list-dir.ts
9669
- import { readdirSync as readdirSync4, statSync as statSync5, existsSync as existsSync13 } from "fs";
9670
- import { join as join8, basename as basename4 } from "path";
9563
+ import { readdirSync as readdirSync4, statSync as statSync5, existsSync as existsSync12 } from "fs";
9564
+ import { join as join7, basename as basename4 } from "path";
9671
9565
  var listDirTool = {
9672
9566
  definition: {
9673
9567
  name: "list_dir",
@@ -9689,7 +9583,7 @@ var listDirTool = {
9689
9583
  async execute(args) {
9690
9584
  const dirPath = String(args["path"] ?? process.cwd());
9691
9585
  const recursive = Boolean(args["recursive"] ?? false);
9692
- if (!existsSync13(dirPath)) {
9586
+ if (!existsSync12(dirPath)) {
9693
9587
  const targetName = basename4(dirPath).toLowerCase();
9694
9588
  const cwd = process.cwd();
9695
9589
  const suggestions = [];
@@ -9747,11 +9641,11 @@ function listRecursive(basePath, indent, recursive, lines) {
9747
9641
  if (entry.isDirectory()) {
9748
9642
  lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
9749
9643
  if (recursive) {
9750
- listRecursive(join8(basePath, entry.name), indent + " ", true, lines);
9644
+ listRecursive(join7(basePath, entry.name), indent + " ", true, lines);
9751
9645
  }
9752
9646
  } else {
9753
9647
  try {
9754
- const stat = statSync5(join8(basePath, entry.name));
9648
+ const stat = statSync5(join7(basePath, entry.name));
9755
9649
  const size = formatSize(stat.size);
9756
9650
  lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
9757
9651
  } catch {
@@ -9766,10 +9660,77 @@ function formatSize(bytes) {
9766
9660
  return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
9767
9661
  }
9768
9662
 
9663
+ // src/tools/regex-search.ts
9664
+ import { Worker } from "worker_threads";
9665
+ var source = String.raw`
9666
+ const { parentPort, workerData: d } = require('node:worker_threads');
9667
+ const fs = require('node:fs');
9668
+ let re;
9669
+ try { re = new RegExp(d.pattern, d.ignoreCase ? 'i' : ''); }
9670
+ catch { re = new RegExp(d.pattern.replace(/[.*+?^$()|[\]\\]/g, '\\$&'), d.ignoreCase ? 'i' : ''); }
9671
+ const results = [];
9672
+ for (const f of d.files) {
9673
+ if (results.length >= d.maxResults) break;
9674
+ let fd;
9675
+ try {
9676
+ fd = fs.openSync(f.fullPath, 'r');
9677
+ const buf = Buffer.alloc(1000001);
9678
+ let size = 0;
9679
+ while (size < buf.length) { const n = fs.readSync(fd, buf, size, buf.length - size, null); if (!n) break; size += n; }
9680
+ if (size > 1000000 || buf.subarray(0, Math.min(size,512)).includes(0)) continue;
9681
+ const lines = buf.subarray(0,size).toString('utf8').split('\n');
9682
+ for (let i=0; i<lines.length && results.length<d.maxResults; i++) {
9683
+ if (!re.test(lines[i])) continue;
9684
+ const match = { file:f.relPath.replace(/\\/g,'/'), lineNumber:i+1, lineText:lines[i].slice(0,10000) };
9685
+ if (d.context) {
9686
+ match.contextBefore=[]; match.contextAfter=[];
9687
+ for(let j=Math.max(0,i-d.context);j<i;j++) match.contextBefore.push([j+1,lines[j].slice(0,10000)]);
9688
+ for(let j=i+1;j<=Math.min(lines.length-1,i+d.context);j++) match.contextAfter.push([j+1,lines[j].slice(0,10000)]);
9689
+ }
9690
+ results.push(match);
9691
+ }
9692
+ } catch {} finally { if (fd !== undefined) fs.closeSync(fd); }
9693
+ }
9694
+ parentPort.postMessage(results);
9695
+ `;
9696
+ var active = 0;
9697
+ async function searchInWorker(files, pattern, ignoreCase, context, maxResults) {
9698
+ const signal = currentAbortSignal();
9699
+ if (signal.aborted) throw new Error("Search cancelled");
9700
+ if (active >= 4) throw new Error("Search capacity reached; wait for another search to finish");
9701
+ active++;
9702
+ let worker;
9703
+ try {
9704
+ return await new Promise((resolve13, reject) => {
9705
+ worker = new Worker(source, { eval: true, workerData: { files, pattern, ignoreCase, context, maxResults }, resourceLimits: { maxOldGenerationSizeMb: 64 } });
9706
+ let settled = false;
9707
+ const finish = (error, results) => {
9708
+ if (settled) return;
9709
+ settled = true;
9710
+ clearTimeout(timer);
9711
+ signal.removeEventListener("abort", abort);
9712
+ if (error) reject(error);
9713
+ else resolve13(results ?? []);
9714
+ };
9715
+ const timer = setTimeout(() => finish(new Error("Search exceeded 2 second CPU/time budget; simplify the pattern or narrow the path")), 2e3);
9716
+ const abort = () => finish(new Error("Search cancelled"));
9717
+ signal.addEventListener("abort", abort, { once: true });
9718
+ if (signal.aborted) abort();
9719
+ worker.once("message", (results) => finish(void 0, results));
9720
+ worker.once("error", (error) => finish(error));
9721
+ worker.once("exit", (code) => {
9722
+ if (!settled) finish(new Error(`Search worker exited (${code})`));
9723
+ });
9724
+ });
9725
+ } finally {
9726
+ if (worker) await worker.terminate();
9727
+ active--;
9728
+ }
9729
+ }
9730
+
9769
9731
  // src/tools/builtin/grep-files.ts
9770
- import { readdirSync as readdirSync5, readFileSync as readFileSync12, statSync as statSync6, existsSync as existsSync14 } from "fs";
9771
- import { readFile } from "fs/promises";
9772
- import { join as join9, relative } from "path";
9732
+ import { readdirSync as readdirSync5, statSync as statSync6, existsSync as existsSync13 } from "fs";
9733
+ import { join as join8, relative } from "path";
9773
9734
  var grepFilesTool = {
9774
9735
  definition: {
9775
9736
  name: "grep_files",
@@ -9817,43 +9778,24 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
9817
9778
  const rootPath = String(args["path"] ?? process.cwd());
9818
9779
  const filePattern = args["file_pattern"] ? String(args["file_pattern"]) : void 0;
9819
9780
  const ignoreCase = Boolean(args["ignore_case"] ?? false);
9820
- const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
9821
- const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
9781
+ const contextLines = Math.min(10, Math.max(0, Number(args["context_lines"]) || 0));
9782
+ const maxResults = Math.min(200, Math.max(1, Number(args["max_results"]) || 50));
9822
9783
  if (!pattern) throw new ToolError("grep_files", "pattern is required");
9823
- if (!existsSync14(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
9784
+ if (!existsSync13(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
9824
9785
  const MAX_PATTERN_LENGTH = 1e3;
9825
- if (pattern.length > MAX_PATTERN_LENGTH) {
9786
+ if (pattern.length > MAX_PATTERN_LENGTH || (filePattern?.length ?? 0) > MAX_PATTERN_LENGTH) {
9826
9787
  throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
9827
9788
  }
9828
- let regex;
9829
- try {
9830
- regex = new RegExp(pattern, ignoreCase ? "gi" : "g");
9831
- } catch {
9832
- const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9833
- regex = new RegExp(escaped, ignoreCase ? "gi" : "g");
9834
- }
9835
- const results = [];
9836
- const stat = statSync6(rootPath);
9837
- if (stat.isFile()) {
9838
- searchInFile(rootPath, rootPath, regex, contextLines, maxResults, results);
9839
- } else {
9840
- const filePaths = collectFilePaths(rootPath, filePattern, maxResults);
9841
- const BATCH_SIZE = 16;
9842
- for (let i = 0; i < filePaths.length && results.length < maxResults; i += BATCH_SIZE) {
9843
- const batch = filePaths.slice(i, i + BATCH_SIZE);
9844
- const batchResults = await Promise.all(
9845
- batch.map(
9846
- ({ fullPath, relPath }) => searchInFileAsync(fullPath, relPath, regex, contextLines, maxResults - results.length)
9847
- )
9848
- );
9849
- for (const br of batchResults) {
9850
- for (const r of br) {
9851
- if (results.length >= maxResults) break;
9852
- results.push(r);
9853
- }
9854
- }
9789
+ assertReadablePath(rootPath);
9790
+ const paths = statSync6(rootPath).isFile() ? [{ fullPath: rootPath, relPath: rootPath }] : collectFilePaths(rootPath, filePattern, maxResults);
9791
+ const files = paths.filter((f) => {
9792
+ try {
9793
+ return !isCredentialVault(f.fullPath);
9794
+ } catch {
9795
+ return false;
9855
9796
  }
9856
- }
9797
+ });
9798
+ const results = await searchInWorker(files, pattern, ignoreCase, contextLines, maxResults);
9857
9799
  if (results.length === 0) {
9858
9800
  return `No matches found for pattern: ${pattern}
9859
9801
  Searched in: ${rootPath}${filePattern ? `
@@ -9893,8 +9835,23 @@ File filter: ${filePattern}` : ""}`;
9893
9835
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", ".next", "__pycache__", ".cache", "coverage", ".nyc_output"]);
9894
9836
  var BINARY_EXTS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".ico", ".woff", ".woff2", ".ttf", ".eot", ".otf", ".mp3", ".mp4", ".wav", ".zip", ".tar", ".gz", ".rar", ".7z", ".exe", ".dll", ".so", ".dylib", ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx"]);
9895
9837
  function matchesFilePattern(filename, pattern) {
9896
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
9897
- return new RegExp(`^${escaped}$`, "i").test(filename);
9838
+ filename = filename.toLowerCase();
9839
+ pattern = pattern.toLowerCase();
9840
+ let i = 0, j = 0, star = -1, restart = 0;
9841
+ while (i < filename.length) {
9842
+ if (pattern[j] === filename[i]) {
9843
+ i++;
9844
+ j++;
9845
+ } else if (pattern[j] === "*") {
9846
+ star = j++;
9847
+ restart = i;
9848
+ } else if (star >= 0) {
9849
+ j = star + 1;
9850
+ i = ++restart;
9851
+ } else return false;
9852
+ }
9853
+ while (pattern[j] === "*") j++;
9854
+ return j === pattern.length;
9898
9855
  }
9899
9856
  function isBinary(filename) {
9900
9857
  const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
@@ -9902,8 +9859,9 @@ function isBinary(filename) {
9902
9859
  }
9903
9860
  function collectFilePaths(rootPath, filePattern, maxFiles) {
9904
9861
  const paths = [];
9905
- function walk(dirPath) {
9906
- if (paths.length >= maxFiles) return;
9862
+ let visited = 0;
9863
+ function walk(dirPath, depth = 0) {
9864
+ if (paths.length >= maxFiles || depth > 20 || visited >= 2e4) return;
9907
9865
  let entries;
9908
9866
  try {
9909
9867
  entries = readdirSync5(dirPath, { withFileTypes: true });
@@ -9911,14 +9869,14 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
9911
9869
  return;
9912
9870
  }
9913
9871
  for (const entry of entries) {
9914
- if (paths.length >= maxFiles) return;
9872
+ if (paths.length >= maxFiles || ++visited > 2e4) return;
9915
9873
  if (entry.isDirectory()) {
9916
9874
  if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
9917
- walk(join9(dirPath, entry.name));
9875
+ walk(join8(dirPath, entry.name), depth + 1);
9918
9876
  } else if (entry.isFile()) {
9919
9877
  if (isBinary(entry.name)) continue;
9920
9878
  if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
9921
- const fullPath = join9(dirPath, entry.name);
9879
+ const fullPath = join8(dirPath, entry.name);
9922
9880
  try {
9923
9881
  if (statSync6(fullPath).size > 1e6) continue;
9924
9882
  } catch {
@@ -9931,81 +9889,10 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
9931
9889
  walk(rootPath);
9932
9890
  return paths;
9933
9891
  }
9934
- async function searchInFileAsync(fullPath, displayPath2, regex, contextLines, maxResults) {
9935
- let content;
9936
- try {
9937
- content = await readFile(fullPath, "utf-8");
9938
- } catch {
9939
- return [];
9940
- }
9941
- const results = [];
9942
- const lines = content.split("\n");
9943
- regex.lastIndex = 0;
9944
- for (let i = 0; i < lines.length && results.length < maxResults; i++) {
9945
- regex.lastIndex = 0;
9946
- if (regex.test(lines[i])) {
9947
- const match = {
9948
- file: displayPath2.replace(/\\/g, "/"),
9949
- lineNumber: i + 1,
9950
- lineText: lines[i]
9951
- };
9952
- if (contextLines > 0) {
9953
- match.contextBefore = [];
9954
- for (let j = Math.max(0, i - contextLines); j < i; j++) {
9955
- match.contextBefore.push([j + 1, lines[j]]);
9956
- }
9957
- match.contextAfter = [];
9958
- for (let j = i + 1; j <= Math.min(lines.length - 1, i + contextLines); j++) {
9959
- match.contextAfter.push([j + 1, lines[j]]);
9960
- }
9961
- }
9962
- results.push(match);
9963
- }
9964
- }
9965
- return results;
9966
- }
9967
- function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, results) {
9968
- try {
9969
- const stat = statSync6(fullPath);
9970
- if (stat.size > 1e6) return;
9971
- } catch {
9972
- return;
9973
- }
9974
- let content;
9975
- try {
9976
- content = readFileSync12(fullPath, "utf-8");
9977
- } catch {
9978
- return;
9979
- }
9980
- const lines = content.split("\n");
9981
- regex.lastIndex = 0;
9982
- for (let i = 0; i < lines.length; i++) {
9983
- if (results.length >= maxResults) return;
9984
- regex.lastIndex = 0;
9985
- if (regex.test(lines[i])) {
9986
- const result = {
9987
- file: displayPath2,
9988
- lineNumber: i + 1,
9989
- lineText: lines[i].trimEnd()
9990
- };
9991
- if (contextLines > 0) {
9992
- result.contextBefore = [];
9993
- result.contextAfter = [];
9994
- for (let c = Math.max(0, i - contextLines); c < i; c++) {
9995
- result.contextBefore.push([c + 1, lines[c].trimEnd()]);
9996
- }
9997
- for (let c = i + 1; c <= Math.min(lines.length - 1, i + contextLines); c++) {
9998
- result.contextAfter.push([c + 1, lines[c].trimEnd()]);
9999
- }
10000
- }
10001
- results.push(result);
10002
- }
10003
- }
10004
- }
10005
9892
 
10006
9893
  // src/tools/builtin/glob-files.ts
10007
- import { readdirSync as readdirSync6, statSync as statSync7, existsSync as existsSync15 } from "fs";
10008
- import { join as join10, relative as relative2, basename as basename5 } from "path";
9894
+ import { readdirSync as readdirSync6, statSync as statSync7, existsSync as existsSync14 } from "fs";
9895
+ import { join as join9, relative as relative2, basename as basename5 } from "path";
10009
9896
  var globFilesTool = {
10010
9897
  definition: {
10011
9898
  name: "glob_files",
@@ -10038,7 +9925,7 @@ Results sorted by most recent modification time. Automatically skips node_module
10038
9925
  const rootPath = String(args["path"] ?? process.cwd());
10039
9926
  const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
10040
9927
  if (!pattern) throw new ToolError("glob_files", "pattern is required");
10041
- if (!existsSync15(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
9928
+ if (!existsSync14(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
10042
9929
  const regex = globToRegex(pattern);
10043
9930
  const matches2 = [];
10044
9931
  collectMatchingFiles(rootPath, rootPath, regex, matches2, maxResults);
@@ -10115,7 +10002,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
10115
10002
  }
10116
10003
  for (const entry of entries) {
10117
10004
  if (results.length >= maxResults) break;
10118
- const fullPath = join10(dirPath, entry.name);
10005
+ const fullPath = join9(dirPath, entry.name);
10119
10006
  if (entry.isDirectory()) {
10120
10007
  if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
10121
10008
  collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
@@ -10134,9 +10021,9 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
10134
10021
  }
10135
10022
 
10136
10023
  // src/tools/builtin/run-interactive.ts
10137
- import { spawn as spawn4 } from "child_process";
10138
- import { platform as platform4 } from "os";
10139
- var IS_WINDOWS4 = platform4() === "win32";
10024
+ import { spawn as spawn3 } from "child_process";
10025
+ import { platform as platform3 } from "os";
10026
+ var IS_WINDOWS4 = platform3() === "win32";
10140
10027
  var runInteractiveTool = {
10141
10028
  definition: {
10142
10029
  name: "run_interactive",
@@ -10198,7 +10085,7 @@ var runInteractiveTool = {
10198
10085
  };
10199
10086
  const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
10200
10087
  return new Promise((resolve13) => {
10201
- const child = spawn4(executable, cmdArgs.map(String), {
10088
+ const child = spawn3(executable, cmdArgs.map(String), {
10202
10089
  cwd: process.cwd(),
10203
10090
  env,
10204
10091
  stdio: ["pipe", "pipe", "pipe"]
@@ -10468,8 +10355,8 @@ var webFetchTool = {
10468
10355
  if (e.message.startsWith("Blocked:")) throw e;
10469
10356
  throw new NetworkError(`Invalid URL: "${url}"`);
10470
10357
  }
10471
- const controller2 = new AbortController();
10472
- const timeoutId = setTimeout(() => controller2.abort(), 2e4);
10358
+ const controller = new AbortController();
10359
+ const timeoutId = setTimeout(() => controller.abort(), 2e4);
10473
10360
  let rawHtml;
10474
10361
  let finalUrl;
10475
10362
  let contentType;
@@ -10490,7 +10377,7 @@ var webFetchTool = {
10490
10377
  }
10491
10378
  await resolveAndCheck(parsedHop.hostname);
10492
10379
  const r = await fetch(currentUrl, {
10493
- signal: controller2.signal,
10380
+ signal: controller.signal,
10494
10381
  headers: FETCH_HEADERS,
10495
10382
  redirect: "manual",
10496
10383
  // 手动控制重定向
@@ -10853,8 +10740,8 @@ function parseGoogleResults(html, maxResults) {
10853
10740
  return results;
10854
10741
  }
10855
10742
  async function fetchHtml(url) {
10856
- const controller2 = new AbortController();
10857
- const timer = setTimeout(() => controller2.abort(), REQUEST_TIMEOUT_MS);
10743
+ const controller = new AbortController();
10744
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
10858
10745
  try {
10859
10746
  const res = await fetch(url, {
10860
10747
  method: "GET",
@@ -10863,7 +10750,7 @@ async function fetchHtml(url) {
10863
10750
  "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9",
10864
10751
  "Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8"
10865
10752
  },
10866
- signal: controller2.signal
10753
+ signal: controller.signal
10867
10754
  });
10868
10755
  if (!res.ok) {
10869
10756
  throw new NetworkError(`HTTP ${res.status} ${res.statusText}`, res.status);
@@ -10957,10 +10844,8 @@ Any of these triggers means use save_last_response, NOT write_file:
10957
10844
  };
10958
10845
 
10959
10846
  // src/tools/builtin/save-memory.ts
10960
- import { join as join11 } from "path";
10961
- import { homedir as homedir5 } from "os";
10962
10847
  function getConfigDir() {
10963
- return join11(homedir5(), CONFIG_DIR_NAME);
10848
+ return getExecutionConfigDir();
10964
10849
  }
10965
10850
  function parseSensitivity(value) {
10966
10851
  return value === "low" || value === "medium" || value === "high" ? value : void 0;
@@ -11224,15 +11109,15 @@ var googleSearchTool = {
11224
11109
  url.searchParams.set("cx", cx);
11225
11110
  url.searchParams.set("q", query);
11226
11111
  url.searchParams.set("num", String(numResults));
11227
- const controller2 = new AbortController();
11228
- const timeout = setTimeout(() => controller2.abort(), REQUEST_TIMEOUT_MS2);
11112
+ const controller = new AbortController();
11113
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
11229
11114
  try {
11230
11115
  const response = await fetch(url.toString(), {
11231
11116
  method: "GET",
11232
11117
  headers: {
11233
11118
  "Accept": "application/json"
11234
11119
  },
11235
- signal: controller2.signal
11120
+ signal: controller.signal
11236
11121
  });
11237
11122
  if (!response.ok) {
11238
11123
  const errorBody = await response.text().catch(() => "");
@@ -11316,13 +11201,13 @@ function isToolCapableProvider(provider) {
11316
11201
  }
11317
11202
 
11318
11203
  // src/agents/agent-config.ts
11319
- import { existsSync as existsSync17, readdirSync as readdirSync8, readFileSync as readFileSync14 } from "fs";
11320
- import { join as join13 } from "path";
11321
- import { homedir as homedir6 } from "os";
11204
+ import { existsSync as existsSync16, readdirSync as readdirSync8, readFileSync as readFileSync11 } from "fs";
11205
+ import { join as join11 } from "path";
11206
+ import { homedir as homedir5 } from "os";
11322
11207
 
11323
11208
  // src/plugins/plugin-manager.ts
11324
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync13, realpathSync as realpathSync2, cpSync, rmSync, statSync as statSync8 } from "fs";
11325
- import { basename as basename6, dirname as dirname7, join as join12, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
11209
+ import { existsSync as existsSync15, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync10, realpathSync as realpathSync2, cpSync, rmSync, statSync as statSync8 } from "fs";
11210
+ import { basename as basename6, dirname as dirname7, join as join10, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
11326
11211
  import { createHash as createHash2 } from "crypto";
11327
11212
  import { z as z2 } from "zod";
11328
11213
  var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
@@ -11382,19 +11267,19 @@ var PluginManifestSchema = z2.object({
11382
11267
  permissionHints: z2.array(z2.string()).default([])
11383
11268
  }).strict();
11384
11269
  function pluginRoot(configDir) {
11385
- return join12(configDir, PLUGINS_DIR_NAME);
11270
+ return join10(configDir, PLUGINS_DIR_NAME);
11386
11271
  }
11387
11272
  function pluginManifestPath(pluginDir) {
11388
- return join12(pluginDir, MANIFEST_RELATIVE);
11273
+ return join10(pluginDir, MANIFEST_RELATIVE);
11389
11274
  }
11390
11275
  function statePath(configDir) {
11391
- return join12(pluginRoot(configDir), STATE_FILE);
11276
+ return join10(pluginRoot(configDir), STATE_FILE);
11392
11277
  }
11393
11278
  function loadState(configDir) {
11394
11279
  const file = statePath(configDir);
11395
- if (!existsSync16(file)) return { version: 2, plugins: [] };
11280
+ if (!existsSync15(file)) return { version: 2, plugins: [] };
11396
11281
  try {
11397
- const parsed = JSON.parse(readFileSync13(file, "utf-8"));
11282
+ const parsed = JSON.parse(readFileSync10(file, "utf-8"));
11398
11283
  const rawPlugins = Array.isArray(parsed.plugins) ? parsed.plugins : [];
11399
11284
  return { version: 2, plugins: rawPlugins.map((p) => migrateEntry(p)) };
11400
11285
  } catch {
@@ -11452,7 +11337,7 @@ function extractCommandPaths(command, pluginDir) {
11452
11337
  try {
11453
11338
  const abs = resolve6(resolvedDir, ref);
11454
11339
  if (!abs.startsWith(resolvedDir + sep3)) continue;
11455
- if (existsSync16(abs)) {
11340
+ if (existsSync15(abs)) {
11456
11341
  const real = realpathSync2(abs);
11457
11342
  if (!real.startsWith(resolvedDir + sep3)) continue;
11458
11343
  valid.push(relative3(resolvedDir, real));
@@ -11484,8 +11369,8 @@ function hashPluginContent(pluginDir, manifest) {
11484
11369
  }
11485
11370
  const sorted = [...assetRels].sort();
11486
11371
  const hasher = createHash2("sha256");
11487
- const manifestPath = join12(resolvedDir, MANIFEST_RELATIVE);
11488
- hasher.update(normalizeForHash(readFileSync13(manifestPath)));
11372
+ const manifestPath = join10(resolvedDir, MANIFEST_RELATIVE);
11373
+ hasher.update(normalizeForHash(readFileSync10(manifestPath)));
11489
11374
  for (const rel of sorted) {
11490
11375
  const abs = resolve6(resolvedDir, rel);
11491
11376
  if (!abs.startsWith(resolvedDir + sep3)) {
@@ -11505,7 +11390,7 @@ function hashPluginContent(pluginDir, manifest) {
11505
11390
  if (!realPath.startsWith(resolvedDir + sep3)) {
11506
11391
  throw new Error(`plugin asset symlink escapes plugin dir: ${rel} \u2192 ${realPath}`);
11507
11392
  }
11508
- hasher.update(normalizeForHash(readFileSync13(abs)));
11393
+ hasher.update(normalizeForHash(readFileSync10(abs)));
11509
11394
  }
11510
11395
  return hasher.digest("hex");
11511
11396
  }
@@ -11513,22 +11398,22 @@ function effectiveHash(entry) {
11513
11398
  return entry.contentHash ?? entry.hash ?? "";
11514
11399
  }
11515
11400
  function readPluginManifest(manifestPath) {
11516
- return PluginManifestSchema.parse(JSON.parse(readFileSync13(manifestPath, "utf-8")));
11401
+ return PluginManifestSchema.parse(JSON.parse(readFileSync10(manifestPath, "utf-8")));
11517
11402
  }
11518
11403
  function findPluginSourceDir(inputPath) {
11519
11404
  const abs = resolve6(inputPath);
11520
11405
  const direct = statSync8(abs).isDirectory() ? abs : dirname7(abs);
11521
- if (existsSync16(pluginManifestPath(direct))) return direct;
11522
- const nested = join12(direct, ".aicli-plugin");
11523
- if (existsSync16(join12(nested, "plugin.json"))) return direct;
11406
+ if (existsSync15(pluginManifestPath(direct))) return direct;
11407
+ const nested = join10(direct, ".aicli-plugin");
11408
+ if (existsSync15(join10(nested, "plugin.json"))) return direct;
11524
11409
  throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
11525
11410
  }
11526
11411
  function installPlugin(configDir, inputPath) {
11527
11412
  const sourceDir = findPluginSourceDir(inputPath);
11528
11413
  const manifest = readPluginManifest(pluginManifestPath(sourceDir));
11529
- const targetDir = join12(pluginRoot(configDir), manifest.name);
11414
+ const targetDir = join10(pluginRoot(configDir), manifest.name);
11530
11415
  mkdirSync8(pluginRoot(configDir), { recursive: true });
11531
- if (existsSync16(targetDir)) rmSync(targetDir, { recursive: true, force: true });
11416
+ if (existsSync15(targetDir)) rmSync(targetDir, { recursive: true, force: true });
11532
11417
  cpSync(sourceDir, targetDir, { recursive: true });
11533
11418
  const manifestPath = pluginManifestPath(targetDir);
11534
11419
  const contentHash = hashPluginContent(targetDir, manifest);
@@ -11586,18 +11471,18 @@ function getInstalledPlugin(configDir, name) {
11586
11471
  }
11587
11472
  function listInstalledPlugins(configDir) {
11588
11473
  const root = pluginRoot(configDir);
11589
- if (!existsSync16(root)) return [];
11474
+ if (!existsSync15(root)) return [];
11590
11475
  const state2 = loadState(configDir);
11591
11476
  const out = [];
11592
11477
  for (const entry of readdirSync7(root)) {
11593
- const dir = join12(root, entry);
11478
+ const dir = join10(root, entry);
11594
11479
  try {
11595
11480
  if (!statSync8(dir).isDirectory()) continue;
11596
11481
  } catch {
11597
11482
  continue;
11598
11483
  }
11599
11484
  const manifestPath = pluginManifestPath(dir);
11600
- if (!existsSync16(manifestPath)) continue;
11485
+ if (!existsSync15(manifestPath)) continue;
11601
11486
  try {
11602
11487
  const manifest = readPluginManifest(manifestPath);
11603
11488
  const contentHash = hashPluginContent(dir, manifest);
@@ -11637,7 +11522,7 @@ function uniqueDirs(plugin, rels) {
11637
11522
  for (const rel of rels) {
11638
11523
  const abs = resolve6(plugin.dir, rel);
11639
11524
  if (!abs.startsWith(resolve6(plugin.dir))) continue;
11640
- if (existsSync16(abs)) dirs.add(statSync8(abs).isDirectory() ? abs : dirname7(abs));
11525
+ if (existsSync15(abs)) dirs.add(statSync8(abs).isDirectory() ? abs : dirname7(abs));
11641
11526
  }
11642
11527
  return [...dirs];
11643
11528
  }
@@ -11777,13 +11662,13 @@ function normalizeName(name) {
11777
11662
  function isStringArray(value) {
11778
11663
  return Array.isArray(value) && value.every((item) => typeof item === "string");
11779
11664
  }
11780
- function parseAgentConfig(raw, source, path3) {
11665
+ function parseAgentConfig(raw, source2, path3) {
11781
11666
  if (!raw || typeof raw !== "object") return null;
11782
11667
  const obj = raw;
11783
11668
  if (typeof obj.name !== "string" || obj.name.trim() === "") return null;
11784
11669
  const cfg = {
11785
11670
  name: obj.name.trim(),
11786
- source,
11671
+ source: source2,
11787
11672
  path: path3
11788
11673
  };
11789
11674
  if (typeof obj.description === "string") cfg.description = obj.description;
@@ -11799,15 +11684,15 @@ function parseAgentConfig(raw, source, path3) {
11799
11684
  if (typeof obj.contextPolicy === "string") cfg.contextPolicy = obj.contextPolicy;
11800
11685
  return cfg;
11801
11686
  }
11802
- function loadAgentDir(dir, source) {
11803
- if (!existsSync17(dir)) return [];
11687
+ function loadAgentDir(dir, source2) {
11688
+ if (!existsSync16(dir)) return [];
11804
11689
  const out = [];
11805
11690
  for (const file of readdirSync8(dir)) {
11806
11691
  if (!file.endsWith(".json")) continue;
11807
- const path3 = join13(dir, file);
11692
+ const path3 = join11(dir, file);
11808
11693
  try {
11809
- const parsed = JSON.parse(readFileSync14(path3, "utf-8"));
11810
- const cfg = parseAgentConfig(parsed, source, path3);
11694
+ const parsed = JSON.parse(readFileSync11(path3, "utf-8"));
11695
+ const cfg = parseAgentConfig(parsed, source2, path3);
11811
11696
  if (cfg) out.push(cfg);
11812
11697
  } catch {
11813
11698
  }
@@ -11816,8 +11701,8 @@ function loadAgentDir(dir, source) {
11816
11701
  }
11817
11702
  function getAgentDirs(configDir, cwd = process.cwd()) {
11818
11703
  return {
11819
- user: join13(configDir ?? join13(homedir6(), CONFIG_DIR_NAME), "agents"),
11820
- project: join13(cwd, CONFIG_DIR_NAME, "agents")
11704
+ user: join11(configDir ?? join11(homedir5(), CONFIG_DIR_NAME), "agents"),
11705
+ project: join11(cwd, CONFIG_DIR_NAME, "agents")
11821
11706
  };
11822
11707
  }
11823
11708
  function listAgentConfigs(configDir, cwd = process.cwd()) {
@@ -12320,9 +12205,8 @@ var spawnAgentTool = {
12320
12205
  };
12321
12206
 
12322
12207
  // src/tools/builtin/task-manager.ts
12323
- import { spawn as spawn5 } from "child_process";
12208
+ import { spawn as spawn4 } from "child_process";
12324
12209
  import { randomUUID } from "crypto";
12325
- import { platform as platform5 } from "os";
12326
12210
  var MAX_OUTPUT_CHARS = 1e4;
12327
12211
  var MAX_OUTPUT_BYTES = 64 * 1024;
12328
12212
  var MAX_TASKS = 20;
@@ -12341,14 +12225,16 @@ function decodeField(chunks) {
12341
12225
  }
12342
12226
  function appendOutput(task, field, chunk) {
12343
12227
  const chunks = field === "stdout" ? task._stdoutChunks : task._stderrChunks;
12344
- chunks.push(chunk);
12228
+ chunks.push(chunk.length > MAX_OUTPUT_BYTES ? chunk.subarray(-MAX_OUTPUT_BYTES) : chunk);
12345
12229
  capChunks(chunks);
12346
12230
  task[field] = decodeField(chunks);
12347
12231
  }
12348
12232
  function createTask(command, description) {
12233
+ if ([...tasks.values()].filter((t) => t._active && t.owner === getExecutionConfigDir()).length >= 10) throw new Error("Background task limit reached (10 running per user).");
12234
+ if ([...tasks.values()].filter((t) => t._active).length >= MAX_TASKS) throw new Error("Background task limit reached (20 running). Stop a task before creating another.");
12349
12235
  if (tasks.size >= MAX_TASKS) {
12350
12236
  for (const [id2, t] of tasks) {
12351
- if (t.status !== "running") {
12237
+ if (!t._active) {
12352
12238
  tasks.delete(id2);
12353
12239
  break;
12354
12240
  }
@@ -12356,16 +12242,19 @@ function createTask(command, description) {
12356
12242
  }
12357
12243
  const id = randomUUID().slice(0, 8);
12358
12244
  const { shell, args } = shellInvocation(command);
12359
- const proc = spawn5(shell, args, {
12245
+ const proc = spawn4(shell, args, {
12360
12246
  stdio: ["ignore", "pipe", "pipe"],
12361
- detached: false,
12247
+ detached: process.platform !== "win32",
12248
+ windowsHide: true,
12362
12249
  env: { ...process.env, PYTHONUTF8: "1", PYTHONIOENCODING: "utf-8" }
12363
12250
  });
12364
12251
  const task = {
12365
12252
  id,
12253
+ owner: getExecutionConfigDir(),
12366
12254
  command,
12367
12255
  description,
12368
12256
  process: proc,
12257
+ _active: true,
12369
12258
  startTime: Date.now(),
12370
12259
  endTime: null,
12371
12260
  status: "running",
@@ -12378,11 +12267,13 @@ function createTask(command, description) {
12378
12267
  proc.stdout?.on("data", (chunk) => appendOutput(task, "stdout", chunk));
12379
12268
  proc.stderr?.on("data", (chunk) => appendOutput(task, "stderr", chunk));
12380
12269
  proc.on("close", (code) => {
12270
+ task._active = false;
12381
12271
  task.endTime = Date.now();
12382
12272
  task.exitCode = code;
12383
- task.status = code === 0 ? "completed" : "failed";
12273
+ if (task.status !== "stopped") task.status = code === 0 ? "completed" : "failed";
12384
12274
  });
12385
12275
  proc.on("error", (err) => {
12276
+ task._active = false;
12386
12277
  task.endTime = Date.now();
12387
12278
  task.status = "failed";
12388
12279
  task.stderr += `
@@ -12392,43 +12283,20 @@ Process error: ${err.message}`;
12392
12283
  return task;
12393
12284
  }
12394
12285
  function listTasks() {
12395
- return [...tasks.values()].map(
12396
- ({ process: _p, _stdoutChunks: _o, _stderrChunks: _e, ...rest }) => rest
12286
+ return [...tasks.values()].filter((t) => t.owner === getExecutionConfigDir()).map(
12287
+ ({ process: _p, _stdoutChunks: _o, _stderrChunks: _e, _active: _a, ...rest }) => rest
12397
12288
  );
12398
12289
  }
12399
12290
  function getTaskOutput(id) {
12400
12291
  const task = tasks.get(id);
12401
- if (!task) return null;
12292
+ if (!task || task.owner !== getExecutionConfigDir()) return null;
12402
12293
  return { stdout: task.stdout, stderr: task.stderr, status: task.status };
12403
12294
  }
12404
12295
  function stopTask(id) {
12405
12296
  const task = tasks.get(id);
12406
- if (!task || task.status !== "running") return false;
12297
+ if (!task || task.owner !== getExecutionConfigDir() || task.status !== "running") return false;
12407
12298
  try {
12408
- const proc = task.process;
12409
- if (platform5() === "win32" && proc.pid) {
12410
- try {
12411
- spawn5("taskkill", ["/PID", String(proc.pid), "/T", "/F"], {
12412
- windowsHide: true,
12413
- stdio: "ignore"
12414
- });
12415
- } catch {
12416
- }
12417
- try {
12418
- proc.kill();
12419
- } catch {
12420
- }
12421
- } else {
12422
- proc.kill("SIGTERM");
12423
- setTimeout(() => {
12424
- if (proc.exitCode === null && proc.signalCode === null) {
12425
- try {
12426
- proc.kill("SIGKILL");
12427
- } catch {
12428
- }
12429
- }
12430
- }, 3e3).unref();
12431
- }
12299
+ killProcessTree(task.process, true);
12432
12300
  task.status = "stopped";
12433
12301
  task.endTime = Date.now();
12434
12302
  return true;
@@ -12536,14 +12404,14 @@ var taskStopTool = {
12536
12404
 
12537
12405
  // src/tools/builtin/git-tools.ts
12538
12406
  import { execFileSync as execFileSync2 } from "child_process";
12539
- import { existsSync as existsSync18 } from "fs";
12540
- import { join as join14 } from "path";
12407
+ import { existsSync as existsSync17 } from "fs";
12408
+ import { join as join12 } from "path";
12541
12409
  function assertGitRepo(cwd) {
12542
12410
  let dir = cwd;
12543
12411
  const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
12544
12412
  while (dir && dir !== root) {
12545
- if (existsSync18(join14(dir, ".git"))) return;
12546
- const parent = join14(dir, "..");
12413
+ if (existsSync17(join12(dir, ".git"))) return;
12414
+ const parent = join12(dir, "..");
12547
12415
  if (parent === dir) break;
12548
12416
  dir = parent;
12549
12417
  }
@@ -12808,7 +12676,7 @@ ${commitOutput.trim()}`;
12808
12676
  };
12809
12677
 
12810
12678
  // src/tools/builtin/notebook-edit.ts
12811
- import { readFileSync as readFileSync15, existsSync as existsSync19 } from "fs";
12679
+ import { readFileSync as readFileSync12, existsSync as existsSync18 } from "fs";
12812
12680
  import { writeFile } from "fs/promises";
12813
12681
  import { resolve as resolve7, extname as extname2 } from "path";
12814
12682
  var notebookEditTool = {
@@ -12863,10 +12731,10 @@ var notebookEditTool = {
12863
12731
  if (extname2(absPath).toLowerCase() !== ".ipynb") {
12864
12732
  throw new ToolError("notebook_edit", "path must point to a .ipynb file");
12865
12733
  }
12866
- if (!existsSync19(absPath)) {
12734
+ if (!existsSync18(absPath)) {
12867
12735
  throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
12868
12736
  }
12869
- const raw = readFileSync15(absPath, "utf-8");
12737
+ const raw = readFileSync12(absPath, "utf-8");
12870
12738
  const nb = parseNotebook(raw);
12871
12739
  const cellIdx0 = cellIndexRaw - 1;
12872
12740
  undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
@@ -13192,8 +13060,6 @@ ${lines.join("\n")}`;
13192
13060
  };
13193
13061
 
13194
13062
  // src/tools/builtin/recall-memory.ts
13195
- import { join as join15 } from "path";
13196
- import { homedir as homedir7 } from "os";
13197
13063
  function formatHit(h, i) {
13198
13064
  const ts = h.chunk.timestamp.slice(0, 16).replace("T", " ");
13199
13065
  const title = h.chunk.sessionTitle ? ` \xB7 ${h.chunk.sessionTitle}` : "";
@@ -13243,7 +13109,7 @@ var recallMemoryTool = {
13243
13109
  const excludeCurrent = Boolean(args["excludeCurrentSession"]);
13244
13110
  const currentId = args["currentSessionId"] ? String(args["currentSessionId"]) : void 0;
13245
13111
  const minScore = args["minScore"] !== void 0 ? Number(args["minScore"]) : 0.25;
13246
- const configDir = join15(homedir7(), CONFIG_DIR_NAME);
13112
+ const configDir = getExecutionConfigDir();
13247
13113
  let persistentSection = "";
13248
13114
  try {
13249
13115
  const persistentHits = searchPersistentMemories(configDir, query, process.cwd(), 3);
@@ -13297,8 +13163,8 @@ function estimateToolDefinitionTokens(def) {
13297
13163
 
13298
13164
  // src/tools/registry.ts
13299
13165
  import { pathToFileURL } from "url";
13300
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
13301
- import { join as join16 } from "path";
13166
+ import { existsSync as existsSync19, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
13167
+ import { join as join13 } from "path";
13302
13168
  var ToolRegistry = class {
13303
13169
  tools = /* @__PURE__ */ new Map();
13304
13170
  pluginToolNames = /* @__PURE__ */ new Set();
@@ -13459,7 +13325,7 @@ var ToolRegistry = class {
13459
13325
  * Returns the number of successfully loaded plugins.
13460
13326
  */
13461
13327
  async loadPlugins(pluginsDir, allowPlugins = false) {
13462
- if (!existsSync20(pluginsDir)) {
13328
+ if (!existsSync19(pluginsDir)) {
13463
13329
  try {
13464
13330
  mkdirSync9(pluginsDir, { recursive: true });
13465
13331
  } catch {
@@ -13485,12 +13351,12 @@ var ToolRegistry = class {
13485
13351
  process.stderr.write(
13486
13352
  `
13487
13353
  [plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
13488
- ` + files.map((f) => ` + ${join16(pluginsDir, f)}`).join("\n") + "\n\n"
13354
+ ` + files.map((f) => ` + ${join13(pluginsDir, f)}`).join("\n") + "\n\n"
13489
13355
  );
13490
13356
  let loaded = 0;
13491
13357
  for (const file of files) {
13492
13358
  try {
13493
- const fileUrl = pathToFileURL(join16(pluginsDir, file)).href;
13359
+ const fileUrl = pathToFileURL(join13(pluginsDir, file)).href;
13494
13360
  const mod = await import(fileUrl);
13495
13361
  const tool = mod.tool ?? mod.default?.tool ?? mod.default;
13496
13362
  if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
@@ -13518,7 +13384,7 @@ var ToolRegistry = class {
13518
13384
  };
13519
13385
 
13520
13386
  // src/mcp/client.ts
13521
- import { spawn as spawn6 } from "child_process";
13387
+ import { spawn as spawn5 } from "child_process";
13522
13388
  var McpClient = class {
13523
13389
  serverId;
13524
13390
  config;
@@ -13556,7 +13422,7 @@ var McpClient = class {
13556
13422
  async connect() {
13557
13423
  const timeout = this.config.timeout ?? MCP_CONNECT_TIMEOUT;
13558
13424
  try {
13559
- this.process = spawn6(this.config.command, this.config.args ?? [], {
13425
+ this.process = spawn5(this.config.command, this.config.args ?? [], {
13560
13426
  stdio: ["pipe", "pipe", "pipe"],
13561
13427
  env: { ...process.env, ...this.config.env },
13562
13428
  // Windows 上 npx 等是 .cmd 脚本,需要 shell 模式
@@ -14035,11 +13901,11 @@ var McpManager = class {
14035
13901
  };
14036
13902
 
14037
13903
  // src/skills/manager.ts
14038
- import { existsSync as existsSync21, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync9 } from "fs";
14039
- import { join as join17 } from "path";
13904
+ import { existsSync as existsSync20, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync9 } from "fs";
13905
+ import { join as join14 } from "path";
14040
13906
 
14041
13907
  // src/skills/types.ts
14042
- import { readFileSync as readFileSync16 } from "fs";
13908
+ import { readFileSync as readFileSync13 } from "fs";
14043
13909
  import { basename as basename7 } from "path";
14044
13910
  function parseSimpleYaml(yaml) {
14045
13911
  const result = {};
@@ -14061,7 +13927,7 @@ function parseYamlArray(value) {
14061
13927
  function parseSkillFile(filePath) {
14062
13928
  let raw;
14063
13929
  try {
14064
- raw = readFileSync16(filePath, "utf-8");
13930
+ raw = readFileSync13(filePath, "utf-8");
14065
13931
  } catch {
14066
13932
  return null;
14067
13933
  }
@@ -14109,7 +13975,7 @@ var SkillManager = class {
14109
13975
  return this.skills.size;
14110
13976
  }
14111
13977
  loadSkillDir(dir, createIfMissing) {
14112
- if (!existsSync21(dir)) {
13978
+ if (!existsSync20(dir)) {
14113
13979
  if (createIfMissing) {
14114
13980
  try {
14115
13981
  mkdirSync10(dir, { recursive: true });
@@ -14126,14 +13992,14 @@ var SkillManager = class {
14126
13992
  }
14127
13993
  for (const entry of entries) {
14128
13994
  let filePath;
14129
- const fullPath = join17(dir, entry);
13995
+ const fullPath = join14(dir, entry);
14130
13996
  if (entry.endsWith(".md")) {
14131
13997
  filePath = fullPath;
14132
13998
  } else {
14133
13999
  try {
14134
14000
  if (statSync9(fullPath).isDirectory()) {
14135
- const skillMd = join17(fullPath, "SKILL.md");
14136
- if (existsSync21(skillMd)) filePath = skillMd;
14001
+ const skillMd = join14(fullPath, "SKILL.md");
14002
+ if (existsSync20(skillMd)) filePath = skillMd;
14137
14003
  else continue;
14138
14004
  } else {
14139
14005
  continue;
@@ -14246,7 +14112,7 @@ function sendSocketMessage(ws, message) {
14246
14112
  }
14247
14113
 
14248
14114
  // src/web/tool-executor-web.ts
14249
- import { existsSync as existsSync22, readFileSync as readFileSync17 } from "fs";
14115
+ import { existsSync as existsSync21, readFileSync as readFileSync14 } from "fs";
14250
14116
  var ToolExecutorWeb = class _ToolExecutorWeb {
14251
14117
  constructor(registry, ws) {
14252
14118
  this.registry = registry;
@@ -14393,9 +14259,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
14393
14259
  getDiffPreview(call) {
14394
14260
  if (call.name === "write_file") {
14395
14261
  const { path: filePath, content: chunk, append } = resolveWriteFileArgs(call.arguments);
14396
- if (filePath && existsSync22(filePath)) {
14262
+ if (filePath && existsSync21(filePath)) {
14397
14263
  try {
14398
- const old = readFileSync17(filePath, "utf-8");
14264
+ const old = readFileSync14(filePath, "utf-8");
14399
14265
  return renderDiff(old, append ? old + chunk : chunk, { filePath });
14400
14266
  } catch {
14401
14267
  }
@@ -14631,16 +14497,16 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
14631
14497
  };
14632
14498
 
14633
14499
  // src/repl/dev-state.ts
14634
- import { existsSync as existsSync23, readFileSync as readFileSync18, unlinkSync as unlinkSync5, mkdirSync as mkdirSync11 } from "fs";
14635
- import { join as join18 } from "path";
14636
- import { homedir as homedir8 } from "os";
14500
+ import { existsSync as existsSync22, readFileSync as readFileSync15, unlinkSync as unlinkSync5, mkdirSync as mkdirSync11 } from "fs";
14501
+ import { join as join15 } from "path";
14502
+ import { homedir as homedir6 } from "os";
14637
14503
  function getDevStatePath() {
14638
- return join18(homedir8(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
14504
+ return join15(homedir6(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
14639
14505
  }
14640
14506
  function loadDevState() {
14641
14507
  const path3 = getDevStatePath();
14642
- if (!existsSync23(path3)) return null;
14643
- const content = readFileSync18(path3, "utf-8").trim();
14508
+ if (!existsSync22(path3)) return null;
14509
+ const content = readFileSync15(path3, "utf-8").trim();
14644
14510
  return content || null;
14645
14511
  }
14646
14512
 
@@ -15010,8 +14876,8 @@ var checkpointDeletedMessage = (name) => `\u2713 Deleted checkpoint "${name}"`;
15010
14876
  var checkpointNotFoundMessage = (name) => `Checkpoint "${name}" not found.`;
15011
14877
 
15012
14878
  // src/core/context-files.ts
15013
- import { existsSync as existsSync24, readFileSync as readFileSync19 } from "fs";
15014
- import { join as join19, relative as relative4, resolve as resolve8 } from "path";
14879
+ import { existsSync as existsSync23, readFileSync as readFileSync16 } from "fs";
14880
+ import { join as join16, relative as relative4, resolve as resolve8 } from "path";
15015
14881
  function uniqueNonEmpty(values) {
15016
14882
  const seen = /* @__PURE__ */ new Set();
15017
14883
  const out = [];
@@ -15041,7 +14907,7 @@ function readContextFile(level, filePath, cwd, maxBytes) {
15041
14907
  const name = filePath.split(/[\\/]/).pop() ?? filePath;
15042
14908
  const shown = displayPath(filePath, cwd);
15043
14909
  try {
15044
- const raw = readFileSync19(filePath);
14910
+ const raw = readFileSync16(filePath);
15045
14911
  const byteCount = raw.byteLength;
15046
14912
  if (byteCount === 0) {
15047
14913
  return {
@@ -15088,8 +14954,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
15088
14954
  function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
15089
14955
  const skipped = [];
15090
14956
  for (const candidate of candidates) {
15091
- const filePath = join19(dir, candidate);
15092
- if (!existsSync24(filePath)) continue;
14957
+ const filePath = join16(dir, candidate);
14958
+ if (!existsSync23(filePath)) continue;
15093
14959
  const result = readContextFile(level, filePath, cwd, maxBytes);
15094
14960
  if (result.skipped) skipped.push(result.skipped);
15095
14961
  if (result.layer) return { layer: result.layer, skipped };
@@ -15116,7 +14982,7 @@ function loadContextFiles(options) {
15116
14982
  });
15117
14983
  return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
15118
14984
  }
15119
- if (!existsSync24(filePath)) {
14985
+ if (!existsSync23(filePath)) {
15120
14986
  skipped.push({
15121
14987
  level: "single",
15122
14988
  filePath,
@@ -15229,8 +15095,8 @@ import { mkdirSync as mkdirSync15, createWriteStream as createWriteStream2 } fro
15229
15095
  import { dirname as dirname10 } from "path";
15230
15096
 
15231
15097
  // src/repl/commands/project-init.ts
15232
- import { existsSync as existsSync25, readFileSync as readFileSync20, readdirSync as readdirSync11, statSync as statSync11 } from "fs";
15233
- import { join as join20 } from "path";
15098
+ import { existsSync as existsSync24, readFileSync as readFileSync17, readdirSync as readdirSync11, statSync as statSync11 } from "fs";
15099
+ import { join as join17 } from "path";
15234
15100
  var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
15235
15101
  "node_modules",
15236
15102
  ".git",
@@ -15274,11 +15140,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
15274
15140
  const sorted = filtered.sort((a, b) => {
15275
15141
  let aIsDir = false, bIsDir = false;
15276
15142
  try {
15277
- aIsDir = statSync11(join20(d, a)).isDirectory();
15143
+ aIsDir = statSync11(join17(d, a)).isDirectory();
15278
15144
  } catch {
15279
15145
  }
15280
15146
  try {
15281
- bIsDir = statSync11(join20(d, b)).isDirectory();
15147
+ bIsDir = statSync11(join17(d, b)).isDirectory();
15282
15148
  } catch {
15283
15149
  }
15284
15150
  if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
@@ -15286,7 +15152,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
15286
15152
  });
15287
15153
  for (let i = 0; i < sorted.length && count < maxEntries; i++) {
15288
15154
  const name = sorted[i];
15289
- const fullPath = join20(d, name);
15155
+ const fullPath = join17(d, name);
15290
15156
  const isLast = i === sorted.length - 1;
15291
15157
  const connector = isLast ? "+-- " : "|-- ";
15292
15158
  let isDir;
@@ -15313,7 +15179,7 @@ function scanProject(cwd) {
15313
15179
  configFiles: [],
15314
15180
  directoryStructure: ""
15315
15181
  };
15316
- const check = (file) => existsSync25(join20(cwd, file));
15182
+ const check = (file) => existsSync24(join17(cwd, file));
15317
15183
  const configCandidates = [
15318
15184
  "package.json",
15319
15185
  "tsconfig.json",
@@ -15340,7 +15206,7 @@ function scanProject(cwd) {
15340
15206
  info.type = "node";
15341
15207
  info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
15342
15208
  try {
15343
- const pkg = JSON.parse(readFileSync20(join20(cwd, "package.json"), "utf-8"));
15209
+ const pkg = JSON.parse(readFileSync17(join17(cwd, "package.json"), "utf-8"));
15344
15210
  const scripts = pkg.scripts ?? {};
15345
15211
  info.buildCommand = scripts.build ? "npm run build" : void 0;
15346
15212
  info.testCommand = scripts.test ? "npm test" : void 0;
@@ -16108,7 +15974,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
16108
15974
  }
16109
15975
 
16110
15976
  // src/hub/persist.ts
16111
- import { join as join21 } from "path";
15977
+ import { join as join18 } from "path";
16112
15978
  function discussionToMessages(state2) {
16113
15979
  const out = [];
16114
15980
  const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
@@ -16146,7 +16012,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
16146
16012
  session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
16147
16013
  session.titleAiGenerated = true;
16148
16014
  await sm.save();
16149
- return { id: session.id, path: join21(config.getHistoryDir(), `${session.id}.json`) };
16015
+ return { id: session.id, path: join18(config.getHistoryDir(), `${session.id}.json`) };
16150
16016
  }
16151
16017
 
16152
16018
  // src/core/think-command.ts
@@ -16267,10 +16133,10 @@ var PLAN_MODE_TOOL_HINT = [
16267
16133
  "read_file \xB7 list_dir \xB7 grep_files \xB7 glob_files",
16268
16134
  "web_fetch \xB7 google_search \xB7 ask_user \xB7 write_todos"
16269
16135
  ];
16270
- function applyPlanCommand(sub, active) {
16271
- const key = (sub ?? "enter").toLowerCase() === "toggle" ? active ? "exit" : "enter" : (sub ?? "enter").toLowerCase();
16136
+ function applyPlanCommand(sub, active2) {
16137
+ const key = (sub ?? "enter").toLowerCase() === "toggle" ? active2 ? "exit" : "enter" : (sub ?? "enter").toLowerCase();
16272
16138
  if (key === "enter") {
16273
- if (active) {
16139
+ if (active2) {
16274
16140
  return {
16275
16141
  planMode: true,
16276
16142
  changed: false,
@@ -16293,7 +16159,7 @@ function applyPlanCommand(sub, active) {
16293
16159
  };
16294
16160
  }
16295
16161
  if (key === "execute") {
16296
- if (!active) {
16162
+ if (!active2) {
16297
16163
  return { planMode: false, changed: false, status: "not-active", summary: "Not in Plan Mode. Enter first with /plan", details: [] };
16298
16164
  }
16299
16165
  return {
@@ -16305,22 +16171,22 @@ function applyPlanCommand(sub, active) {
16305
16171
  };
16306
16172
  }
16307
16173
  if (key === "exit" || key === "cancel") {
16308
- if (!active) {
16174
+ if (!active2) {
16309
16175
  return { planMode: false, changed: false, status: "not-active", summary: "Not in Plan Mode.", details: [] };
16310
16176
  }
16311
16177
  return { planMode: false, changed: true, status: "cancelled", summary: "Plan Mode cancelled. Returning to normal mode.", details: [] };
16312
16178
  }
16313
16179
  if (key === "status" || key === "show") {
16314
16180
  return {
16315
- planMode: active,
16181
+ planMode: active2,
16316
16182
  changed: false,
16317
16183
  status: "status",
16318
- summary: `Plan Mode: ${active ? "ACTIVE \u{1F4CB}" : "inactive"}`,
16319
- details: active ? ["Tools restricted to read-only set.", "Use /plan execute to start executing, or /plan exit to cancel."] : []
16184
+ summary: `Plan Mode: ${active2 ? "ACTIVE \u{1F4CB}" : "inactive"}`,
16185
+ details: active2 ? ["Tools restricted to read-only set.", "Use /plan execute to start executing, or /plan exit to cancel."] : []
16320
16186
  };
16321
16187
  }
16322
16188
  return {
16323
- planMode: active,
16189
+ planMode: active2,
16324
16190
  changed: false,
16325
16191
  status: "unknown",
16326
16192
  summary: `Unknown /plan option "${sub}" \u2014 expected execute | exit | status`,
@@ -16592,8 +16458,8 @@ function buildSessionCostReport(input) {
16592
16458
  const cacheHitPct = usage.cacheReadTokens > 0 ? Math.round(usage.cacheReadTokens / (usage.cacheReadTokens + usage.inputTokens) * 100) : null;
16593
16459
  return { hasUsage: true, totalTokens, cacheHitPct, cost, costSource, savings };
16594
16460
  }
16595
- function costSourceNote(source) {
16596
- switch (source) {
16461
+ function costSourceNote(source2) {
16462
+ switch (source2) {
16597
16463
  case "accrued-partial":
16598
16464
  return "(partial \u2014 some turns had unknown pricing)";
16599
16465
  case "estimated":
@@ -16604,8 +16470,8 @@ function costSourceNote(source) {
16604
16470
  }
16605
16471
 
16606
16472
  // src/web/commands/_shared.ts
16607
- import { existsSync as existsSync28, readFileSync as readFileSync22, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, statSync as statSync14, createWriteStream } from "fs";
16608
- import { join as join24, resolve as resolve9, dirname as dirname8 } from "path";
16473
+ import { existsSync as existsSync27, readFileSync as readFileSync19, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, statSync as statSync14, createWriteStream } from "fs";
16474
+ import { join as join21, resolve as resolve9, dirname as dirname8 } from "path";
16609
16475
 
16610
16476
  // src/cli/review-prompts.ts
16611
16477
  function buildReviewPrompt(diff, gitContextStr, detailed) {
@@ -16677,34 +16543,34 @@ function readGitDiff(options = {}) {
16677
16543
  }
16678
16544
 
16679
16545
  // src/diagnostics/doctor-report.ts
16680
- import { existsSync as existsSync27, statSync as statSync13 } from "fs";
16681
- import { join as join23 } from "path";
16682
- import { arch as arch2, platform as platform7, release as release2 } from "os";
16546
+ import { existsSync as existsSync26, statSync as statSync13 } from "fs";
16547
+ import { join as join20 } from "path";
16548
+ import { arch as arch2, platform as platform5, release as release2 } from "os";
16683
16549
 
16684
16550
  // src/diagnostics/crash-log.ts
16685
16551
  import {
16686
- existsSync as existsSync26,
16552
+ existsSync as existsSync25,
16687
16553
  mkdirSync as mkdirSync12,
16688
16554
  readdirSync as readdirSync12,
16689
- readFileSync as readFileSync21,
16555
+ readFileSync as readFileSync18,
16690
16556
  statSync as statSync12,
16691
16557
  unlinkSync as unlinkSync6,
16692
16558
  writeFileSync as writeFileSync2
16693
16559
  } from "fs";
16694
- import { join as join22 } from "path";
16695
- import { homedir as homedir9, platform as platform6, release, arch } from "os";
16560
+ import { join as join19 } from "path";
16561
+ import { homedir as homedir7, platform as platform4, release, arch } from "os";
16696
16562
  var LOGS_DIR_NAME = "logs";
16697
16563
  var CRASH_LOG_PREFIX = "crash-";
16698
16564
  function getLogsDir(configDir) {
16699
- const base = configDir ?? join22(homedir9(), CONFIG_DIR_NAME);
16700
- return join22(base, LOGS_DIR_NAME);
16565
+ const base = configDir ?? join19(homedir7(), CONFIG_DIR_NAME);
16566
+ return join19(base, LOGS_DIR_NAME);
16701
16567
  }
16702
16568
  function truncate(s, n) {
16703
16569
  return s.length <= n ? s : s.slice(0, n) + "\u2026";
16704
16570
  }
16705
16571
  function listRecentCrashes(limit = 10, configDir) {
16706
16572
  const dir = getLogsDir(configDir);
16707
- if (!existsSync26(dir)) return [];
16573
+ if (!existsSync25(dir)) return [];
16708
16574
  const files = [];
16709
16575
  let names;
16710
16576
  try {
@@ -16714,10 +16580,10 @@ function listRecentCrashes(limit = 10, configDir) {
16714
16580
  }
16715
16581
  for (const name of names) {
16716
16582
  if (!name.startsWith(CRASH_LOG_PREFIX) || !name.endsWith(".log")) continue;
16717
- const full = join22(dir, name);
16583
+ const full = join19(dir, name);
16718
16584
  try {
16719
16585
  const st = statSync12(full);
16720
- const head = readFileSync21(full, "utf-8").split("\n").slice(0, 20);
16586
+ const head = readFileSync18(full, "utf-8").split("\n").slice(0, 20);
16721
16587
  const tsLine = head.find((l) => l.startsWith("timestamp:"));
16722
16588
  const kindLine = head.find((l) => l.startsWith("kind:"));
16723
16589
  const msgIdx = head.findIndex((l) => l.trim() === "## message");
@@ -16736,8 +16602,8 @@ function listRecentCrashes(limit = 10, configDir) {
16736
16602
  return files.slice(0, limit);
16737
16603
  }
16738
16604
  function getConfigDirUsage(configDir) {
16739
- const base = configDir ?? join22(homedir9(), CONFIG_DIR_NAME);
16740
- if (!existsSync26(base)) return { totalBytes: 0, entries: [] };
16605
+ const base = configDir ?? join19(homedir7(), CONFIG_DIR_NAME);
16606
+ if (!existsSync25(base)) return { totalBytes: 0, entries: [] };
16741
16607
  const entries = [];
16742
16608
  let total = 0;
16743
16609
  let names;
@@ -16747,7 +16613,7 @@ function getConfigDirUsage(configDir) {
16747
16613
  return { totalBytes: 0, entries: [] };
16748
16614
  }
16749
16615
  for (const name of names) {
16750
- const full = join22(base, name);
16616
+ const full = join19(base, name);
16751
16617
  const bytes = dirSize(full);
16752
16618
  entries.push({ name, bytes });
16753
16619
  total += bytes;
@@ -16762,7 +16628,7 @@ function dirSize(path3) {
16762
16628
  if (!st.isDirectory()) return 0;
16763
16629
  let sum = 0;
16764
16630
  for (const name of readdirSync12(path3)) {
16765
- sum += dirSize(join22(path3, name));
16631
+ sum += dirSize(join19(path3, name));
16766
16632
  }
16767
16633
  return sum;
16768
16634
  } catch {
@@ -16772,7 +16638,7 @@ function dirSize(path3) {
16772
16638
 
16773
16639
  // src/diagnostics/doctor-report.ts
16774
16640
  function checkFile(label, path3) {
16775
- const exists = existsSync27(path3);
16641
+ const exists = existsSync26(path3);
16776
16642
  let sizeBytes = null;
16777
16643
  if (exists) {
16778
16644
  try {
@@ -16809,18 +16675,18 @@ function buildDoctorReport(options) {
16809
16675
  version: VERSION,
16810
16676
  npmCheck: options.npmCheck,
16811
16677
  node: process.version,
16812
- platform: `${platform7()} ${release2()} (${arch2()})`,
16678
+ platform: `${platform5()} ${release2()} (${arch2()})`,
16813
16679
  cwd,
16814
16680
  projectRoot,
16815
16681
  configDir,
16816
16682
  providers: options.providers.listAll().map((p) => ({ id: p.id, displayName: p.displayName, configured: p.configured })),
16817
16683
  files: [
16818
- checkFile("config.json", join23(configDir, "config.json")),
16819
- checkFile("memory.md", join23(configDir, MEMORY_FILE_NAME)),
16820
- checkFile("memory.jsonl", join23(configDir, "memory.jsonl")),
16821
- checkFile("dev-state.md", join23(configDir, DEV_STATE_FILE_NAME)),
16822
- checkFile("hooks-trust.json", join23(configDir, "hooks-trust.json")),
16823
- checkFile("plugin-state.json", join23(configDir, "plugins", "plugin-state.json"))
16684
+ checkFile("config.json", join20(configDir, "config.json")),
16685
+ checkFile("memory.md", join20(configDir, MEMORY_FILE_NAME)),
16686
+ checkFile("memory.jsonl", join20(configDir, "memory.jsonl")),
16687
+ checkFile("dev-state.md", join20(configDir, DEV_STATE_FILE_NAME)),
16688
+ checkFile("hooks-trust.json", join20(configDir, "hooks-trust.json")),
16689
+ checkFile("plugin-state.json", join20(configDir, "plugins", "plugin-state.json"))
16824
16690
  ],
16825
16691
  context: {
16826
16692
  enabled: contextSetting !== false,
@@ -16855,7 +16721,7 @@ function buildDoctorReport(options) {
16855
16721
  disabled: hooks.filter((h) => h.disabled).length
16856
16722
  },
16857
16723
  plugins: {
16858
- root: join23(configDir, "plugins"),
16724
+ root: join20(configDir, "plugins"),
16859
16725
  installed: plugins.length,
16860
16726
  active: activeAssets.plugins.length,
16861
16727
  invalid: plugins.filter((p) => !p.valid).length,
@@ -16870,7 +16736,7 @@ function buildDoctorReport(options) {
16870
16736
  mcp: {
16871
16737
  enabled: config.get("mcpEnabled"),
16872
16738
  configured: Object.keys(mcpServers).length,
16873
- projectConfigExists: existsSync27(join23(projectRoot, MCP_PROJECT_CONFIG_NAME)),
16739
+ projectConfigExists: existsSync26(join20(projectRoot, MCP_PROJECT_CONFIG_NAME)),
16874
16740
  statuses: options.mcpStatuses ?? []
16875
16741
  },
16876
16742
  recentCrashes: listRecentCrashes(5, configDir),
@@ -17392,7 +17258,7 @@ Cache: write=${cacheCreate} read=${cacheRead}` : "";
17392
17258
  Cost: ${formatCost(cost)}` : "";
17393
17259
  let memoryLine = "";
17394
17260
  try {
17395
- const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-GJE7HKVK.js");
17261
+ const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-EXJBXL33.js");
17396
17262
  const pending = countPendingMemories2(ctx.config.getConfigDir());
17397
17263
  if (pending > 0) memoryLine = `
17398
17264
  Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
@@ -18015,7 +17881,7 @@ async function handleRewind(args, ctx) {
18015
17881
  ctx.send({ type: "error", message: intent.message });
18016
17882
  return;
18017
17883
  }
18018
- const { fileCheckpoints: fileCheckpoints2 } = await import("./file-checkpoint-CGH6OJVI.js");
17884
+ const { fileCheckpoints: fileCheckpoints2 } = await import("./file-checkpoint-64A2AXHE.js");
18019
17885
  if (intent.kind === "list") {
18020
17886
  const rows = buildRewindList(session.messages, fileCheckpoints2.getMessageIndices());
18021
17887
  ctx.send({
@@ -18072,9 +17938,9 @@ function parseAgentCommand(args) {
18072
17938
  function formatAgentList(agents, preferred) {
18073
17939
  const lines = [`Agents (${agents.length})`, ""];
18074
17940
  for (const agent of agents) {
18075
- const active = agent.name === preferred ? " *" : " ";
17941
+ const active2 = agent.name === preferred ? " *" : " ";
18076
17942
  const model = [agent.provider, agent.model].filter(Boolean).join("/") || "inherit";
18077
- lines.push(`${active} ${agent.name} [${agent.source ?? "builtin"}] \u2014 ${agent.description ?? ""}`);
17943
+ lines.push(`${active2} ${agent.name} [${agent.source ?? "builtin"}] \u2014 ${agent.description ?? ""}`);
18078
17944
  lines.push(
18079
17945
  ` model: ${model} \xB7 permission: ${agent.permissionProfile ?? "workspace-write"} \xB7 maxRounds: ${agent.maxToolRounds ?? "inherit"}`
18080
17946
  );
@@ -18133,7 +17999,7 @@ async function handlePlugin(args, ctx) {
18133
17999
  lines.push("");
18134
18000
  }
18135
18001
  if (pluginTools.length > 0) {
18136
- const legacyDir = join24(configDir, PLUGINS_DIR_NAME);
18002
+ const legacyDir = join21(configDir, PLUGINS_DIR_NAME);
18137
18003
  lines.push(`**Legacy JS tools** (${pluginTools.length}):`);
18138
18004
  lines.push(`Dir: ${legacyDir}`);
18139
18005
  for (const t of pluginTools) lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
@@ -18215,14 +18081,14 @@ async function handleSkill(args, ctx) {
18215
18081
  return;
18216
18082
  }
18217
18083
  const intent = parseSkillCommand(args);
18218
- const active = ctx.skillManager.getActive();
18084
+ const active2 = ctx.skillManager.getActive();
18219
18085
  if (intent.kind === "list") {
18220
18086
  const skills = ctx.skillManager.listSkills();
18221
18087
  if (skills.length === 0) {
18222
18088
  ctx.send({ type: "info", message: "No skills available. Add .md files to ~/.aicli/skills/" });
18223
18089
  } else {
18224
18090
  const lines = skills.map((s) => {
18225
- const isActive = active?.meta.name === s.meta.name;
18091
+ const isActive = active2?.meta.name === s.meta.name;
18226
18092
  const marker = isActive ? " \u2705 (active)" : "";
18227
18093
  return ` ${s.meta.name}${marker} \u2014 ${s.meta.description || "(no description)"}`;
18228
18094
  });
@@ -18238,12 +18104,12 @@ ${lines.join("\n")}` });
18238
18104
  return;
18239
18105
  }
18240
18106
  const note = skillHistoryNote(
18241
- active?.meta.name ?? "(none)",
18107
+ active2?.meta.name ?? "(none)",
18242
18108
  intent.kind === "deactivate" ? "(none)" : intent.name,
18243
18109
  ctx.sessions.current?.messages.length ?? 0
18244
18110
  );
18245
18111
  if (intent.kind === "deactivate") {
18246
- if (!active) {
18112
+ if (!active2) {
18247
18113
  ctx.send({ type: "info", message: "No skill is currently active." });
18248
18114
  return;
18249
18115
  }
@@ -18254,7 +18120,7 @@ ${note}` : "\u{1F50C} Skill deactivated." });
18254
18120
  ctx.sendStatus();
18255
18121
  return;
18256
18122
  }
18257
- if (active?.meta.name === intent.name) {
18123
+ if (active2?.meta.name === intent.name) {
18258
18124
  ctx.send({ type: "info", message: `Skill '${intent.name}' is already active.` });
18259
18125
  return;
18260
18126
  }
@@ -18273,8 +18139,8 @@ ${note}` : head });
18273
18139
  }
18274
18140
  async function handleCommands(_args, ctx) {
18275
18141
  const configDir = ctx.config.getConfigDir();
18276
- const commandsDir = join24(configDir, CUSTOM_COMMANDS_DIR_NAME);
18277
- if (!existsSync28(commandsDir)) {
18142
+ const commandsDir = join21(configDir, CUSTOM_COMMANDS_DIR_NAME);
18143
+ if (!existsSync27(commandsDir)) {
18278
18144
  ctx.send({ type: "info", message: `No custom commands directory.
18279
18145
  Create: ${commandsDir}/ with .md files.` });
18280
18146
  return;
@@ -18380,7 +18246,7 @@ async function handleTest(args, ctx) {
18380
18246
  const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
18381
18247
  testArgs = isCommand ? { command: argStr } : { filter: argStr };
18382
18248
  }
18383
- const runTests = ctx.runTests ?? (await import("./run-tests-V3N7YV3P.js")).executeTests;
18249
+ const runTests = ctx.runTests ?? (await import("./run-tests-2C7MWZBN.js")).executeTests;
18384
18250
  const report = await runTests(testArgs);
18385
18251
  ctx.send({ type: "info", message: report });
18386
18252
  } catch (err) {
@@ -18404,9 +18270,9 @@ async function handleDiff(args, ctx) {
18404
18270
  let modifiedFiles = 0;
18405
18271
  const diffLines2 = [];
18406
18272
  for (const [filePath, { earliest }] of fileMap) {
18407
- const currentContent = existsSync28(filePath) ? (() => {
18273
+ const currentContent = existsSync27(filePath) ? (() => {
18408
18274
  try {
18409
- return readFileSync22(filePath, "utf-8");
18275
+ return readFileSync19(filePath, "utf-8");
18410
18276
  } catch {
18411
18277
  return null;
18412
18278
  }
@@ -18441,9 +18307,9 @@ async function handleDiff(args, ctx) {
18441
18307
  }
18442
18308
  async function handleInit(args, ctx) {
18443
18309
  const cwd = process.cwd();
18444
- const targetPath = join24(cwd, "AICLI.md");
18310
+ const targetPath = join21(cwd, "AICLI.md");
18445
18311
  const force = args.includes("--force");
18446
- if (existsSync28(targetPath) && !force) {
18312
+ if (existsSync27(targetPath) && !force) {
18447
18313
  ctx.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
18448
18314
  Use /init --force to overwrite.` });
18449
18315
  return;
@@ -18672,9 +18538,9 @@ async function handleContext(_args, ctx) {
18672
18538
  async function handleIndex(args, ctx) {
18673
18539
  const sub = (args[0] ?? "status").toLowerCase();
18674
18540
  const root = process.cwd();
18675
- const { loadIndex: loadIndex2, clearIndex } = await import("./store-SIKHCUYJ.js");
18676
- const { indexProject: indexProject2 } = await import("./indexer-FPKY7LCT.js");
18677
- const { loadVectorStore, clearVectorStore } = await import("./vector-store-JAXWDZYW.js");
18541
+ const { loadIndex: loadIndex2, clearIndex } = await import("./store-2N6PPPBP.js");
18542
+ const { indexProject: indexProject2 } = await import("./indexer-VRXDZ74A.js");
18543
+ const { loadVectorStore, clearVectorStore } = await import("./vector-store-T2DVUMY4.js");
18678
18544
  if (sub === "status") {
18679
18545
  const idx = loadIndex2(root);
18680
18546
  const vec = loadVectorStore(root);
@@ -18724,7 +18590,7 @@ async function handleIndex(args, ctx) {
18724
18590
  message: `Building semantic index for ${idx.symbolCount} symbols\u2026 (first run downloads ~117 MB model)`
18725
18591
  });
18726
18592
  try {
18727
- const { rebuildSemanticIndex } = await import("./semantic-QOBAXEQ4.js");
18593
+ const { rebuildSemanticIndex } = await import("./semantic-TRQAZSRY.js");
18728
18594
  const stats = await rebuildSemanticIndex(root);
18729
18595
  const first = stats.modelFirstLoadMs ? ` (model load+first batch ${stats.modelFirstLoadMs}ms)` : "";
18730
18596
  ctx.send({
@@ -18755,7 +18621,7 @@ async function handleMemory(args, ctx) {
18755
18621
  if (intent.kind === "rebuild") {
18756
18622
  ctx.send({ type: "info", message: rebuildStartMessage(intent.full) });
18757
18623
  try {
18758
- const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-DGUOUUQW.js");
18624
+ const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
18759
18625
  const stats = await buildChatIndex2({ full: intent.full });
18760
18626
  ctx.send({ type: "info", message: rebuildDoneMessage(stats) });
18761
18627
  } catch (err) {
@@ -18764,14 +18630,14 @@ async function handleMemory(args, ctx) {
18764
18630
  return;
18765
18631
  }
18766
18632
  if (intent.kind === "index-clear") {
18767
- const { clearChatIndex: clearChatIndex2 } = await import("./chat-index-DGUOUUQW.js");
18633
+ const { clearChatIndex: clearChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
18768
18634
  clearChatIndex2();
18769
18635
  ctx.send({ type: "info", message: "Chat memory index cleared (persistent memory untouched)." });
18770
18636
  return;
18771
18637
  }
18772
18638
  if (intent.kind === "status") {
18773
- const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-DGUOUUQW.js");
18774
- const { listMemoryEntries: listMemoryEntries2, isMemoryExpired: isMemoryExpired2, formatMemoryForPrompt: formatMemoryForPrompt2 } = await import("./persistent-memory-GJE7HKVK.js");
18639
+ const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-RLVLQRRC.js");
18640
+ const { listMemoryEntries: listMemoryEntries2, isMemoryExpired: isMemoryExpired2, formatMemoryForPrompt: formatMemoryForPrompt2 } = await import("./persistent-memory-EXJBXL33.js");
18775
18641
  const memories = listMemoryEntries2(configDir, { includeExpired: true, includeRejected: true });
18776
18642
  const injected = formatMemoryForPrompt2(configDir, process.cwd(), ctx.config.get("memory")?.maxChars);
18777
18643
  const view = buildMemoryStatusView({
@@ -18794,8 +18660,8 @@ async function handleMemory(args, ctx) {
18794
18660
  return;
18795
18661
  }
18796
18662
  if (intent.kind === "recall") {
18797
- const { searchPersistentMemories: searchPersistentMemories2, touchMemoryReferences: touchMemoryReferences2 } = await import("./persistent-memory-GJE7HKVK.js");
18798
- const { searchChatMemory: searchChatMemory2 } = await import("./chat-index-DGUOUUQW.js");
18663
+ const { searchPersistentMemories: searchPersistentMemories2, touchMemoryReferences: touchMemoryReferences2 } = await import("./persistent-memory-EXJBXL33.js");
18664
+ const { searchChatMemory: searchChatMemory2 } = await import("./chat-index-RLVLQRRC.js");
18799
18665
  const persistentHits = searchPersistentMemories2(configDir, intent.query, process.cwd(), 3);
18800
18666
  if (persistentHits.length > 0) {
18801
18667
  touchMemoryReferences2(configDir, persistentHits.map((h) => h.entry.id));
@@ -18915,7 +18781,7 @@ async function handleMemory(args, ctx) {
18915
18781
  ctx.handleMemoryManage(intent.action, args[1], intent.action === "expire" ? args[2] : void 0);
18916
18782
  } else if (intent.kind === "export") {
18917
18783
  if (args[1] === "md") {
18918
- const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-GJE7HKVK.js");
18784
+ const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-EXJBXL33.js");
18919
18785
  ctx.send({
18920
18786
  type: "export_data",
18921
18787
  format: "md",
@@ -18953,7 +18819,7 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
18953
18819
  return;
18954
18820
  }
18955
18821
  const dirPath = resolve9(sub);
18956
- if (!existsSync28(dirPath)) {
18822
+ if (!existsSync27(dirPath)) {
18957
18823
  ctx.send({ type: "error", message: `Directory not found: ${dirPath}` });
18958
18824
  return;
18959
18825
  }
@@ -19317,7 +19183,7 @@ async function handleAgent(args, ctx) {
19317
19183
 
19318
19184
  // src/repl/commands/utils.ts
19319
19185
  import { execSync as execSync2 } from "child_process";
19320
- import { platform as platform8 } from "os";
19186
+ import { platform as platform6 } from "os";
19321
19187
 
19322
19188
  // src/repl/commands/constants.ts
19323
19189
  var SESSION_PAGE_SIZE = 20;
@@ -19330,7 +19196,7 @@ function fmtCtx(tokens) {
19330
19196
  return `${tokens}`;
19331
19197
  }
19332
19198
  function copyToClipboard(text) {
19333
- const plat = platform8();
19199
+ const plat = platform6();
19334
19200
  if (plat === "win32") {
19335
19201
  execSync2("clip", { input: text, stdio: ["pipe", "ignore", "ignore"] });
19336
19202
  } else if (plat === "darwin") {
@@ -19917,16 +19783,16 @@ No tools match "${filter}".
19917
19783
  usage: "/mcp [reconnect [serverId] | trust-project]",
19918
19784
  async execute(args, ctx) {
19919
19785
  if (args[0] === "trust-project") {
19920
- const { join: join29 } = await import("path");
19921
- const { existsSync: existsSync33 } = await import("fs");
19786
+ const { join: join26 } = await import("path");
19787
+ const { existsSync: existsSync32 } = await import("fs");
19922
19788
  const { getGitRoot: getGitRoot2 } = await import("./git-context-VNCA2GU7.js");
19923
- const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-5CMEFZQB.js");
19924
- const { approveProject, hashMcpFile } = await import("./project-trust-MUG325AW.js");
19789
+ const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-GYJTB6KI.js");
19790
+ const { approveProject, hashMcpFile } = await import("./project-trust-6CJB5ZMX.js");
19925
19791
  const cwd = process.cwd();
19926
19792
  const projectRoot = getGitRoot2(cwd) ?? cwd;
19927
- const mcpPath = join29(projectRoot, MCP_PROJECT_CONFIG_NAME2);
19793
+ const mcpPath = join26(projectRoot, MCP_PROJECT_CONFIG_NAME2);
19928
19794
  console.log();
19929
- if (!existsSync33(mcpPath)) {
19795
+ if (!existsSync32(mcpPath)) {
19930
19796
  console.log(theme.dim(` No .mcp.json in ${projectRoot}.`));
19931
19797
  console.log();
19932
19798
  return;
@@ -19999,8 +19865,8 @@ No tools match "${filter}".
19999
19865
  const errorStr = s.error ? theme.error(`
20000
19866
  Error: ${s.error}`) : "";
20001
19867
  const serverLabel = s.serverName !== s.serverId ? ` (${s.serverName})` : "";
20002
- const source = ctx.getMcpServerSource(s.serverId);
20003
- const sourceTag = source === "project" ? theme.accent(" [project]") : theme.dim(" [global]");
19868
+ const source2 = ctx.getMcpServerSource(s.serverId);
19869
+ const sourceTag = source2 === "project" ? theme.accent(" [project]") : theme.dim(" [global]");
20004
19870
  console.log(` ${theme.accent(s.serverId)}${theme.dim(serverLabel)}${sourceTag} [${statusBadge}]${toolCountStr}${errorStr}`);
20005
19871
  if (s.connected) {
20006
19872
  const mcpTools = ctx.tools.listMcpTools().filter(
@@ -20028,7 +19894,7 @@ No tools match "${filter}".
20028
19894
  }
20029
19895
  if (!sub || sub === "list") {
20030
19896
  const skills = manager.listSkills();
20031
- const active = manager.getActive();
19897
+ const active2 = manager.getActive();
20032
19898
  console.log();
20033
19899
  console.log(theme.heading(" \u{1F3AF} Agent Skills"));
20034
19900
  console.log(theme.dim(" " + "\u2500".repeat(50)));
@@ -20038,7 +19904,7 @@ No tools match "${filter}".
20038
19904
  console.log(theme.dim(" Format: YAML frontmatter (name/description/tools) + prompt body"));
20039
19905
  } else {
20040
19906
  for (const s of skills) {
20041
- const isActive = active?.meta.name === s.meta.name;
19907
+ const isActive = active2?.meta.name === s.meta.name;
20042
19908
  const badge = isActive ? theme.success(" \u2713 active") : "";
20043
19909
  const toolInfo = s.meta.tools ? theme.dim(` [${s.meta.tools.join(", ")}]`) : "";
20044
19910
  console.log(
@@ -20126,7 +19992,7 @@ No tools match "${filter}".
20126
19992
  }
20127
19993
 
20128
19994
  // src/repl/commands/security-commands.ts
20129
- import { platform as platform9 } from "os";
19995
+ import { platform as platform7 } from "os";
20130
19996
  import chalk6 from "chalk";
20131
19997
  function createSecurityCommands() {
20132
19998
  return [
@@ -20207,7 +20073,7 @@ ${outcome.summary}
20207
20073
  usage: "/bug [--copy]",
20208
20074
  execute(args, ctx) {
20209
20075
  const doCopy = args.includes("--copy");
20210
- const os = platform9();
20076
+ const os = platform7();
20211
20077
  const nodeVer = process.version;
20212
20078
  const provider = ctx.getCurrentProvider();
20213
20079
  const model = ctx.getCurrentModel();
@@ -20361,12 +20227,12 @@ ${outcome.summary}
20361
20227
  if (scanAll) {
20362
20228
  const metas = ctx.sessions.listSessions();
20363
20229
  console.log(theme.info(` Scanning ${metas.length} session(s)\u2026`));
20364
- const { readFileSync: readFileSync26 } = await import("fs");
20365
- const { join: join29 } = await import("path");
20230
+ const { readFileSync: readFileSync23 } = await import("fs");
20231
+ const { join: join26 } = await import("path");
20366
20232
  const historyDir = ctx.config.getHistoryDir();
20367
20233
  for (const m of metas) {
20368
20234
  try {
20369
- const content = readFileSync26(join29(historyDir, `${m.id}.json`), "utf-8");
20235
+ const content = readFileSync23(join26(historyDir, `${m.id}.json`), "utf-8");
20370
20236
  const hits2 = scanString(content, opts);
20371
20237
  if (hits2.length) {
20372
20238
  filesWithHits++;
@@ -20413,8 +20279,8 @@ ${outcome.summary}
20413
20279
  }
20414
20280
 
20415
20281
  // src/repl/commands/review-commands.ts
20416
- import { existsSync as existsSync29, readFileSync as readFileSync23, writeFileSync as writeFileSync4 } from "fs";
20417
- import { join as join25 } from "path";
20282
+ import { existsSync as existsSync28, readFileSync as readFileSync20, writeFileSync as writeFileSync4 } from "fs";
20283
+ import { join as join22 } from "path";
20418
20284
  function createReviewCommands() {
20419
20285
  return [
20420
20286
  {
@@ -20588,11 +20454,11 @@ function createReviewCommands() {
20588
20454
  Branches (${branches.length}):
20589
20455
  `));
20590
20456
  for (const b of branches) {
20591
- const active = b.id === session.activeBranchId ? theme.success("\u25CF ") : " ";
20457
+ const active2 = b.id === session.activeBranchId ? theme.success("\u25CF ") : " ";
20592
20458
  const parent = b.parentBranchId ? theme.dim(` \u2190 ${b.parentBranchId}@${b.parentMessageIndex}`) : "";
20593
20459
  const count = b.id === session.activeBranchId ? session.messages.length : session.getBranchMessages(b.id)?.length ?? 0;
20594
20460
  console.log(
20595
- ` ${active}${theme.accent(b.id.padEnd(10))} ${b.title.padEnd(20)} ` + theme.dim(`(${count} msgs)`) + parent
20461
+ ` ${active2}${theme.accent(b.id.padEnd(10))} ${b.title.padEnd(20)} ` + theme.dim(`(${count} msgs)`) + parent
20596
20462
  );
20597
20463
  }
20598
20464
  console.log();
@@ -20705,9 +20571,9 @@ function createReviewCommands() {
20705
20571
  ctx.renderer.renderError(`Branch "${id}" not found.`);
20706
20572
  return;
20707
20573
  }
20708
- const active = session.activeBranchId;
20574
+ const active2 = session.activeBranchId;
20709
20575
  console.log(`
20710
- ${theme.info("diff")} ${id} (source) \u2194 ${active} (active)`);
20576
+ ${theme.info("diff")} ${id} (source) \u2194 ${active2} (active)`);
20711
20577
  console.log(theme.dim(` Common prefix: ${d.commonPrefix} message(s)`));
20712
20578
  if (d.sourceOnly.length === 0 && d.targetOnly.length === 0) {
20713
20579
  console.log(theme.success(" Branches are identical.\n"));
@@ -20726,7 +20592,7 @@ function createReviewCommands() {
20726
20592
  }
20727
20593
  if (d.targetOnly.length > 0) {
20728
20594
  console.log(theme.dim(`
20729
- Only in ${active} (active) (${d.targetOnly.length}):`));
20595
+ Only in ${active2} (active) (${d.targetOnly.length}):`));
20730
20596
  d.targetOnly.forEach((m, i) => {
20731
20597
  console.log(` ${theme.dim(`[${d.commonPrefix + i}]`)} ${m.role}: ${preview(m)}`);
20732
20598
  });
@@ -20773,9 +20639,9 @@ function createReviewCommands() {
20773
20639
  usage: "/init [--force]",
20774
20640
  async execute(args, ctx) {
20775
20641
  const cwd = process.cwd();
20776
- const targetPath = join25(cwd, "AICLI.md");
20642
+ const targetPath = join22(cwd, "AICLI.md");
20777
20643
  const force = args.includes("--force");
20778
- if (existsSync29(targetPath) && !force) {
20644
+ if (existsSync28(targetPath) && !force) {
20779
20645
  ctx.renderer.printInfo(`AICLI.md already exists at ${targetPath}`);
20780
20646
  ctx.renderer.printInfo("Use /init --force to overwrite, or edit it manually.");
20781
20647
  return;
@@ -20839,7 +20705,7 @@ function createReviewCommands() {
20839
20705
  usage: "/test [command|filter]",
20840
20706
  async execute(args, ctx) {
20841
20707
  try {
20842
- const { executeTests } = await import("./run-tests-V3N7YV3P.js");
20708
+ const { executeTests } = await import("./run-tests-2C7MWZBN.js");
20843
20709
  const argStr = args.join(" ").trim();
20844
20710
  let testArgs = {};
20845
20711
  if (argStr) {
@@ -20874,9 +20740,9 @@ function createReviewCommands() {
20874
20740
  let newFiles = 0;
20875
20741
  let modifiedFiles = 0;
20876
20742
  for (const [filePath, { earliest }] of fileMap) {
20877
- const currentContent = existsSync29(filePath) ? (() => {
20743
+ const currentContent = existsSync28(filePath) ? (() => {
20878
20744
  try {
20879
- return readFileSync23(filePath, "utf-8");
20745
+ return readFileSync20(filePath, "utf-8");
20880
20746
  } catch {
20881
20747
  return null;
20882
20748
  }
@@ -21090,9 +20956,9 @@ function createMemoryCommands() {
21090
20956
  async execute(args, ctx) {
21091
20957
  const sub = (args[0] ?? "status").toLowerCase();
21092
20958
  const root = process.cwd();
21093
- const { loadIndex: loadIndex2, clearIndex } = await import("./store-SIKHCUYJ.js");
21094
- const { indexProject: indexProject2 } = await import("./indexer-FPKY7LCT.js");
21095
- const { loadVectorStore, clearVectorStore } = await import("./vector-store-JAXWDZYW.js");
20959
+ const { loadIndex: loadIndex2, clearIndex } = await import("./store-2N6PPPBP.js");
20960
+ const { indexProject: indexProject2 } = await import("./indexer-VRXDZ74A.js");
20961
+ const { loadVectorStore, clearVectorStore } = await import("./vector-store-T2DVUMY4.js");
21096
20962
  if (sub === "status") {
21097
20963
  const idx = loadIndex2(root);
21098
20964
  const vec = loadVectorStore(root);
@@ -21152,7 +21018,7 @@ function createMemoryCommands() {
21152
21018
  }
21153
21019
  console.log(theme.dim(` Building semantic index for ${idx.symbolCount} symbols\u2026`));
21154
21020
  console.log(theme.dim(" (First run downloads ~117 MB embedding model to ~/.aicli/models/)"));
21155
- const { rebuildSemanticIndex } = await import("./semantic-QOBAXEQ4.js");
21021
+ const { rebuildSemanticIndex } = await import("./semantic-TRQAZSRY.js");
21156
21022
  try {
21157
21023
  const stats = await rebuildSemanticIndex(root, {
21158
21024
  onProgress: (done, total) => {
@@ -21351,14 +21217,14 @@ ${view.chatHeading}
21351
21217
  console.log(theme.heading("\n\u{1F4DD} Governed Persistent Memory:") + filterNote + "\n" + theme.dim("\u2500".repeat(72)));
21352
21218
  for (const entry of entries) {
21353
21219
  const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
21354
- const source = [
21220
+ const source2 = [
21355
21221
  entry.source ?? "unknown",
21356
21222
  entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : "",
21357
21223
  entry.sourceProject ? `project ${entry.sourceProject}` : ""
21358
21224
  ].filter(Boolean).join(" \xB7 ");
21359
21225
  console.log(theme.accent(` ${entry.id.slice(0, 8)} ${status} scope:${entry.scope} sensitivity:${entry.sensitivity}`));
21360
21226
  const lastRef = entry.lastReferencedAt ? ` \xB7 last-referenced: ${entry.lastReferencedAt.slice(0, 10)}` : "";
21361
- console.log(theme.dim(` source: ${source} \xB7 created: ${entry.createdAt.replace("T", " ").slice(0, 19)}${lastRef}${entry.expiresAt ? ` \xB7 expires: ${entry.expiresAt}` : ""}`));
21227
+ console.log(theme.dim(` source: ${source2} \xB7 created: ${entry.createdAt.replace("T", " ").slice(0, 19)}${lastRef}${entry.expiresAt ? ` \xB7 expires: ${entry.expiresAt}` : ""}`));
21362
21228
  console.log(" " + entry.content.replace(/\n/g, "\n "));
21363
21229
  console.log();
21364
21230
  }
@@ -22039,16 +21905,16 @@ function createSessionCommands() {
22039
21905
 
22040
21906
  // src/repl/commands/misc-commands.ts
22041
21907
  import { basename as basename9 } from "path";
22042
- import { platform as platform10 } from "os";
21908
+ import { platform as platform8 } from "os";
22043
21909
 
22044
21910
  // src/repl/clipboard.ts
22045
21911
  import { execSync as execSync3 } from "child_process";
22046
- import { existsSync as existsSync30, statSync as statSync15, unlinkSync as unlinkSync7 } from "fs";
21912
+ import { existsSync as existsSync29, statSync as statSync15, unlinkSync as unlinkSync7 } from "fs";
22047
21913
  import { tmpdir as tmpdir3 } from "os";
22048
- import { join as join26 } from "path";
21914
+ import { join as join23 } from "path";
22049
21915
  var CLIPBOARD_TIMEOUT = 5e3;
22050
21916
  function readClipboardImage() {
22051
- const outPath = join26(tmpdir3(), `aicli-paste-${Date.now()}.png`);
21917
+ const outPath = join23(tmpdir3(), `aicli-paste-${Date.now()}.png`);
22052
21918
  try {
22053
21919
  if (process.platform === "win32") {
22054
21920
  _readWin32(outPath);
@@ -22061,7 +21927,7 @@ function readClipboardImage() {
22061
21927
  _cleanup(outPath);
22062
21928
  return null;
22063
21929
  }
22064
- if (!existsSync30(outPath) || statSync15(outPath).size === 0) {
21930
+ if (!existsSync29(outPath) || statSync15(outPath).size === 0) {
22065
21931
  _cleanup(outPath);
22066
21932
  return null;
22067
21933
  }
@@ -22114,7 +21980,7 @@ function _readLinux(outPath) {
22114
21980
  }
22115
21981
  function _cleanup(path3) {
22116
21982
  try {
22117
- if (existsSync30(path3)) unlinkSync7(path3);
21983
+ if (existsSync29(path3)) unlinkSync7(path3);
22118
21984
  } catch {
22119
21985
  }
22120
21986
  }
@@ -22361,7 +22227,7 @@ No commands match "${filter}".
22361
22227
  ctx.renderer.printSuccess(`Copied to clipboard (${lines} lines, ${chars} chars)`);
22362
22228
  } catch (err) {
22363
22229
  const msg = err instanceof Error ? err.message : String(err);
22364
- if (platform10() === "linux" && msg.includes("not found")) {
22230
+ if (platform8() === "linux" && msg.includes("not found")) {
22365
22231
  ctx.renderer.renderError("Clipboard tool not found. Install xclip or xsel: sudo apt install xclip");
22366
22232
  } else {
22367
22233
  ctx.renderer.renderError(`Failed to copy: ${msg}`);
@@ -22956,7 +22822,18 @@ var SessionHandler = class _SessionHandler {
22956
22822
  }))
22957
22823
  });
22958
22824
  }
22825
+ executionState = /* @__PURE__ */ new Map();
22826
+ connectionAbort = new AbortController();
22959
22827
  async handleMessage(raw) {
22828
+ if (this.connectionAbort.signal.aborted && /"type"\s*:\s*"(?:chat|command|hub_start)"/.test(raw)) this.connectionAbort = new AbortController();
22829
+ return runWithExecutionContext({
22830
+ configDir: this.config.getConfigDir(),
22831
+ sessionKey: this._bashSessionKey,
22832
+ state: this.executionState,
22833
+ signal: this.connectionAbort.signal
22834
+ }, () => this.handleMessageScoped(raw));
22835
+ }
22836
+ async handleMessageScoped(raw) {
22960
22837
  if (this.disconnected) return;
22961
22838
  let msg;
22962
22839
  try {
@@ -22967,6 +22844,7 @@ var SessionHandler = class _SessionHandler {
22967
22844
  }
22968
22845
  switch (msg.type) {
22969
22846
  case "chat": {
22847
+ if (this.connectionAbort.signal.aborted) this.connectionAbort = new AbortController();
22970
22848
  if (msg.messageId) {
22971
22849
  if (typeof msg.messageId !== "string" || msg.messageId.length > 200) throw new Error("Invalid message ID");
22972
22850
  const scope = this.config.getConfigDir();
@@ -23027,6 +22905,7 @@ var SessionHandler = class _SessionHandler {
23027
22905
  return;
23028
22906
  }
23029
22907
  case "abort":
22908
+ this.connectionAbort.abort();
23030
22909
  if (this.abortController) {
23031
22910
  this.abortController.abort();
23032
22911
  }
@@ -23050,8 +22929,16 @@ var SessionHandler = class _SessionHandler {
23050
22929
  }
23051
22930
  }
23052
22931
  onDisconnect(closeSocket = true) {
22932
+ runWithExecutionContext({
22933
+ configDir: this.config.getConfigDir(),
22934
+ sessionKey: this._bashSessionKey,
22935
+ state: this.executionState
22936
+ }, () => this.disconnectScoped(closeSocket));
22937
+ }
22938
+ disconnectScoped(closeSocket) {
23053
22939
  if (this.disconnected) return;
23054
22940
  this.disconnected = true;
22941
+ this.connectionAbort.abort();
23055
22942
  if (closeSocket && this.ws.readyState === this.ws.OPEN) this.ws.close(1001, "Connection closed; reconnect to restore session");
23056
22943
  runLifecycleHooks(mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()), "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
23057
22944
  this.toolExecutor.cancelAll();
@@ -24030,20 +23917,20 @@ ${summaryContent}`,
24030
23917
  this.send({ type: "error", message: "Usage: /memory show --stale <days> (positive number)" });
24031
23918
  return;
24032
23919
  }
24033
- const source = filters.orphaned ? listOrphanedProjectMemories(configDir) : listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
24034
- const { entries, applied } = filterMemoryEntries(source, filters);
23920
+ const source2 = filters.orphaned ? listOrphanedProjectMemories(configDir) : listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
23921
+ const { entries, applied } = filterMemoryEntries(source2, filters);
24035
23922
  const filterNote = applied.length > 0 ? ` (filtered: ${applied.join(", ")})` : "";
24036
23923
  const content = entries.length === 0 ? applied.length > 0 ? `(no entries match [${applied.join(", ")}])` : "(empty \u2014 no persistent memory entries yet)" : `Governed persistent memory${filterNote}
24037
23924
 
24038
23925
  ` + entries.map((entry) => {
24039
23926
  const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
24040
- const source2 = [
23927
+ const source3 = [
24041
23928
  entry.source ?? "unknown",
24042
23929
  entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : "",
24043
23930
  entry.sourceProject ? `project ${entry.sourceProject}` : ""
24044
23931
  ].filter(Boolean).join(" \xB7 ");
24045
23932
  return `## ${entry.id.slice(0, 8)} \xB7 ${status} \xB7 scope:${entry.scope} \xB7 sensitivity:${entry.sensitivity}
24046
- Source: ${source2}
23933
+ Source: ${source3}
24047
23934
  ${entry.content}`;
24048
23935
  }).join("\n\n");
24049
23936
  this.send({
@@ -24153,7 +24040,7 @@ Backup saved: ${result.backupPath}` : "";
24153
24040
  return;
24154
24041
  }
24155
24042
  try {
24156
- const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-DGUOUUQW.js");
24043
+ const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
24157
24044
  const loaded = loadChatIndex2();
24158
24045
  if (!loaded || loaded.idx.chunks.length === 0) {
24159
24046
  this.send({ type: "memory_hits", query: q, hits: [], indexMissing: true });
@@ -24189,7 +24076,7 @@ Backup saved: ${result.backupPath}` : "";
24189
24076
  }
24190
24077
  async handleMemoryStatus() {
24191
24078
  try {
24192
- const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-DGUOUUQW.js");
24079
+ const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-RLVLQRRC.js");
24193
24080
  const s = getChatIndexStatus2();
24194
24081
  this.send({
24195
24082
  type: "memory_status",
@@ -24214,7 +24101,7 @@ Backup saved: ${result.backupPath}` : "";
24214
24101
  type: "info",
24215
24102
  message: full ? "\u{1F9E0} Rebuilding chat memory index (this may take a while on first run \u2014 ~117 MB embedder)." : "\u{1F9E0} Refreshing chat memory index (incremental)\u2026"
24216
24103
  });
24217
- const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-DGUOUUQW.js");
24104
+ const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
24218
24105
  const stats = await buildChatIndex2({
24219
24106
  full,
24220
24107
  onProgress: (p) => {
@@ -24372,9 +24259,9 @@ async function setupProxy(configProxy) {
24372
24259
  }
24373
24260
 
24374
24261
  // src/web/auth.ts
24375
- import { existsSync as existsSync31, readFileSync as readFileSync24, writeFileSync as writeFileSync6, mkdirSync as mkdirSync16, readdirSync as readdirSync14, copyFileSync } from "fs";
24376
- import { isAbsolute as isAbsolute3, join as join27, relative as relative5 } from "path";
24377
- import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
24262
+ import { existsSync as existsSync30, readFileSync as readFileSync21, mkdirSync as mkdirSync16, readdirSync as readdirSync14, openSync, closeSync, unlinkSync as unlinkSync8 } from "fs";
24263
+ import { isAbsolute as isAbsolute3, join as join24, relative as relative5 } from "path";
24264
+ import { createHmac, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2, pbkdf2Sync, pbkdf2 } from "crypto";
24378
24265
  var USERS_FILE = "users.json";
24379
24266
  var TOKEN_EXPIRY_HOURS = 24;
24380
24267
  var TOKEN_EXPIRY_MS = TOKEN_EXPIRY_HOURS * 3600 * 1e3;
@@ -24386,14 +24273,16 @@ var AuthManager = class {
24386
24273
  usersFile;
24387
24274
  baseDir;
24388
24275
  db;
24276
+ loadedRaw;
24389
24277
  constructor(baseDir) {
24390
24278
  this.baseDir = baseDir;
24391
- this.usersFile = join27(baseDir, USERS_FILE);
24279
+ this.usersFile = join24(baseDir, USERS_FILE);
24392
24280
  this.db = this.loadOrCreate();
24393
24281
  }
24394
24282
  // ── Public API ─────────────────────────────────────────────────
24395
24283
  /** Check if any users exist (first-run detection) */
24396
24284
  hasUsers() {
24285
+ this.refresh();
24397
24286
  return this.db.users.length > 0;
24398
24287
  }
24399
24288
  /** Check if auth is enabled (has users.json with at least 1 user) */
@@ -24402,6 +24291,18 @@ var AuthManager = class {
24402
24291
  }
24403
24292
  /** Register a new user. Returns error string or null on success. */
24404
24293
  register(username, password) {
24294
+ return this.registerPrepared(username, password);
24295
+ }
24296
+ async registerAsync(username, password) {
24297
+ if (typeof username !== "string" || typeof password !== "string" || password.length > 1024) return "Invalid username or password";
24298
+ if (!/^[a-z0-9_-]{2,32}$/i.test(username.trim()) || password.length < 4) return "Invalid username or password";
24299
+ const salt = randomBytes2(16).toString("hex");
24300
+ const hash = await hashPasswordAsync(password, salt);
24301
+ return this.registerPrepared(username, password, { salt, hash });
24302
+ }
24303
+ registerPrepared(username, password, prepared) {
24304
+ this.refresh();
24305
+ if (typeof username !== "string" || typeof password !== "string" || password.length > 1024) return "Invalid username or password";
24405
24306
  username = username.trim().toLowerCase();
24406
24307
  if (!username || username.length < 2 || username.length > 32) {
24407
24308
  return "Username must be 2-32 characters";
@@ -24415,11 +24316,12 @@ var AuthManager = class {
24415
24316
  if (this.db.users.find((u) => u.username === username)) {
24416
24317
  return "Username already exists";
24417
24318
  }
24418
- const salt = randomBytes(16).toString("hex");
24419
- const passwordHash = this.hashPassword(password, salt);
24420
- const dataDir = join27(USERS_DIR, username);
24421
- const fullDataDir = join27(this.baseDir, dataDir);
24422
- mkdirSync16(join27(fullDataDir, "history"), { recursive: true });
24319
+ const salt = prepared?.salt ?? randomBytes2(16).toString("hex");
24320
+ const passwordHash = prepared?.hash ?? this.hashPassword(password, salt);
24321
+ const dataDir = join24(USERS_DIR, username);
24322
+ const fullDataDir = join24(this.baseDir, dataDir);
24323
+ mkdirSync16(fullDataDir, { recursive: true, mode: 448 });
24324
+ mkdirSync16(join24(fullDataDir, "history"), { recursive: true, mode: 448 });
24423
24325
  const user = {
24424
24326
  username,
24425
24327
  passwordHash,
@@ -24442,6 +24344,8 @@ var AuthManager = class {
24442
24344
  * resets the counter.
24443
24345
  */
24444
24346
  login(username, password) {
24347
+ this.refresh();
24348
+ if (!validCredentials(username, password)) return null;
24445
24349
  username = username.trim().toLowerCase();
24446
24350
  const lockState = this.getLockState(username);
24447
24351
  if (lockState.locked) return null;
@@ -24454,30 +24358,62 @@ var AuthManager = class {
24454
24358
  const hash = isLegacy ? this.hashPasswordLegacy(password, user.salt) : this.hashPassword(password, user.salt);
24455
24359
  const a = Buffer.from(hash, "utf-8");
24456
24360
  const b = Buffer.from(user.passwordHash, "utf-8");
24457
- if (a.length !== b.length || !timingSafeEqual(a, b)) {
24361
+ if (a.length !== b.length || !timingSafeEqual2(a, b)) {
24458
24362
  this.recordFailedLogin(username);
24459
24363
  return null;
24460
24364
  }
24461
24365
  if (isLegacy) {
24462
- const newSalt = randomBytes(16).toString("hex");
24366
+ const newSalt = randomBytes2(16).toString("hex");
24463
24367
  user.passwordHash = this.hashPassword(password, newSalt);
24464
24368
  user.salt = newSalt;
24465
24369
  user.hashVersion = 2;
24466
24370
  this.save();
24467
24371
  }
24468
- loginAttempts.delete(username);
24372
+ loginAttempts.delete(this.attemptKey(username));
24469
24373
  return this.createToken(username);
24470
24374
  }
24471
24375
  /**
24472
24376
  * Returns current lockout state for a username and lazily expires it.
24473
24377
  * Exposed (read-only) for tests and the `aicli user` CLI status output.
24474
24378
  */
24379
+ async loginAsync(username, password) {
24380
+ this.refresh();
24381
+ if (!validCredentials(username, password)) return null;
24382
+ username = username.trim().toLowerCase();
24383
+ if (this.getLockState(username).locked) return null;
24384
+ const user = this.db.users.find((u) => u.username === username);
24385
+ if (!user) {
24386
+ this.recordFailedLogin(username);
24387
+ return null;
24388
+ }
24389
+ const snapshot = JSON.stringify(user);
24390
+ const legacy = !user.hashVersion || user.hashVersion < 2;
24391
+ const hash = legacy ? this.hashPasswordLegacy(password, user.salt) : await hashPasswordAsync(password, user.salt);
24392
+ const salt = legacy ? randomBytes2(16).toString("hex") : user.salt;
24393
+ const upgraded = legacy ? await hashPasswordAsync(password, salt) : hash;
24394
+ this.refresh();
24395
+ const current = this.db.users.find((u) => u.username === username);
24396
+ if (!current || JSON.stringify(current) !== snapshot || this.getLockState(username).locked) return null;
24397
+ const a = Buffer.from(hash), b = Buffer.from(current.passwordHash);
24398
+ if (a.length !== b.length || !timingSafeEqual2(a, b)) {
24399
+ this.recordFailedLogin(username);
24400
+ return null;
24401
+ }
24402
+ if (legacy) {
24403
+ current.salt = salt;
24404
+ current.passwordHash = upgraded;
24405
+ current.hashVersion = 2;
24406
+ this.save();
24407
+ }
24408
+ loginAttempts.delete(this.attemptKey(username));
24409
+ return this.createToken(username);
24410
+ }
24475
24411
  getLockState(username) {
24476
24412
  username = username.trim().toLowerCase();
24477
- const state2 = loginAttempts.get(username);
24413
+ const state2 = loginAttempts.get(this.attemptKey(username));
24478
24414
  if (!state2) return { locked: false, remainingMs: 0, fails: 0 };
24479
- if (state2.lockedUntil > 0 && Date.now() >= state2.lockedUntil) {
24480
- loginAttempts.delete(username);
24415
+ if (Date.now() >= state2.expiresAt) {
24416
+ loginAttempts.delete(this.attemptKey(username));
24481
24417
  return { locked: false, remainingMs: 0, fails: 0 };
24482
24418
  }
24483
24419
  if (state2.lockedUntil > 0) {
@@ -24485,8 +24421,15 @@ var AuthManager = class {
24485
24421
  }
24486
24422
  return { locked: false, remainingMs: 0, fails: state2.fails };
24487
24423
  }
24424
+ attemptKey(username) {
24425
+ return this.usersFile + "\0" + username;
24426
+ }
24488
24427
  recordFailedLogin(username) {
24489
- const state2 = loginAttempts.get(username) ?? { fails: 0, lockedUntil: 0 };
24428
+ username = this.attemptKey(username);
24429
+ const now = Date.now();
24430
+ for (const [key, value] of loginAttempts) if (value.expiresAt <= now) loginAttempts.delete(key);
24431
+ if (!loginAttempts.has(username) && loginAttempts.size >= 1e4) loginAttempts.delete(loginAttempts.keys().next().value);
24432
+ const state2 = loginAttempts.get(username) ?? { fails: 0, lockedUntil: 0, expiresAt: now + LOGIN_LOCKOUT_MS };
24490
24433
  state2.fails += 1;
24491
24434
  if (state2.fails >= LOGIN_MAX_FAILS) {
24492
24435
  state2.lockedUntil = Date.now() + LOGIN_LOCKOUT_MS;
@@ -24496,17 +24439,19 @@ var AuthManager = class {
24496
24439
  /** Verify a token. Returns username or null. */
24497
24440
  verifyToken(token) {
24498
24441
  try {
24442
+ this.refresh();
24443
+ if (typeof token !== "string" || token.length > 4096) return null;
24499
24444
  const parts = token.split(".");
24500
24445
  if (parts.length !== 2) return null;
24501
24446
  const [payloadB64, signature] = parts;
24502
24447
  const expectedSig = this.sign(payloadB64);
24503
24448
  const sigBuf = Buffer.from(signature ?? "", "utf-8");
24504
24449
  const expBuf = Buffer.from(expectedSig, "utf-8");
24505
- if (sigBuf.length !== expBuf.length || !timingSafeEqual(sigBuf, expBuf)) return null;
24450
+ if (sigBuf.length !== expBuf.length || !timingSafeEqual2(sigBuf, expBuf)) return null;
24506
24451
  const payload = JSON.parse(
24507
24452
  Buffer.from(payloadB64, "base64url").toString("utf-8")
24508
24453
  );
24509
- if (Date.now() > payload.exp) return null;
24454
+ if (!Number.isFinite(payload.exp) || Date.now() >= payload.exp) return null;
24510
24455
  const user = this.db.users.find((u) => u.username === payload.username);
24511
24456
  if (!user) return null;
24512
24457
  if (user.tokensRevokedBefore && (!payload.iat || payload.iat < user.tokensRevokedBefore)) {
@@ -24523,6 +24468,7 @@ var AuthManager = class {
24523
24468
  * v0.4.114). Returns true if the user existed.
24524
24469
  */
24525
24470
  logoutAll(username) {
24471
+ this.refresh();
24526
24472
  username = username.trim().toLowerCase();
24527
24473
  const user = this.db.users.find((u) => u.username === username);
24528
24474
  if (!user) return false;
@@ -24532,16 +24478,19 @@ var AuthManager = class {
24532
24478
  }
24533
24479
  /** Get user's data directory (absolute path) */
24534
24480
  getUserDataDir(username) {
24481
+ this.refresh();
24535
24482
  const user = this.db.users.find((u) => u.username === username);
24536
24483
  if (!user) throw new Error(`User not found: ${username}`);
24537
- return join27(this.baseDir, user.dataDir);
24484
+ return join24(this.baseDir, user.dataDir);
24538
24485
  }
24539
24486
  /** List all usernames */
24540
24487
  listUsers() {
24488
+ this.refresh();
24541
24489
  return this.db.users.map((u) => u.username);
24542
24490
  }
24543
24491
  /** Delete a user. Returns error string or null on success. */
24544
24492
  deleteUser(username) {
24493
+ this.refresh();
24545
24494
  username = username.trim().toLowerCase();
24546
24495
  const idx = this.db.users.findIndex((u) => u.username === username);
24547
24496
  if (idx === -1) return `User '${username}' not found`;
@@ -24551,13 +24500,14 @@ var AuthManager = class {
24551
24500
  }
24552
24501
  /** Reset a user's password. Returns error string or null on success. */
24553
24502
  resetPassword(username, newPassword) {
24503
+ this.refresh();
24554
24504
  username = username.trim().toLowerCase();
24555
24505
  const user = this.db.users.find((u) => u.username === username);
24556
24506
  if (!user) return `User '${username}' not found`;
24557
- if (!newPassword || newPassword.length < 4) {
24507
+ if (typeof newPassword !== "string" || newPassword.length > 1024 || newPassword.length < 4) {
24558
24508
  return "Password must be at least 4 characters";
24559
24509
  }
24560
- const salt = randomBytes(16).toString("hex");
24510
+ const salt = randomBytes2(16).toString("hex");
24561
24511
  user.passwordHash = this.hashPassword(newPassword, salt);
24562
24512
  user.salt = salt;
24563
24513
  user.hashVersion = 2;
@@ -24570,30 +24520,30 @@ var AuthManager = class {
24570
24520
  const err = this.register(username, password);
24571
24521
  if (err) return err;
24572
24522
  const userDir = this.getUserDataDir(username);
24573
- const globalConfig = join27(this.baseDir, "config.json");
24574
- if (existsSync31(globalConfig)) {
24523
+ const globalConfig = join24(this.baseDir, "config.json");
24524
+ if (existsSync30(globalConfig)) {
24575
24525
  try {
24576
- const content = readFileSync24(globalConfig, "utf-8");
24577
- writeFileSync6(join27(userDir, "config.json"), content, "utf-8");
24526
+ const content = readFileSync21(globalConfig, "utf-8");
24527
+ atomicWriteFileSync(join24(userDir, "config.json"), content, { mode: 384 });
24578
24528
  } catch {
24579
24529
  }
24580
24530
  }
24581
- const globalMemory = join27(this.baseDir, "memory.md");
24582
- if (existsSync31(globalMemory)) {
24531
+ const globalMemory = join24(this.baseDir, "memory.md");
24532
+ if (existsSync30(globalMemory)) {
24583
24533
  try {
24584
- const content = readFileSync24(globalMemory, "utf-8");
24585
- writeFileSync6(join27(userDir, "memory.md"), content, "utf-8");
24534
+ const content = readFileSync21(globalMemory, "utf-8");
24535
+ atomicWriteFileSync(join24(userDir, "memory.md"), content, { mode: 384 });
24586
24536
  } catch {
24587
24537
  }
24588
24538
  }
24589
- const globalHistory = join27(this.baseDir, "history");
24590
- if (existsSync31(globalHistory)) {
24539
+ const globalHistory = join24(this.baseDir, "history");
24540
+ if (existsSync30(globalHistory)) {
24591
24541
  try {
24592
24542
  const files = readdirSync14(globalHistory).filter((f) => f.endsWith(".json"));
24593
- const userHistory = join27(userDir, "history");
24543
+ const userHistory = join24(userDir, "history");
24594
24544
  for (const f of files) {
24595
24545
  try {
24596
- copyFileSync(join27(globalHistory, f), join27(userHistory, f));
24546
+ atomicWriteFileSync(join24(userHistory, f), readFileSync21(join24(globalHistory, f), "utf8"), { mode: 384 });
24597
24547
  } catch {
24598
24548
  }
24599
24549
  }
@@ -24603,13 +24553,22 @@ var AuthManager = class {
24603
24553
  return null;
24604
24554
  }
24605
24555
  // ── Private methods ────────────────────────────────────────────
24556
+ refresh() {
24557
+ const raw = readFileSync21(this.usersFile, "utf8");
24558
+ const parsed = JSON.parse(raw);
24559
+ if (!this.isValidUsersDB(parsed)) throw new Error("Authentication database is invalid; access refused");
24560
+ this.db = parsed;
24561
+ this.loadedRaw = raw;
24562
+ }
24606
24563
  loadOrCreate() {
24607
- if (existsSync31(this.usersFile)) {
24564
+ if (existsSync30(this.usersFile)) {
24608
24565
  try {
24609
- const parsed = JSON.parse(readFileSync24(this.usersFile, "utf-8"));
24566
+ const raw = readFileSync21(this.usersFile, "utf-8");
24567
+ const parsed = JSON.parse(raw);
24610
24568
  if (!this.isValidUsersDB(parsed)) {
24611
24569
  throw new Error("database structure is invalid");
24612
24570
  }
24571
+ this.loadedRaw = raw;
24613
24572
  return parsed;
24614
24573
  } catch (err) {
24615
24574
  const detail = err instanceof Error ? err.message : String(err);
@@ -24621,7 +24580,7 @@ var AuthManager = class {
24621
24580
  }
24622
24581
  const db = {
24623
24582
  version: 1,
24624
- secret: randomBytes(32).toString("hex"),
24583
+ secret: randomBytes2(32).toString("hex"),
24625
24584
  users: []
24626
24585
  };
24627
24586
  this.saveDB(db);
@@ -24640,7 +24599,7 @@ var AuthManager = class {
24640
24599
  if (typeof record.username !== "string" || !/^[a-z0-9_-]{2,32}$/.test(record.username) || usernames.has(record.username) || typeof record.passwordHash !== "string" || record.passwordHash.length === 0 || typeof record.salt !== "string" || record.salt.length === 0 || typeof record.createdAt !== "string" || typeof record.dataDir !== "string" || record.dataDir.length === 0 || isAbsolute3(record.dataDir)) {
24641
24600
  return false;
24642
24601
  }
24643
- const dataDirFromBase = relative5(this.baseDir, join27(this.baseDir, record.dataDir));
24602
+ const dataDirFromBase = relative5(this.baseDir, join24(this.baseDir, record.dataDir));
24644
24603
  if (dataDirFromBase.startsWith("..") || isAbsolute3(dataDirFromBase)) return false;
24645
24604
  if (record.hashVersion !== void 0 && record.hashVersion !== 1 && record.hashVersion !== 2) return false;
24646
24605
  if (record.tokensRevokedBefore !== void 0 && !Number.isFinite(record.tokensRevokedBefore)) return false;
@@ -24652,8 +24611,19 @@ var AuthManager = class {
24652
24611
  this.saveDB(this.db);
24653
24612
  }
24654
24613
  saveDB(db) {
24655
- mkdirSync16(this.baseDir, { recursive: true });
24656
- atomicWriteFileSync(this.usersFile, JSON.stringify(db, null, 2));
24614
+ mkdirSync16(this.baseDir, { recursive: true, mode: 448 });
24615
+ const lock = this.usersFile + ".lock";
24616
+ const fd = openSync(lock, "wx", 384);
24617
+ try {
24618
+ const current = existsSync30(this.usersFile) ? readFileSync21(this.usersFile, "utf8") : void 0;
24619
+ if (current !== this.loadedRaw) throw new Error("Authentication database changed concurrently; retry the operation");
24620
+ const raw = JSON.stringify(db, null, 2);
24621
+ atomicWriteFileSync(this.usersFile, raw, { mode: 384 });
24622
+ this.loadedRaw = raw;
24623
+ } finally {
24624
+ closeSync(fd);
24625
+ unlinkSync8(lock);
24626
+ }
24657
24627
  }
24658
24628
  /** Legacy hash — kept only for migrating old users (v0.2.x) */
24659
24629
  hashPasswordLegacy(password, salt) {
@@ -24677,6 +24647,19 @@ var AuthManager = class {
24677
24647
  return createHmac("sha256", this.db.secret).update(data).digest("base64url");
24678
24648
  }
24679
24649
  };
24650
+ function validCredentials(username, password) {
24651
+ return typeof username === "string" && /^[a-z0-9_-]{2,32}$/i.test(username.trim()) && typeof password === "string" && password.length > 0 && password.length <= 1024;
24652
+ }
24653
+ var activeHashes = 0;
24654
+ async function hashPasswordAsync(password, salt) {
24655
+ if (activeHashes >= 4) throw new Error("Authentication capacity reached; try again later");
24656
+ activeHashes++;
24657
+ try {
24658
+ return await new Promise((resolve13, reject) => pbkdf2(password, salt, 1e5, 64, "sha512", (error, hash) => error ? reject(error) : resolve13(hash.toString("hex"))));
24659
+ } finally {
24660
+ activeHashes--;
24661
+ }
24662
+ }
24680
24663
 
24681
24664
  // src/web/server.ts
24682
24665
  function getModuleDir() {
@@ -24696,6 +24679,10 @@ async function startWebServer(options = {}) {
24696
24679
  console.log(`\u{1F916} ai-cli Web UI v${VERSION}`);
24697
24680
  console.log(` Initializing...`);
24698
24681
  const config = new ConfigManager();
24682
+ const authManager = new AuthManager(config.getConfigDir());
24683
+ if (!authManager.isEnabled() && !["localhost", "127.0.0.1", "::1"].includes(host)) {
24684
+ throw new Error("Network listening requires authentication. Create a user with aicli user create first.");
24685
+ }
24699
24686
  await setupProxy(config.get("proxy"));
24700
24687
  const providers = new ProviderRegistry();
24701
24688
  const sessions = new SessionManager(config);
@@ -24729,7 +24716,7 @@ async function startWebServer(options = {}) {
24729
24716
  const projectMcpResolved = mcpEnabled ? resolveProjectMcpPath() : null;
24730
24717
  let projectMcpServers = {};
24731
24718
  if (projectMcpResolved) {
24732
- const { checkTrust } = await import("./project-trust-MUG325AW.js");
24719
+ const { checkTrust } = await import("./project-trust-6CJB5ZMX.js");
24733
24720
  const verdict = checkTrust(config.getConfigDir(), projectMcpResolved);
24734
24721
  if (verdict.trusted) {
24735
24722
  projectMcpServers = loadProjectMcpConfig() ?? {};
@@ -24756,8 +24743,8 @@ async function startWebServer(options = {}) {
24756
24743
  }
24757
24744
  }
24758
24745
  let skillManager = null;
24759
- const skillsDir = join28(config.getConfigDir(), SKILLS_DIR_NAME);
24760
- if (existsSync32(skillsDir)) {
24746
+ const skillsDir = join25(config.getConfigDir(), SKILLS_DIR_NAME);
24747
+ if (existsSync31(skillsDir)) {
24761
24748
  skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
24762
24749
  skillManager.loadSkills(pluginAssets.skillDirs);
24763
24750
  const count = skillManager.listSkills().length;
@@ -24776,6 +24763,16 @@ async function startWebServer(options = {}) {
24776
24763
  skillManager
24777
24764
  };
24778
24765
  const app = express();
24766
+ const access = new WebAccess(host, options.externalOrigin);
24767
+ const authLimits = new AuthRateLimiter();
24768
+ app.use((req, res, next) => {
24769
+ if (!access.accepts(req)) {
24770
+ res.status(403).json({ error: "Untrusted request origin or host" });
24771
+ return;
24772
+ }
24773
+ res.locals.externalSecure = !!options.externalOrigin?.startsWith("https://") && req.headers.host === new URL(options.externalOrigin).host;
24774
+ next();
24775
+ });
24779
24776
  app.use((_req, res, next) => {
24780
24777
  res.setHeader(
24781
24778
  "Content-Security-Policy",
@@ -24804,19 +24801,23 @@ async function startWebServer(options = {}) {
24804
24801
  const WS_MSG_RATE_PER_SEC = 30;
24805
24802
  const WS_MSG_BURST = 60;
24806
24803
  const WS_PREAUTH_TIMEOUT_MS = 3e4;
24807
- const wss = new WebSocketServer({ server, maxPayload: WS_MAX_PAYLOAD });
24808
- const authManager = new AuthManager(config.getConfigDir());
24804
+ const wss = new WebSocketServer({ server, maxPayload: WS_MAX_PAYLOAD, verifyClient: ({ req }) => {
24805
+ if (!access.accepts(req) || wss.clients.size >= 128) return false;
24806
+ if (req.headers.origin) return true;
24807
+ const cookie = parseCookie(req.headers.cookie ?? "");
24808
+ return equalToken(cookie.aicli_local, access.localToken) || !!authManager.verifyToken(cookie.aicli_token ?? "");
24809
+ } });
24809
24810
  if (authManager.isEnabled()) {
24810
24811
  console.log(` Auth: ${authManager.listUsers().length} user(s) registered`);
24811
24812
  } else {
24812
- console.log(` Auth: disabled (no users registered)`);
24813
- if (host === "0.0.0.0" || host === "::") {
24814
- console.log(" WARNING: Web UI is reachable from the network without authentication.");
24815
- console.log(" Create a user first for LAN/team use: aicli user create <name>");
24816
- }
24813
+ console.log(" Auth: local launch token (no users registered)");
24817
24814
  }
24818
24815
  const requireAuth = (req, res, next) => {
24819
- if (!authManager.isEnabled()) return next();
24816
+ if (!authManager.isEnabled()) {
24817
+ if (equalToken(parseCookie(req.headers.cookie ?? "").aicli_local, access.localToken)) return next();
24818
+ res.status(401).json({ error: "Open the launch URL printed by aicli to authorize this browser" });
24819
+ return;
24820
+ }
24820
24821
  const authHeader = req.headers.authorization;
24821
24822
  const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
24822
24823
  const cookieToken = parseCookie(req.headers.cookie ?? "")["aicli_token"];
@@ -24834,19 +24835,20 @@ async function startWebServer(options = {}) {
24834
24835
  next();
24835
24836
  };
24836
24837
  const moduleDir = getModuleDir();
24837
- let clientDir = join28(moduleDir, "web", "client");
24838
- if (!existsSync32(clientDir)) {
24839
- clientDir = join28(moduleDir, "client");
24838
+ let clientDir = join25(moduleDir, "web", "client");
24839
+ if (!existsSync31(clientDir)) {
24840
+ clientDir = join25(moduleDir, "client");
24840
24841
  }
24841
- if (!existsSync32(clientDir)) {
24842
- clientDir = join28(moduleDir, "..", "..", "src", "web", "client");
24842
+ if (!existsSync31(clientDir)) {
24843
+ clientDir = join25(moduleDir, "..", "..", "src", "web", "client");
24843
24844
  }
24844
- if (!existsSync32(clientDir)) {
24845
- clientDir = join28(process.cwd(), "src", "web", "client");
24845
+ if (!existsSync31(clientDir)) {
24846
+ clientDir = join25(process.cwd(), "src", "web", "client");
24846
24847
  }
24847
24848
  console.log(` Static files: ${clientDir}`);
24848
24849
  const PRECOMPRESSED_TYPES = {
24849
24850
  ".js": "text/javascript; charset=utf-8",
24851
+ ".mjs": "text/javascript; charset=utf-8",
24850
24852
  ".css": "text/css; charset=utf-8",
24851
24853
  ".html": "text/html; charset=utf-8",
24852
24854
  ".json": "application/json; charset=utf-8",
@@ -24861,14 +24863,14 @@ async function startWebServer(options = {}) {
24861
24863
  const encoding = /\bbr\b/.test(accept) ? "br" : /\bgzip\b/.test(accept) ? "gzip" : null;
24862
24864
  if (!encoding) return next();
24863
24865
  const target = resolvePath(clientDir, `.${req.path}`) + (encoding === "br" ? ".br" : ".gz");
24864
- if (!target.startsWith(clientDir) || !existsSync32(target)) return next();
24866
+ if (!target.startsWith(clientDir) || !existsSync31(target)) return next();
24865
24867
  res.setHeader("Content-Type", contentType);
24866
24868
  res.setHeader("Content-Encoding", encoding);
24867
24869
  res.setHeader("Vary", "Accept-Encoding");
24868
24870
  if (req.path.startsWith("/vendor/")) res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
24869
24871
  createReadStream(target).pipe(res);
24870
24872
  });
24871
- app.use("/vendor", express.static(join28(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
24873
+ app.use("/vendor", express.static(join25(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
24872
24874
  app.use(express.static(clientDir));
24873
24875
  app.get("/api/status", (_req, res) => {
24874
24876
  res.json({
@@ -24887,9 +24889,28 @@ async function startWebServer(options = {}) {
24887
24889
  }
24888
24890
  });
24889
24891
  });
24890
- app.use(express.json());
24891
- app.post("/api/auth/register", (req, res) => {
24892
+ app.use(express.json({ limit: "16kb" }));
24893
+ app.post("/api/auth/bootstrap", (req, res) => {
24894
+ if (!equalToken(req.body?.token, access.localToken)) {
24895
+ res.status(403).json({ error: "Invalid launch token" });
24896
+ return;
24897
+ }
24898
+ res.cookie("aicli_local", access.localToken, { httpOnly: true, sameSite: "strict", secure: isHttpsRequest(req) });
24899
+ res.json({ success: true });
24900
+ });
24901
+ app.use("/api/auth", (req, res, next) => {
24902
+ if (!authLimits.take(req.socket.remoteAddress ?? "unknown")) {
24903
+ res.status(429).json({ error: "Too many authentication attempts; try again later" });
24904
+ return;
24905
+ }
24906
+ next();
24907
+ });
24908
+ app.post("/api/auth/register", async (req, res) => {
24892
24909
  const firstRun = !authManager.hasUsers();
24910
+ if (firstRun && !equalToken(parseCookie(req.headers.cookie ?? "").aicli_local, access.localToken)) {
24911
+ res.status(403).json({ error: "Open the launch URL before enabling authentication" });
24912
+ return;
24913
+ }
24893
24914
  if (!firstRun) {
24894
24915
  const authHeader = req.headers.authorization;
24895
24916
  const token2 = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
@@ -24905,23 +24926,24 @@ async function startWebServer(options = {}) {
24905
24926
  res.status(400).json({ error: "Username and password required" });
24906
24927
  return;
24907
24928
  }
24908
- const err = authManager.register(username, password);
24929
+ const err = await authManager.registerAsync(username, password);
24909
24930
  if (err) {
24910
24931
  res.status(400).json({ error: err });
24911
24932
  return;
24912
24933
  }
24913
- const token = authManager.login(username, password);
24934
+ const token = await authManager.loginAsync(username, password);
24935
+ invalidateConnections();
24914
24936
  console.log(` \u2713 User registered via API: ${username}${firstRun ? " (first-run)" : ""}`);
24915
24937
  res.cookie("aicli_token", token, authCookieOptions(req));
24916
24938
  res.json({ success: true, username });
24917
24939
  });
24918
- app.post("/api/auth/login", (req, res) => {
24940
+ app.post("/api/auth/login", async (req, res) => {
24919
24941
  const { username, password } = req.body ?? {};
24920
24942
  if (!username || !password) {
24921
24943
  res.status(400).json({ error: "Username and password required" });
24922
24944
  return;
24923
24945
  }
24924
- const token = authManager.login(username, password);
24946
+ const token = await authManager.loginAsync(username, password);
24925
24947
  if (!token) {
24926
24948
  res.status(401).json({ error: "Invalid username or password" });
24927
24949
  return;
@@ -24930,13 +24952,16 @@ async function startWebServer(options = {}) {
24930
24952
  res.json({ success: true, username });
24931
24953
  });
24932
24954
  app.post("/api/auth/logout", (req, res) => {
24955
+ const user = authManager.verifyToken(parseCookie(req.headers.cookie ?? "").aicli_token ?? "");
24956
+ if (user) authManager.logoutAll(user);
24957
+ invalidateConnections();
24933
24958
  res.clearCookie("aicli_token", clearAuthCookieOptions(req));
24934
24959
  res.json({ success: true });
24935
24960
  });
24936
24961
  app.get("/api/files", requireAuth, (req, res) => {
24937
24962
  const cwd = process.cwd();
24938
24963
  const prefix = req.query.prefix || "";
24939
- const targetDir = join28(cwd, prefix);
24964
+ const targetDir = join25(cwd, prefix);
24940
24965
  try {
24941
24966
  const canonicalTarget = realpathSync3(resolve12(targetDir));
24942
24967
  const canonicalCwd = realpathSync3(resolve12(cwd));
@@ -24953,7 +24978,7 @@ async function startWebServer(options = {}) {
24953
24978
  const entries = readdirSync15(targetDir, { withFileTypes: true });
24954
24979
  const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
24955
24980
  name: e.name,
24956
- path: relative6(cwd, join28(targetDir, e.name)).replace(/\\/g, "/"),
24981
+ path: relative6(cwd, join25(targetDir, e.name)).replace(/\\/g, "/"),
24957
24982
  isDir: e.isDirectory()
24958
24983
  }));
24959
24984
  res.json({ files });
@@ -24993,8 +25018,8 @@ async function startWebServer(options = {}) {
24993
25018
  try {
24994
25019
  const authUser = req._authUser;
24995
25020
  const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
24996
- const filePath = join28(histDir, `${id}.json`);
24997
- if (!existsSync32(filePath)) {
25021
+ const filePath = join25(histDir, `${id}.json`);
25022
+ if (!existsSync31(filePath)) {
24998
25023
  res.status(404).json({ error: "Session not found" });
24999
25024
  return;
25000
25025
  }
@@ -25009,7 +25034,7 @@ async function startWebServer(options = {}) {
25009
25034
  res.status(404).json({ error: "Session not found" });
25010
25035
  return;
25011
25036
  }
25012
- const data = JSON.parse(readFileSync25(filePath, "utf-8"));
25037
+ const data = JSON.parse(readFileSync22(filePath, "utf-8"));
25013
25038
  res.json({ session: data });
25014
25039
  } catch (err) {
25015
25040
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
@@ -25022,9 +25047,10 @@ async function startWebServer(options = {}) {
25022
25047
  return;
25023
25048
  }
25024
25049
  const cwd = process.cwd();
25025
- const fullPath = resolve12(join28(cwd, filePath));
25050
+ const fullPath = resolve12(join25(cwd, filePath));
25026
25051
  try {
25027
25052
  const canonicalFull = realpathSync3(fullPath);
25053
+ assertReadablePath(canonicalFull);
25028
25054
  const canonicalCwd = realpathSync3(resolve12(cwd));
25029
25055
  if (!canonicalFull.startsWith(canonicalCwd + sep5) && canonicalFull !== canonicalCwd) {
25030
25056
  res.json({ error: "Access denied" });
@@ -25040,7 +25066,7 @@ async function startWebServer(options = {}) {
25040
25066
  res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
25041
25067
  return;
25042
25068
  }
25043
- const content = readFileSync25(fullPath, "utf-8");
25069
+ const content = readFileSync22(fullPath, "utf-8");
25044
25070
  res.json({ content, size: stat.size });
25045
25071
  } catch {
25046
25072
  res.json({ error: "Cannot read file" });
@@ -25052,6 +25078,9 @@ async function startWebServer(options = {}) {
25052
25078
  if (cached) return cached;
25053
25079
  const userDataDir = authManager.getUserDataDir(username);
25054
25080
  const userConfig = new ConfigManager(userDataDir);
25081
+ for (const key of ["defaultPermissionProfile", "allowedPermissionProfiles", "permissionProfiles", "permissionRules", "defaultPermission", "networkPolicy"]) {
25082
+ userConfig.setTransient(key, structuredClone(config.get(key)));
25083
+ }
25055
25084
  if (userConfig.isFirstRun()) {
25056
25085
  userConfig.setTransient("defaultProvider", config.get("defaultProvider"));
25057
25086
  userConfig.setTransient("defaultModels", config.get("defaultModels"));
@@ -25062,7 +25091,7 @@ async function startWebServer(options = {}) {
25062
25091
  // 每用户各自的 config 目录 → 各自的 cost-history.json
25063
25092
  costTracker: new CostTracker(userConfig.getConfigDir()),
25064
25093
  providers,
25065
- // Shared — providers re-read API keys per call
25094
+ // Host-owned service credentials; shared explicitly, never per-user keys.
25066
25095
  sessions: userSessions,
25067
25096
  toolRegistry,
25068
25097
  // Shared
@@ -25075,6 +25104,21 @@ async function startWebServer(options = {}) {
25075
25104
  return userShared;
25076
25105
  }
25077
25106
  const handlers = /* @__PURE__ */ new Map();
25107
+ const validators = /* @__PURE__ */ new Set();
25108
+ function invalidateConnections() {
25109
+ for (const validate of validators) validate();
25110
+ }
25111
+ let authRefreshTimer;
25112
+ server.on("listening", () => {
25113
+ if (!authRefreshTimer) {
25114
+ authRefreshTimer = setInterval(invalidateConnections, 1e3);
25115
+ authRefreshTimer.unref();
25116
+ }
25117
+ });
25118
+ server.on("close", () => {
25119
+ clearInterval(authRefreshTimer);
25120
+ authRefreshTimer = void 0;
25121
+ });
25078
25122
  wss.on("connection", (ws, req) => {
25079
25123
  const urlStr = req.url ?? "";
25080
25124
  const qMark = urlStr.indexOf("?");
@@ -25082,6 +25126,8 @@ async function startWebServer(options = {}) {
25082
25126
  const tabId = params.get("tabId") || `tab-${Date.now()}`;
25083
25127
  const cookies = parseCookie(req.headers.cookie ?? "");
25084
25128
  const token = cookies["aicli_token"] || "";
25129
+ let activeToken = token;
25130
+ const localAuthorized = equalToken(cookies.aicli_local, access.localToken);
25085
25131
  let tokens = WS_MSG_BURST;
25086
25132
  let lastRefill = Date.now();
25087
25133
  const takeToken = () => {
@@ -25141,7 +25187,7 @@ async function startWebServer(options = {}) {
25141
25187
  authenticatedUser = authManager.verifyToken(token);
25142
25188
  }
25143
25189
  const authRequired = authManager.isEnabled();
25144
- if (!authRequired) {
25190
+ if (!authRequired && localAuthorized) {
25145
25191
  console.log(` \u2713 Tab connected: ${tabId.slice(0, 12)} (no auth) (total: ${handlers.size + 1})`);
25146
25192
  handler = attachHandler(shared);
25147
25193
  clearPreAuthTimer();
@@ -25159,7 +25205,26 @@ async function startWebServer(options = {}) {
25159
25205
  }));
25160
25206
  }
25161
25207
  }
25208
+ const validate = () => {
25209
+ if (!handler) return;
25210
+ let valid = false;
25211
+ try {
25212
+ valid = authManager.isEnabled() ? !!activeToken && authManager.verifyToken(activeToken) === authenticatedUser : localAuthorized && !authenticatedUser;
25213
+ } catch {
25214
+ }
25215
+ if (!valid) {
25216
+ handler.onDisconnect(false);
25217
+ if (handlers.get(handlerKey) === handler) handlers.delete(handlerKey);
25218
+ handler = null;
25219
+ ws.close(1008, "Authentication changed; reconnect");
25220
+ }
25221
+ };
25222
+ validators.add(validate);
25223
+ ws.on("close", () => validators.delete(validate));
25224
+ let authBusy = false;
25162
25225
  ws.on("message", async (data) => {
25226
+ validate();
25227
+ if (ws.readyState !== ws.OPEN) return;
25163
25228
  if (!takeToken()) {
25164
25229
  if (ws.readyState === ws.OPEN) {
25165
25230
  try {
@@ -25179,57 +25244,73 @@ async function startWebServer(options = {}) {
25179
25244
  return;
25180
25245
  }
25181
25246
  if (parsed.type === "auth") {
25182
- const { action, username, password } = parsed;
25183
- if (action === "register") {
25184
- if (authManager.hasUsers() && !authenticatedUser) {
25185
- ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Registration is closed. Use `aicli user create` on the host to add users." }));
25247
+ if (authBusy) return;
25248
+ authBusy = true;
25249
+ try {
25250
+ if (!authLimits.take(req.socket.remoteAddress ?? "unknown")) {
25251
+ ws.close(1008, "Authentication rate limit");
25186
25252
  return;
25187
25253
  }
25188
- const err = authManager.register(username, password);
25189
- if (err) {
25190
- ws.send(JSON.stringify({ type: "auth_result", success: false, error: err }));
25254
+ const { action, password } = parsed;
25255
+ const username = typeof parsed.username === "string" ? parsed.username.trim().toLowerCase() : "";
25256
+ if (action === "register") {
25257
+ if (!authenticatedUser && (authManager.hasUsers() || !localAuthorized)) {
25258
+ ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Registration is closed. Use `aicli user create` on the host to add users." }));
25259
+ return;
25260
+ }
25261
+ const err = await authManager.registerAsync(username, password);
25262
+ if (err) {
25263
+ ws.send(JSON.stringify({ type: "auth_result", success: false, error: err }));
25264
+ return;
25265
+ }
25266
+ activeToken = await authManager.loginAsync(username, password) ?? "";
25267
+ if (ws.readyState !== ws.OPEN || authManager.verifyToken(activeToken) !== username) return;
25268
+ authenticatedUser = username;
25269
+ const userShared = getUserShared(username);
25270
+ handler = attachHandler(userShared);
25271
+ clearPreAuthTimer();
25272
+ invalidateConnections();
25273
+ console.log(` \u2713 User registered & connected: ${username} (tab: ${tabId.slice(0, 12)})`);
25274
+ ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
25191
25275
  return;
25192
25276
  }
25193
- authManager.login(username, password);
25194
- authenticatedUser = username;
25195
- const userShared = getUserShared(username);
25196
- handler = attachHandler(userShared);
25197
- clearPreAuthTimer();
25198
- console.log(` \u2713 User registered & connected: ${username} (tab: ${tabId.slice(0, 12)})`);
25199
- ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
25200
- return;
25201
- }
25202
- if (action === "token") {
25203
- const { token: clientToken } = parsed;
25204
- const verifiedUser = authManager.verifyToken(clientToken);
25205
- if (!verifiedUser) {
25206
- ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Invalid or expired token" }));
25277
+ if (action === "token") {
25278
+ const { token: clientToken } = parsed;
25279
+ const verifiedUser = authManager.verifyToken(clientToken);
25280
+ if (!verifiedUser) {
25281
+ ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Invalid or expired token" }));
25282
+ return;
25283
+ }
25284
+ activeToken = clientToken;
25285
+ authenticatedUser = verifiedUser;
25286
+ const userShared = getUserShared(verifiedUser);
25287
+ handler = attachHandler(userShared);
25288
+ clearPreAuthTimer();
25289
+ console.log(` \u2713 Token auth: ${verifiedUser} (tab: ${tabId.slice(0, 12)})`);
25290
+ ws.send(JSON.stringify({ type: "auth_result", success: true, username: verifiedUser }));
25207
25291
  return;
25208
25292
  }
25209
- authenticatedUser = verifiedUser;
25210
- const userShared = getUserShared(verifiedUser);
25211
- handler = attachHandler(userShared);
25212
- clearPreAuthTimer();
25213
- console.log(` \u2713 Token auth: ${verifiedUser} (tab: ${tabId.slice(0, 12)})`);
25214
- ws.send(JSON.stringify({ type: "auth_result", success: true, username: verifiedUser }));
25215
- return;
25216
- }
25217
- if (action === "login") {
25218
- const loginToken = authManager.login(username, password);
25219
- if (!loginToken) {
25220
- ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Invalid username or password" }));
25293
+ if (action === "login") {
25294
+ const loginToken = await authManager.loginAsync(username, password);
25295
+ if (!loginToken) {
25296
+ ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Invalid username or password" }));
25297
+ return;
25298
+ }
25299
+ if (ws.readyState !== ws.OPEN || authManager.verifyToken(loginToken) !== username) return;
25300
+ activeToken = loginToken;
25301
+ authenticatedUser = username;
25302
+ const userShared = getUserShared(username);
25303
+ handler = attachHandler(userShared);
25304
+ clearPreAuthTimer();
25305
+ console.log(` \u2713 User logged in: ${username} (tab: ${tabId.slice(0, 12)})`);
25306
+ ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
25221
25307
  return;
25222
25308
  }
25223
- authenticatedUser = username;
25224
- const userShared = getUserShared(username);
25225
- handler = attachHandler(userShared);
25226
- clearPreAuthTimer();
25227
- console.log(` \u2713 User logged in: ${username} (tab: ${tabId.slice(0, 12)})`);
25228
- ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
25309
+ ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Unknown auth action" }));
25229
25310
  return;
25311
+ } finally {
25312
+ authBusy = false;
25230
25313
  }
25231
- ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Unknown auth action" }));
25232
- return;
25233
25314
  }
25234
25315
  if (!handler) {
25235
25316
  if (ws.readyState === ws.OPEN) {
@@ -25275,13 +25356,15 @@ async function startWebServer(options = {}) {
25275
25356
  }
25276
25357
  });
25277
25358
  server.listen(actualPort, host, () => {
25278
- const url = `http://${host === "0.0.0.0" ? "localhost" : host}:${actualPort}`;
25359
+ actualPort = server.address().port;
25360
+ const address = host.includes(":") ? `[${host}]` : host === "0.0.0.0" ? "localhost" : host;
25361
+ const url = `http://${address}:${actualPort}` + (!authManager.isEnabled() ? `#token=${access.localToken}` : "");
25279
25362
  console.log(`
25280
25363
  \u{1F310} Web UI ready: ${url}`);
25281
25364
  if (actualPort !== port) console.log(` (port ${port} was in use, using ${actualPort})`);
25282
25365
  if (host === "0.0.0.0" || host === "::") {
25283
25366
  try {
25284
- const nets = networkInterfaces();
25367
+ const nets = networkInterfaces2();
25285
25368
  for (const name of Object.keys(nets)) {
25286
25369
  for (const net2 of nets[name] ?? []) {
25287
25370
  if (net2.family === "IPv4" && !net2.internal) {
@@ -25314,27 +25397,24 @@ function resolveProjectMcpPath() {
25314
25397
  const cwd = process.cwd();
25315
25398
  const gitRoot = getGitRoot(cwd);
25316
25399
  const projectRoot = gitRoot ?? cwd;
25317
- const configPath = join28(projectRoot, MCP_PROJECT_CONFIG_NAME);
25318
- return existsSync32(configPath) ? configPath : null;
25400
+ const configPath = join25(projectRoot, MCP_PROJECT_CONFIG_NAME);
25401
+ return existsSync31(configPath) ? configPath : null;
25319
25402
  }
25320
25403
  function loadProjectMcpConfig() {
25321
25404
  const cwd = process.cwd();
25322
25405
  const gitRoot = getGitRoot(cwd);
25323
25406
  const projectRoot = gitRoot ?? cwd;
25324
- const configPath = join28(projectRoot, MCP_PROJECT_CONFIG_NAME);
25325
- if (!existsSync32(configPath)) return null;
25407
+ const configPath = join25(projectRoot, MCP_PROJECT_CONFIG_NAME);
25408
+ if (!existsSync31(configPath)) return null;
25326
25409
  try {
25327
- const raw = JSON.parse(readFileSync25(configPath, "utf-8"));
25410
+ const raw = JSON.parse(readFileSync22(configPath, "utf-8"));
25328
25411
  return raw.mcpServers ?? raw;
25329
25412
  } catch {
25330
25413
  return null;
25331
25414
  }
25332
25415
  }
25333
25416
  function isHttpsRequest(req) {
25334
- if (req.secure) return true;
25335
- const forwardedProto = req.headers["x-forwarded-proto"];
25336
- const proto = Array.isArray(forwardedProto) ? forwardedProto[0] : forwardedProto;
25337
- return proto?.split(",")[0]?.trim().toLowerCase() === "https";
25417
+ return req.secure || req.res?.locals.externalSecure === true;
25338
25418
  }
25339
25419
  function authCookieOptions(req) {
25340
25420
  return {
@@ -25361,13 +25441,13 @@ function parseCookie(cookie) {
25361
25441
  }
25362
25442
  async function openBrowser(url) {
25363
25443
  try {
25364
- const { spawn: spawn7 } = await import("child_process");
25444
+ const { spawn: spawn6 } = await import("child_process");
25365
25445
  if (process.platform === "win32") {
25366
- spawn7("cmd", ["/c", "start", '""', url], { detached: true, stdio: "ignore" }).unref();
25446
+ spawn6("cmd", ["/c", "start", '""', url], { detached: true, stdio: "ignore" }).unref();
25367
25447
  } else if (process.platform === "darwin") {
25368
- spawn7("open", [url], { detached: true, stdio: "ignore" }).unref();
25448
+ spawn6("open", [url], { detached: true, stdio: "ignore" }).unref();
25369
25449
  } else {
25370
- spawn7("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
25450
+ spawn6("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
25371
25451
  }
25372
25452
  } catch {
25373
25453
  }