jinzd-ai-cli 0.4.306 → 0.4.308

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 (95) hide show
  1. package/README.md +1 -0
  2. package/THIRD_PARTY_NOTICES.md +3 -0
  3. package/dist/{agent-client-6GX6QQDU.js → agent-client-TIEAYZXV.js} +4 -1
  4. package/dist/{auth-PE3Z5OHS.js → auth-QTQNZ6IX.js} +2 -2
  5. package/dist/{batch-JLGWQ7HU.js → batch-S7XLEEOH.js} +3 -3
  6. package/dist/{chat-index-2D5GK2DB.js → chat-index-7CLR4CBG.js} +4 -3
  7. package/dist/{chat-index-DGUOUUQW.js → chat-index-RLVLQRRC.js} +3 -2
  8. package/dist/{chunk-JDT6UG6R.js → chunk-2DYQDLLF.js} +1 -1
  9. package/dist/{chunk-T6WQEQI4.js → chunk-2MNUMLN4.js} +1 -1
  10. package/dist/chunk-2XMUCC42.js +67 -0
  11. package/dist/{chunk-7SSCSSC6.js → chunk-3537CQ5M.js} +2 -2
  12. package/dist/{chunk-OAXLMF3U.js → chunk-3YVNDYOY.js} +1 -1
  13. package/dist/{chunk-RYQBBEMM.js → chunk-4PR5AVJ5.js} +115 -19
  14. package/dist/{chunk-GHWZVPUV.js → chunk-6JHACXB3.js} +7 -5
  15. package/dist/{chunk-WZWRA7R2.js → chunk-7OHXFTZO.js} +2 -2
  16. package/dist/{chunk-IUVDZVZ4.js → chunk-AXTLYOA7.js} +24 -17
  17. package/dist/{chunk-XJOK5353.js → chunk-B3MPYI4E.js} +2 -2
  18. package/dist/{chunk-3RDJEYF7.js → chunk-BGZN4K7Q.js} +4 -4
  19. package/dist/chunk-BMLQPSHP.js +69 -0
  20. package/dist/{chunk-NRSYBKWA.js → chunk-C7CKDFJS.js} +1 -1
  21. package/dist/{chunk-JFNBJHR6.js → chunk-E2PNXNKI.js} +4 -4
  22. package/dist/chunk-FVXNEZEW.js +161 -0
  23. package/dist/{chunk-YKJO3SI4.js → chunk-G6DVGQ4I.js} +1 -1
  24. package/dist/{chunk-AYLK4EGA.js → chunk-HC33C4U3.js} +18 -18
  25. package/dist/chunk-JJFJWD35.js +162 -0
  26. package/dist/{chunk-W6PLV3EY.js → chunk-JJNUASMJ.js} +263 -55
  27. package/dist/{chunk-NYNSYLFY.js → chunk-JL44HGZC.js} +1 -1
  28. package/dist/{chunk-NUJTNH76.js → chunk-LBWKMTT3.js} +1 -1
  29. package/dist/{chunk-LAP7TPPK.js → chunk-LR3TGZWQ.js} +2 -2
  30. package/dist/{chunk-WQ3OMEC4.js → chunk-M6HLYG33.js} +1 -1
  31. package/dist/{chunk-I4GQRMFV.js → chunk-M6YV5KSS.js} +6 -4
  32. package/dist/{chunk-K5SDDTN7.js → chunk-NLK45GUS.js} +1 -1
  33. package/dist/{chunk-KBDKKHXB.js → chunk-O6CGOVD4.js} +2 -2
  34. package/dist/chunk-QMFMST2T.js +39 -0
  35. package/dist/{chunk-MI4SFZGX.js → chunk-RMEJR3AW.js} +5 -2
  36. package/dist/{chunk-UFW4S3RU.js → chunk-RVMCY6DP.js} +1 -1
  37. package/dist/{chunk-LF67HWWR.js → chunk-RWRZVQCB.js} +1 -1
  38. package/dist/chunk-SXIJLSVF.js +41 -0
  39. package/dist/chunk-TDQW2YHK.js +65 -0
  40. package/dist/{chunk-VLDB2SVN.js → chunk-TPTFV3Y6.js} +383 -513
  41. package/dist/{chunk-45UVJXFQ.js → chunk-W2UOVGRC.js} +1 -1
  42. package/dist/{chunk-VXEF2SWG.js → chunk-WT252ZDV.js} +2 -2
  43. package/dist/{chunk-NLB7JZSO.js → chunk-WVEIUSXE.js} +246 -55
  44. package/dist/{chunk-OC2XHG5L.js → chunk-YBZM7PHI.js} +4 -4
  45. package/dist/{ci-6PHEL3C7.js → ci-5HONSUAS.js} +7 -7
  46. package/dist/{ci-format-AZYUP6BL.js → ci-format-URUH3ZZ5.js} +2 -2
  47. package/dist/{constants-CPNWIUGZ.js → constants-BLDBI7IG.js} +1 -1
  48. package/dist/{constants-AXBD2EEB.js → constants-FG27DQMS.js} +1 -1
  49. package/dist/doctor-cli-QQLMNJN7.js +18 -0
  50. package/dist/electron-server.js +1003 -829
  51. package/dist/file-checkpoint-64A2AXHE.js +7 -0
  52. package/dist/{file-checkpoint-NKBHGC7L.js → file-checkpoint-VVPUJNCI.js} +2 -1
  53. package/dist/{hub-DDYAWLF2.js → hub-GGFW3DCC.js} +3 -3
  54. package/dist/{hub-server-CBKCP5VD.js → hub-server-JLC7CNP7.js} +55 -17
  55. package/dist/index.js +42 -40
  56. package/dist/{indexer-P3FPRTPJ.js → indexer-GKIWSSRP.js} +3 -3
  57. package/dist/indexer-VRXDZ74A.js +10 -0
  58. package/dist/{persist-S77WRVLD.js → persist-ZZXRWFBX.js} +4 -4
  59. package/dist/{persistent-memory-XZISXRWT.js → persistent-memory-UNLCXUWH.js} +3 -3
  60. package/dist/{persistent-memory-6XYYOMSC.js → persistent-memory-YYWIN6BG.js} +3 -3
  61. package/dist/{pr-UY4ZCZNI.js → pr-YCFGPFI7.js} +7 -7
  62. package/dist/{project-trust-MUG325AW.js → project-trust-6CJB5ZMX.js} +1 -1
  63. package/dist/{project-trust-NKYHL3VZ.js → project-trust-KGG2BWKC.js} +1 -1
  64. package/dist/run-tests-GHY4BYDE.js +13 -0
  65. package/dist/run-tests-KRNMFXAN.js +12 -0
  66. package/dist/{semantic-7GM5M4MU.js → semantic-N227CLSC.js} +4 -4
  67. package/dist/{semantic-QOBAXEQ4.js → semantic-TRQAZSRY.js} +4 -4
  68. package/dist/{server-6IC33CJK.js → server-73UBHSFV.js} +16 -14
  69. package/dist/{server-DBHLVQVP.js → server-QRAWSG4Z.js} +231 -103
  70. package/dist/{store-SIKHCUYJ.js → store-2N6PPPBP.js} +2 -2
  71. package/dist/{store-CMSSWZF7.js → store-76VVFDXM.js} +2 -2
  72. package/dist/{task-orchestrator-RDDETDW2.js → task-orchestrator-GI3RGLZI.js} +15 -14
  73. package/dist/{usage-D473ZVIP.js → usage-GWTIGZF5.js} +4 -4
  74. package/dist/{vector-store-JAXWDZYW.js → vector-store-T2DVUMY4.js} +2 -2
  75. package/dist/{vector-store-PWIOQFTP.js → vector-store-V6UGEYWK.js} +2 -2
  76. package/dist/web/client/app.js +7 -0
  77. package/dist/web/client/app.js.br +0 -0
  78. package/dist/web/client/app.js.gz +0 -0
  79. package/dist/web/client/util.js +14 -23
  80. package/dist/web/client/util.js.br +0 -0
  81. package/dist/web/client/util.js.gz +0 -0
  82. package/dist/web/client/vendor/DOMPurify-LICENSE.txt +202 -0
  83. package/dist/web/client/vendor/purify.es.mjs +2722 -0
  84. package/dist/web/client/vendor/purify.es.mjs.br +0 -0
  85. package/dist/web/client/vendor/purify.es.mjs.gz +0 -0
  86. package/package.json +4 -2
  87. package/dist/chunk-4BKXL7SM.js +0 -98
  88. package/dist/chunk-6BUTA5VW.js +0 -38
  89. package/dist/chunk-IW3Q7AE5.js +0 -40
  90. package/dist/chunk-SKET65WZ.js +0 -96
  91. package/dist/doctor-cli-DDHHDMMJ.js +0 -18
  92. package/dist/file-checkpoint-CGH6OJVI.js +0 -6
  93. package/dist/indexer-FPKY7LCT.js +0 -10
  94. package/dist/run-tests-BAMLVLO6.js +0 -10
  95. package/dist/run-tests-UEE3OMLS.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-WZWRA7R2.js";
58
+ } from "./chunk-7OHXFTZO.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-NLB7JZSO.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-WVEIUSXE.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-JDT6UG6R.js";
125
+ } from "./chunk-2DYQDLLF.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";
@@ -650,8 +729,8 @@ ${err}`
650
729
  return changed;
651
730
  }
652
731
  save() {
653
- mkdirSync(this.configDir, { recursive: true });
654
- atomicWriteFileSync(this.configPath, JSON.stringify(this.config, null, 2));
732
+ mkdirSync(this.configDir, { recursive: true, mode: 448 });
733
+ atomicWriteFileSync(this.configPath, JSON.stringify(this.config, null, 2), { mode: 384 });
655
734
  }
656
735
  getApiKey(providerId) {
657
736
  const envKey = EnvLoader.getApiKey(providerId);
@@ -1293,6 +1372,7 @@ function getDangerLevel(toolName, args) {
1293
1372
  }
1294
1373
  if (toolName === "bash") {
1295
1374
  const cmd = String(args["command"] ?? "");
1375
+ if (/\bgit\s+(?:clean\b|reset\s+--hard\b|push\b[^\n]*(?:--force\b|\s-f\b))/i.test(cmd)) return "destructive";
1296
1376
  if (/\brm\s+[^\n]*(?:-\w*[rRfF]\w*|--recursive|--force)\b/.test(cmd)) return "destructive";
1297
1377
  if (/\brm\s+\S/.test(cmd)) return "destructive";
1298
1378
  if (/\brmdir\b|\bformat\b|\bmkfs\b|\bdd\s+if=|\bshred\b|\bfdisk\b|\bparted\b/.test(cmd)) return "destructive";
@@ -1308,8 +1388,10 @@ function getDangerLevel(toolName, args) {
1308
1388
  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
1389
  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
1390
  if (/\bcmd(\.exe)?\s+\/[ck]\b/i.test(cmd)) return "write";
1311
- return "safe";
1391
+ 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";
1392
+ return "write";
1312
1393
  }
1394
+ if (toolName === "run_tests") return "write";
1313
1395
  if (toolName === "write_file") return "write";
1314
1396
  if (toolName === "edit_file") return "write";
1315
1397
  if (toolName === "save_last_response") return "write";
@@ -1324,7 +1406,7 @@ function getDangerLevel(toolName, args) {
1324
1406
  if (toolName === "git_commit") return "write";
1325
1407
  if (toolName === "git_status" || toolName === "git_diff" || toolName === "git_log") return "safe";
1326
1408
  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";
1409
+ 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
1410
  return "write";
1329
1411
  }
1330
1412
  function schemaToJsonSchema(schema) {
@@ -4678,7 +4760,7 @@ function clearModelCache(configDir, providerId) {
4678
4760
  delete cache.providers[providerId];
4679
4761
  saveModelCache(configDir, cache);
4680
4762
  }
4681
- function normalizeLiveModels(models, fetchedAt, source = "live") {
4763
+ function normalizeLiveModels(models, fetchedAt, source2 = "live") {
4682
4764
  const seen = /* @__PURE__ */ new Set();
4683
4765
  const normalized = [];
4684
4766
  for (const model of models) {
@@ -4690,7 +4772,7 @@ function normalizeLiveModels(models, fetchedAt, source = "live") {
4690
4772
  contextWindow: model.contextWindow || LIVE_DEFAULT_CONTEXT,
4691
4773
  supportsStreaming: model.supportsStreaming ?? true,
4692
4774
  ...model.supportsThinking !== void 0 ? { supportsThinking: model.supportsThinking } : {},
4693
- source,
4775
+ source: source2,
4694
4776
  available: true,
4695
4777
  metadataUnknown: model.metadataUnknown ?? !model.contextWindow,
4696
4778
  ...fetchedAt ? { fetchedAt } : {}
@@ -5463,26 +5545,33 @@ var SessionManager = class {
5463
5545
  }
5464
5546
  /** P1-PERF-03: `force` 跳过脏检查(用于进程退出、fork 等必须落盘场景) */
5465
5547
  async save(force = false) {
5466
- if (!this._current) return;
5467
- if (this._current.messages.length === 0) return;
5468
- if (!force && !this._current.dirty) return;
5548
+ const session = this._current;
5549
+ if (!session) return;
5550
+ if (session.messages.length === 0) return;
5551
+ if (!force && !session.dirty) return;
5469
5552
  mkdirSync3(this.historyDir, { recursive: true });
5470
- const filePath = join4(this.historyDir, `${this._current.id}.json`);
5471
- const raw = this._current.toJSON();
5553
+ const filePath = join4(this.historyDir, `${session.id}.json`);
5554
+ const raw = session.toJSON();
5472
5555
  const opts = this.redactOptionsForSave();
5473
5556
  const { value: payload, hits } = redactJson(raw, opts);
5474
5557
  this.lastRedactionHits = hits.length;
5475
- atomicWriteFileSync(filePath, JSON.stringify(payload, null, 2));
5476
- this._current.dirty = false;
5558
+ const serialized = JSON.stringify(payload, null, 2);
5559
+ session.dirty = false;
5560
+ try {
5561
+ await atomicWriteFile(filePath, serialized, { mode: 384 });
5562
+ } catch (error) {
5563
+ session.dirty = true;
5564
+ throw error;
5565
+ }
5477
5566
  try {
5478
5567
  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
5568
+ id: session.id,
5569
+ provider: session.provider,
5570
+ model: session.model,
5571
+ title: session.title,
5572
+ messageCount: session.messages.length,
5573
+ created: session.created,
5574
+ updated: session.updated
5486
5575
  }, filePath);
5487
5576
  } catch {
5488
5577
  }
@@ -5633,16 +5722,22 @@ var SessionManager = class {
5633
5722
  };
5634
5723
 
5635
5724
  // src/tools/builtin/bash.ts
5636
- import { spawn as spawn3 } from "child_process";
5725
+ import { spawn as spawn2 } from "child_process";
5637
5726
  import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
5638
- import { platform as platform3 } from "os";
5727
+ import { platform as platform2 } from "os";
5639
5728
  import { resolve } from "path";
5640
5729
 
5641
5730
  // src/tools/undo-stack.ts
5642
- import { readFileSync as readFileSync5, writeFileSync, unlinkSync as unlinkSync3, rmdirSync, existsSync as existsSync5 } from "fs";
5731
+ import { writeFileSync, unlinkSync as unlinkSync3, rmdirSync, existsSync as existsSync5 } from "fs";
5643
5732
  var MAX_UNDO_DEPTH = 20;
5644
5733
  var UndoStack = class {
5645
- stack = [];
5734
+ stateKey = /* @__PURE__ */ Symbol("stack");
5735
+ get stack() {
5736
+ return scopedState(this.stateKey, () => ({ items: [] })).items;
5737
+ }
5738
+ set stack(items) {
5739
+ scopedState(this.stateKey, () => ({ items: [] })).items = items;
5740
+ }
5646
5741
  /**
5647
5742
  * 在执行文件写入操作之前调用,保存当前状态。
5648
5743
  * @param filePath 将要被写入的文件路径
@@ -5651,11 +5746,7 @@ var UndoStack = class {
5651
5746
  push(filePath, description) {
5652
5747
  let previousContent = null;
5653
5748
  if (existsSync5(filePath)) {
5654
- try {
5655
- previousContent = readFileSync5(filePath, "utf-8");
5656
- } catch {
5657
- return;
5658
- }
5749
+ previousContent = readSnapshot(filePath);
5659
5750
  }
5660
5751
  this.pushEntry({
5661
5752
  filePath,
@@ -5692,7 +5783,7 @@ var UndoStack = class {
5692
5783
  /** 内部统一入栈方法,含溢出裁剪 */
5693
5784
  pushEntry(entry) {
5694
5785
  this.stack.push(entry);
5695
- if (this.stack.length > MAX_UNDO_DEPTH) {
5786
+ while (this.stack.length > MAX_UNDO_DEPTH || this.stack.reduce((n, e) => n + Buffer.byteLength(e.previousContent ?? ""), 0) > MAX_SNAPSHOT_TOTAL_BYTES) {
5696
5787
  this.stack.shift();
5697
5788
  }
5698
5789
  }
@@ -5753,186 +5844,17 @@ var UndoStack = class {
5753
5844
  };
5754
5845
  var undoStack = new UndoStack();
5755
5846
 
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
5847
  // 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";
5848
+ import { spawn } from "child_process";
5849
+ import { platform } from "os";
5850
+ var IS_WINDOWS2 = platform() === "win32";
5929
5851
  var SHELL = IS_WINDOWS2 ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
5930
5852
  var pool = /* @__PURE__ */ new Map();
5931
5853
  var IDLE_EVICT_MS = 5 * 6e4;
5932
5854
  var evictionTimer = null;
5933
5855
  function spawnShell(cwd) {
5934
5856
  const shellArgs = IS_WINDOWS2 ? ["-NoProfile", "-NonInteractive", "-Command", "-"] : ["-s"];
5935
- const proc = spawn2(SHELL, shellArgs, {
5857
+ const proc = spawn(SHELL, shellArgs, {
5936
5858
  cwd,
5937
5859
  windowsHide: true,
5938
5860
  stdio: ["pipe", "pipe", "pipe"]
@@ -6123,7 +6045,7 @@ function cleanupPoolSession(sessionKey) {
6123
6045
  }
6124
6046
 
6125
6047
  // src/tools/builtin/bash.ts
6126
- var IS_WINDOWS3 = platform3() === "win32";
6048
+ var IS_WINDOWS3 = platform2() === "win32";
6127
6049
  var SHELL2 = IS_WINDOWS3 ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
6128
6050
  var cwdBySession = /* @__PURE__ */ new Map();
6129
6051
  function getCwd() {
@@ -6153,7 +6075,8 @@ Important rules:
6153
6075
  4. To delete directories, use Remove-Item -Recurse (the system will automatically optimize to a more reliable method).
6154
6076
  5. IMPORTANT: On Windows, "curl" is an alias for Invoke-WebRequest and does NOT support curl flags like -s, -X, -H. Use Invoke-RestMethod instead for HTTP requests. Example: Invoke-RestMethod -Uri "http://localhost:3000/api/health" -Method Get
6155
6077
  6. NEVER run long-running / never-exiting commands through bash \u2014 they block until timeout. This includes starting an Android emulator (emulator -avd X), streaming logs (adb logcat), and dev servers (node server.js, npm run dev, metro / react-native start). Use the 'task_create' tool to run these in the background, then poll status with short bash calls (e.g. adb shell getprop sys.boot_completed).
6156
- 7. CRITICAL \u2014 this is PowerShell, NOT bash: \\ is NOT an escape character. Writing \\" inside a double-quoted string CLOSES the string and silently splits your command into fragments \u2014 it often still exits 0 with no output, so it looks like it worked when it did not. To escape a quote use \`" or "". For anything with nested quoting (especially ssh + psql -c "SELECT ..."), do NOT try to escape your way out: write the payload to a file with the 'write_file' tool, 'scp' it to the host, and run it with a single level of quoting (e.g. ssh host "psql -d db -f /tmp/x.sql").` : `Execute commands in ${SHELL2}.
6078
+ 7. CRITICAL \u2014 this is PowerShell, NOT bash: \\ is NOT an escape character. Writing \\" inside a double-quoted string CLOSES the string and silently splits your command into fragments \u2014 it often still exits 0 with no output, so it looks like it worked when it did not. To escape a quote use \`" or "". For anything with nested quoting (especially ssh + psql -c "SELECT ..."), do NOT try to escape your way out: write the payload to a file with the 'write_file' tool, 'scp' it to the host, and run it with a single level of quoting (e.g. ssh host "psql -d db -f /tmp/x.sql").
6079
+ 8. CRITICAL \u2014 running a program by its full path: a quoted string at the START of a statement is parsed as a string EXPRESSION, not a command. "C:\\path\\python.exe" -c "print(1)" is a SYNTAX ERROR. Put the call operator '&' in front: & "C:\\path\\python.exe" -c "print(1)" \u2014 or $py = "C:\\path\\python.exe"; & $py -c "print(1)". This applies to any quoted path used as a command, anywhere a statement starts (including after ';').` : `Execute commands in ${SHELL2}.
6157
6080
  Important rules:
6158
6081
  1. Each bash call runs in an independent subprocess; cd commands do not persist. To run in a specific directory, use the cwd parameter, or combine commands: e.g. "cd mydir && ls" or "mkdir -p mydir && touch mydir/file.txt".
6159
6082
  2. If a command fails (returns an error or non-zero exit code), stop immediately, report the error to the user, and do not retry the same or similar commands.
@@ -6408,6 +6331,116 @@ function detectPowerShellBackslashQuote(command) {
6408
6331
  }
6409
6332
  return false;
6410
6333
  }
6334
+ var PS_OPERATOR_WORDS = /* @__PURE__ */ new Set([
6335
+ "eq",
6336
+ "ne",
6337
+ "gt",
6338
+ "ge",
6339
+ "lt",
6340
+ "le",
6341
+ "ceq",
6342
+ "cne",
6343
+ "cgt",
6344
+ "cge",
6345
+ "clt",
6346
+ "cle",
6347
+ "ieq",
6348
+ "ine",
6349
+ "igt",
6350
+ "ige",
6351
+ "ilt",
6352
+ "ile",
6353
+ "like",
6354
+ "notlike",
6355
+ "match",
6356
+ "notmatch",
6357
+ "clike",
6358
+ "cnotlike",
6359
+ "cmatch",
6360
+ "cnotmatch",
6361
+ "ilike",
6362
+ "inotlike",
6363
+ "imatch",
6364
+ "inotmatch",
6365
+ "contains",
6366
+ "notcontains",
6367
+ "ccontains",
6368
+ "cnotcontains",
6369
+ "icontains",
6370
+ "inotcontains",
6371
+ "in",
6372
+ "notin",
6373
+ "cin",
6374
+ "cnotin",
6375
+ "iin",
6376
+ "inotin",
6377
+ "is",
6378
+ "isnot",
6379
+ "as",
6380
+ "and",
6381
+ "or",
6382
+ "not",
6383
+ "xor",
6384
+ "band",
6385
+ "bor",
6386
+ "bnot",
6387
+ "bxor",
6388
+ "shl",
6389
+ "shr",
6390
+ "join",
6391
+ "split",
6392
+ "replace",
6393
+ "csplit",
6394
+ "creplace",
6395
+ "isplit",
6396
+ "ireplace",
6397
+ "f"
6398
+ ]);
6399
+ function detectMissingCallOperator(command) {
6400
+ if (!IS_WINDOWS3) return false;
6401
+ let atStatementStart = true;
6402
+ let afterCallOperator = false;
6403
+ for (let i = 0; i < command.length; i++) {
6404
+ const ch = command[i];
6405
+ if (ch === " " || ch === " ") continue;
6406
+ if (ch === ";" || ch === "\n" || ch === "{" || ch === "(" || ch === "|" || ch === "&") {
6407
+ if (ch === "&") {
6408
+ const next = command.slice(i + 1).replace(/^[ \t]+/, "");
6409
+ afterCallOperator = !next.startsWith("&");
6410
+ } else {
6411
+ afterCallOperator = false;
6412
+ }
6413
+ atStatementStart = true;
6414
+ continue;
6415
+ }
6416
+ if (ch === "." && atStatementStart) {
6417
+ afterCallOperator = true;
6418
+ continue;
6419
+ }
6420
+ if ((ch === '"' || ch === "'") && atStatementStart && !afterCallOperator) {
6421
+ const quote = ch;
6422
+ let j = i + 1;
6423
+ while (j < command.length && command[j] !== quote) j++;
6424
+ if (j >= command.length) return false;
6425
+ const rest = command.slice(j + 1);
6426
+ const m = /^[ \t]+(\S+)/.exec(rest);
6427
+ if (m) {
6428
+ const token = m[1];
6429
+ if (/^[-]/.test(token)) {
6430
+ const word = token.slice(1).toLowerCase();
6431
+ if (PS_OPERATOR_WORDS.has(word)) return false;
6432
+ return true;
6433
+ }
6434
+ if (/^[+*%=<>|;)}\]]/.test(token)) return false;
6435
+ return true;
6436
+ }
6437
+ return false;
6438
+ }
6439
+ atStatementStart = false;
6440
+ afterCallOperator = false;
6441
+ }
6442
+ return false;
6443
+ }
6411
6444
  var BACKSLASH_QUOTE_SILENT_WARNING = `
6412
6445
 
6413
6446
  \u26A0 WARNING: this command contains \\" inside a double-quoted string, but PowerShell does NOT treat \\ as an escape \u2014 that quote CLOSED the string and the command was split into fragments. It exited 0 with no output because the outer program (e.g. ssh) still ran, but it very likely did NOT run what you intended. Do NOT assume this succeeded.
@@ -6488,6 +6521,11 @@ function buildErrorHint(command, stderr) {
6488
6521
  `Hint: PowerShell does NOT treat \\ as an escape character \u2014 the \\" in this command closed the double-quoted string and split the command into fragments. Escape quotes with \`" or "" instead. Better: avoid nested quoting entirely \u2014 write the payload to a file with 'write_file', 'scp' it over, and run it with one level of quoting (e.g. ssh host "psql -d <db> -f /tmp/x.sql").`
6489
6522
  );
6490
6523
  }
6524
+ if (detectMissingCallOperator(command) && /(ParserError|UnexpectedToken|Unexpected token|意外的标记|运算符后面提供|必须在)/i.test(stderr)) {
6525
+ hints.push(
6526
+ `Hint: in PowerShell a quoted string at the START of a statement is parsed as a string EXPRESSION, not as a command \u2014 so "C:\\path\\python.exe" -c "..." is a syntax error, not a quoting problem. Do NOT retry by removing the quotes, dropping the argument, or re-quoting it differently; all of those fail the same way. Fix: put the call operator '&' in front \u2014 \`& "C:\\path\\python.exe" -c "..."\` \u2014 or assign the path to a variable first (\`$py = "C:\\path\\python.exe"; & $py -c "..."\`).`
6527
+ );
6528
+ }
6491
6529
  return hints.length > 0 ? hints.map((h) => `\u{1F4A1} ${h}`).join("\n\n") : null;
6492
6530
  }
6493
6531
  function detectBlockingCommand(command) {
@@ -6590,7 +6628,7 @@ function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
6590
6628
  function runShell(command, opts) {
6591
6629
  return new Promise((resolvePromise) => {
6592
6630
  const shellArgs = IS_WINDOWS3 ? ["-NoProfile", "-NonInteractive", "-Command", WIN_UTF8_PREAMBLE + command] : ["-c", command];
6593
- const child = spawn3(SHELL2, shellArgs, {
6631
+ const child = spawn2(SHELL2, shellArgs, {
6594
6632
  cwd: opts.cwd,
6595
6633
  env: opts.env,
6596
6634
  windowsHide: true,
@@ -6669,16 +6707,16 @@ function updateCwdFromCommand(command, baseCwd) {
6669
6707
  }
6670
6708
 
6671
6709
  // src/tools/builtin/read-file.ts
6672
- import { readFileSync as readFileSync6, existsSync as existsSync7, statSync as statSync3, readdirSync as readdirSync3 } from "fs";
6710
+ import { readFileSync as readFileSync5, existsSync as existsSync7, statSync as statSync3, readdirSync as readdirSync3 } from "fs";
6673
6711
  import { execFileSync } from "child_process";
6674
6712
  import { extname, resolve as resolve2, basename, sep, dirname as dirname2 } from "path";
6675
6713
  import { homedir as homedir2 } from "os";
6676
6714
 
6677
6715
  // src/tools/builtin/notebook-utils.ts
6678
- function cellSourceToString(source) {
6679
- if (!source) return "";
6680
- if (Array.isArray(source)) return source.join("");
6681
- return source;
6716
+ function cellSourceToString(source2) {
6717
+ if (!source2) return "";
6718
+ if (Array.isArray(source2)) return source2.join("");
6719
+ return source2;
6682
6720
  }
6683
6721
  function stringToCellSource(content) {
6684
6722
  if (content.length === 0) return [];
@@ -6733,16 +6771,16 @@ function renderNotebookAsText(nb, filePath) {
6733
6771
  nb.cells.forEach((cell, idx) => {
6734
6772
  const cellNum = idx + 1;
6735
6773
  const type = cell.cell_type;
6736
- const source = cellSourceToString(cell.source);
6774
+ const source2 = cellSourceToString(cell.source);
6737
6775
  if (type === "markdown") {
6738
6776
  lines.push(`## Cell ${cellNum} [markdown]`);
6739
- lines.push(source);
6777
+ lines.push(source2);
6740
6778
  lines.push("");
6741
6779
  } else if (type === "code") {
6742
6780
  const execLabel = cell.execution_count != null ? ` (execution #${cell.execution_count})` : "";
6743
6781
  lines.push(`## Cell ${cellNum} [code${execLabel}]`);
6744
6782
  lines.push("```" + language);
6745
- lines.push(source);
6783
+ lines.push(source2);
6746
6784
  lines.push("```");
6747
6785
  const outputText = extractOutputText(cell.outputs);
6748
6786
  if (outputText) {
@@ -6754,7 +6792,7 @@ function renderNotebookAsText(nb, filePath) {
6754
6792
  lines.push("");
6755
6793
  } else if (type === "raw") {
6756
6794
  lines.push(`## Cell ${cellNum} [raw]`);
6757
- lines.push(source);
6795
+ lines.push(source2);
6758
6796
  lines.push("");
6759
6797
  }
6760
6798
  });
@@ -6779,6 +6817,7 @@ function createCell(cellType, content) {
6779
6817
  // src/tools/builtin/read-file.ts
6780
6818
  var MAX_FILE_BYTES = 10 * 1024 * 1024;
6781
6819
  function getHardRefusal(normalizedPath) {
6820
+ if (isCredentialVault(normalizedPath)) return "[Refused] Credential vault files cannot be read by tools.";
6782
6821
  const home2 = homedir2();
6783
6822
  const p = normalizedPath.toLowerCase();
6784
6823
  const base = basename(normalizedPath).toLowerCase();
@@ -6953,7 +6992,7 @@ var readFileTool = {
6953
6992
  const offsetLine = typeof rawOffset === "number" && Number.isFinite(rawOffset) && rawOffset > 0 ? Math.floor(rawOffset) : void 0;
6954
6993
  const limitLines = typeof rawLimit === "number" && Number.isFinite(rawLimit) && rawLimit > 0 ? Math.floor(rawLimit) : void 0;
6955
6994
  if (!filePath) throw new ToolError("read_file", "path is required");
6956
- const normalizedPath = resolve2(filePath);
6995
+ const normalizedPath = canonicalPath(filePath);
6957
6996
  const refusal = getHardRefusal(normalizedPath);
6958
6997
  if (refusal) return refusal;
6959
6998
  if (!existsSync7(normalizedPath)) {
@@ -7020,7 +7059,7 @@ Suggestion: read existing text versions (.md / .txt) in the project, or install
7020
7059
  }
7021
7060
  if (ext === ".ipynb") {
7022
7061
  try {
7023
- const raw = readFileSync6(normalizedPath, "utf-8");
7062
+ const raw = readFileSync5(normalizedPath, "utf-8");
7024
7063
  const nb = parseNotebook(raw);
7025
7064
  return renderNotebookAsText(nb, filePath);
7026
7065
  } catch (err) {
@@ -7038,8 +7077,8 @@ Do NOT claim you inspected this image \u2014 you have not seen it.`;
7038
7077
  return `[Binary file: ${filePath} (${ext})]
7039
7078
  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.`;
7040
7079
  }
7041
- const { readFile: readFile2 } = await import("fs/promises");
7042
- const buf = size > 1048576 ? await readFile2(normalizedPath) : readFileSync6(normalizedPath);
7080
+ const { readFile } = await import("fs/promises");
7081
+ const buf = size > 1048576 ? await readFile(normalizedPath) : readFileSync5(normalizedPath);
7043
7082
  if (encoding === "base64") {
7044
7083
  return `[File: ${filePath} | base64]
7045
7084
 
@@ -7077,12 +7116,12 @@ ${content}`;
7077
7116
  };
7078
7117
 
7079
7118
  // src/tools/builtin/write-file.ts
7080
- import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync10, statSync as statSync4 } from "fs";
7119
+ import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
7081
7120
  import { dirname as dirname5 } from "path";
7082
7121
 
7083
7122
  // src/tools/executor.ts
7084
7123
  import chalk3 from "chalk";
7085
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
7124
+ import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
7086
7125
  import { tmpdir } from "os";
7087
7126
 
7088
7127
  // src/core/readline-internal.ts
@@ -7322,7 +7361,7 @@ function simpleDiff(oldLines, newLines) {
7322
7361
  // src/tools/hooks.ts
7323
7362
  import { execSync } from "child_process";
7324
7363
  import { createHash } from "crypto";
7325
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7 } from "fs";
7364
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6 } from "fs";
7326
7365
  import { join as join5 } from "path";
7327
7366
  var TRUST_FILE = "hooks-trust.json";
7328
7367
  var DEFAULT_TIMEOUT_MS = 5e3;
@@ -7338,7 +7377,7 @@ function loadHookTrustStore(configDir) {
7338
7377
  const file = hookTrustPath(configDir);
7339
7378
  if (!existsSync8(file)) return { records: [] };
7340
7379
  try {
7341
- const parsed = JSON.parse(readFileSync7(file, "utf-8"));
7380
+ const parsed = JSON.parse(readFileSync6(file, "utf-8"));
7342
7381
  return { records: Array.isArray(parsed.records) ? parsed.records : [] };
7343
7382
  } catch {
7344
7383
  return { records: [] };
@@ -7352,9 +7391,9 @@ function hashHook(event, command) {
7352
7391
  return createHash("sha256").update(`${event}
7353
7392
  ${command}`, "utf-8").digest("hex");
7354
7393
  }
7355
- function isTrusted(source, event, hash, store) {
7356
- if (source === "user" || source === "managed") return true;
7357
- return store.records.some((r) => r.source === source && r.event === event && r.hash === hash);
7394
+ function isTrusted(source2, event, hash, store) {
7395
+ if (source2 === "user" || source2 === "managed") return true;
7396
+ return store.records.some((r) => r.source === source2 && r.event === event && r.hash === hash);
7358
7397
  }
7359
7398
  function listHooks(config, configDir) {
7360
7399
  if (!config || config.enabled === false) return [];
@@ -7362,15 +7401,15 @@ function listHooks(config, configDir) {
7362
7401
  const out = [];
7363
7402
  const push = (event, hook, index) => {
7364
7403
  if (!hook.command) return;
7365
- const source = hook.source ?? "user";
7404
+ const source2 = hook.source ?? "user";
7366
7405
  const hash = hashHook(event, hook.command);
7367
7406
  out.push({
7368
7407
  id: `${event}:${index}`,
7369
7408
  event,
7370
7409
  command: hook.command,
7371
- source,
7410
+ source: source2,
7372
7411
  hash,
7373
- trusted: isTrusted(source, event, hash, store),
7412
+ trusted: isTrusted(source2, event, hash, store),
7374
7413
  disabled: hook.disabled === true,
7375
7414
  description: hook.description
7376
7415
  });
@@ -7446,15 +7485,15 @@ function runLifecycleHooks(config, event, payload, opts) {
7446
7485
  for (let i = 0; i < hooks.length; i++) {
7447
7486
  const hook = hooks[i];
7448
7487
  if (!hook.command || hook.disabled) continue;
7449
- const source = hook.source ?? "user";
7488
+ const source2 = hook.source ?? "user";
7450
7489
  const hash = hashHook(event, hook.command);
7451
7490
  const descriptor = {
7452
7491
  id: `${event}:${i}`,
7453
7492
  event,
7454
7493
  command: hook.command,
7455
- source,
7494
+ source: source2,
7456
7495
  hash,
7457
- trusted: isTrusted(source, event, hash, store),
7496
+ trusted: isTrusted(source2, event, hash, store),
7458
7497
  disabled: false,
7459
7498
  description: hook.description
7460
7499
  };
@@ -7603,7 +7642,8 @@ var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
7603
7642
  "recall_memory"
7604
7643
  ]);
7605
7644
  function normalizePathForPermission(value) {
7606
- return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
7645
+ const normalized = value.replace(/\\/g, "/").replace(/\/+$/, "");
7646
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
7607
7647
  }
7608
7648
  function canonicalizeWithNearestParent(value) {
7609
7649
  const missing = [];
@@ -7691,7 +7731,7 @@ function profileHardDecision(profileName, toolName, args, dangerLevel, ctx) {
7691
7731
  return { action: "deny", reason: "file write target is outside workspace/temp roots", profileName, hardBoundary: true };
7692
7732
  }
7693
7733
  }
7694
- if (toolName === "bash" || toolName === "run_interactive" || toolName === "task_create") {
7734
+ if (toolName === "bash" || toolName === "run_interactive" || toolName === "task_create" || toolName === "run_tests") {
7695
7735
  return {
7696
7736
  action: "confirm",
7697
7737
  reason: "shell/process writes cannot be proven to stay inside workspace/temp roots",
@@ -7793,9 +7833,9 @@ function checkNetworkPolicy(toolName, args, policy) {
7793
7833
  if (!policy?.enabled) return null;
7794
7834
  let action = networkToolAction(toolName, policy);
7795
7835
  let reason = "network access requires confirmation";
7796
- if (toolName === "bash") {
7836
+ if (["bash", "run_tests", "run_interactive", "task_create"].includes(toolName)) {
7797
7837
  const command = String(args["command"] ?? "");
7798
- if (!isShellNetworkCommand(command)) return null;
7838
+ if (toolName === "bash" && !isShellNetworkCommand(command) && dangerIsReadOnly(command)) return null;
7799
7839
  action = policy.tools?.shell ?? policy.defaultAction ?? "confirm";
7800
7840
  reason = "shell command may access the network";
7801
7841
  }
@@ -7831,6 +7871,9 @@ function checkNetworkPolicy(toolName, args, policy) {
7831
7871
  if (!permissionAction) return null;
7832
7872
  return { action: permissionAction, reason, profileName: "networkPolicy" };
7833
7873
  }
7874
+ function dangerIsReadOnly(command) {
7875
+ return /^(?:pwd|echo [\w .-]+|git status(?: --short)?)$/.test(command.trim());
7876
+ }
7834
7877
 
7835
7878
  // src/tools/truncate.ts
7836
7879
  var DEFAULT_MAX_TOOL_OUTPUT_CHARS = 12e3;
@@ -8057,8 +8100,7 @@ function getTopUsedTools(limit = 5) {
8057
8100
  }
8058
8101
 
8059
8102
  // src/tools/action-classifier.ts
8060
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
8061
- import { isAbsolute as isAbsolute2, join as join7, resolve as resolve4 } from "path";
8103
+ import { isAbsolute as isAbsolute2, resolve as resolve4 } from "path";
8062
8104
  var RECENT_DENIED_LIMIT = 50;
8063
8105
  var recentlyDenied = [];
8064
8106
  function recordRecentlyDeniedAutoAction(call, classification) {
@@ -8078,7 +8120,8 @@ function clearRecentlyDeniedAutoActions() {
8078
8120
  recentlyDenied.splice(0, recentlyDenied.length);
8079
8121
  }
8080
8122
  function normalizePath(value) {
8081
- return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
8123
+ const normalized = canonicalPath(value).replace(/\\/g, "/").replace(/\/+$/, "");
8124
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
8082
8125
  }
8083
8126
  function isInside(root, target) {
8084
8127
  const absoluteRoot = resolve4(root);
@@ -8091,56 +8134,6 @@ function getPathArg(call) {
8091
8134
  const raw = call.arguments.path ?? call.arguments.filePath ?? call.arguments.outputPath ?? call.arguments.file;
8092
8135
  return typeof raw === "string" && raw.trim() ? raw.trim() : null;
8093
8136
  }
8094
- function splitShellWords(command) {
8095
- const out = [];
8096
- const re = /"([^"]*)"|'([^']*)'|([^\s]+)/g;
8097
- let m;
8098
- while (m = re.exec(command)) out.push(m[1] ?? m[2] ?? m[3] ?? "");
8099
- return out.filter(Boolean);
8100
- }
8101
- function readPackageManifest(root) {
8102
- const p = join7(root, "package.json");
8103
- if (!existsSync10(p)) return null;
8104
- try {
8105
- return JSON.parse(readFileSync8(p, "utf-8"));
8106
- } catch {
8107
- return null;
8108
- }
8109
- }
8110
- function manifestHasDependency(root, name) {
8111
- const manifest = readPackageManifest(root);
8112
- if (!manifest) return false;
8113
- for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
8114
- const deps = manifest[key];
8115
- if (deps && typeof deps === "object" && Object.prototype.hasOwnProperty.call(deps, name)) return true;
8116
- }
8117
- return false;
8118
- }
8119
- function lockfileMentions(root, name) {
8120
- for (const file of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]) {
8121
- const p = join7(root, file);
8122
- if (!existsSync10(p)) continue;
8123
- try {
8124
- if (readFileSync8(p, "utf-8").includes(name)) return true;
8125
- } catch {
8126
- }
8127
- }
8128
- return false;
8129
- }
8130
- function getInstallPackages(command) {
8131
- const words = splitShellWords(command);
8132
- const cmd = words[0]?.toLowerCase();
8133
- const sub = words[1]?.toLowerCase();
8134
- if (!cmd || !sub) return [];
8135
- const isInstall = cmd === "npm" && (sub === "install" || sub === "i" || sub === "add") || (cmd === "pnpm" || cmd === "yarn") && (sub === "add" || sub === "install");
8136
- if (!isInstall) return [];
8137
- return words.slice(2).filter((w) => !w.startsWith("-")).filter((w) => w !== "." && w !== "./").map((w) => w.replace(/^@([^/]+)\/([^@]+)(?:@.+)?$/, "@$1/$2").replace(/^([^@]+)@.+$/, "$1"));
8138
- }
8139
- function isPlainGitPush(command) {
8140
- if (!/^\s*git\s+push\b/i.test(command)) return false;
8141
- if (/--force|-f\b|\+[^\s]+/i.test(command)) return false;
8142
- return !/[;&|`$<>\n\r]/.test(command);
8143
- }
8144
8137
  function hasSecretExfiltrationShape(command) {
8145
8138
  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);
8146
8139
  }
@@ -8162,7 +8155,7 @@ var RuleBasedActionClassifier = class {
8162
8155
  }
8163
8156
  return { decision: "confirm", reason: "destructive actions require confirmation", ruleId: "auto.destructive-confirm", confidence: "high" };
8164
8157
  }
8165
- classifyShell(call, dangerLevel, ctx) {
8158
+ classifyShell(call, dangerLevel, _ctx) {
8166
8159
  const command = String(call.arguments.command ?? "");
8167
8160
  if (/\bcurl\b[^\n|;&]*\|\s*(?:sudo\s+)?(?:bash|sh|zsh)\b/i.test(command)) {
8168
8161
  return { decision: "deny", reason: "curl pipe shell execution is blocked in auto mode", ruleId: "deny.curl-pipe-shell", confidence: "high" };
@@ -8188,18 +8181,7 @@ var RuleBasedActionClassifier = class {
8188
8181
  if (/\b(npx|pnpm\s+dlx|yarn\s+dlx)\s+(claude|codex|aider|cursor-agent|gemini)\b/i.test(command)) {
8189
8182
  return { decision: "confirm", reason: "third-party agent loop requires confirmation", ruleId: "confirm.third-party-agent", confidence: "high" };
8190
8183
  }
8191
- if (isPlainGitPush(command)) {
8192
- return { decision: "auto-approve", reason: "plain git push without force", ruleId: "auto.git-push", confidence: "high" };
8193
- }
8194
- const pkgs = getInstallPackages(command);
8195
- if (pkgs.length > 0) {
8196
- const allDeclared = pkgs.every((p) => manifestHasDependency(ctx.workspaceRoot, p) || lockfileMentions(ctx.workspaceRoot, p));
8197
- 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" };
8198
- }
8199
- if (/\b(curl|wget|Invoke-WebRequest|Invoke-RestMethod)\b/i.test(command) && !/[>|;&`$]/.test(command)) {
8200
- return { decision: "auto-approve", reason: "read-only HTTP shell command", ruleId: "auto.readonly-http-shell", confidence: "medium" };
8201
- }
8202
- if (dangerLevel === "safe") {
8184
+ if (dangerLevel === "safe" && getDangerLevel(call.name, call.arguments) === "safe") {
8203
8185
  return { decision: "auto-approve", reason: "safe shell action", ruleId: "auto.safe-shell", confidence: "medium" };
8204
8186
  }
8205
8187
  return { decision: "confirm", reason: "shell action is not proven low-risk", ruleId: "confirm.shell-default", confidence: "medium" };
@@ -8668,10 +8650,10 @@ var ToolExecutor = class {
8668
8650
  if (call.name === "write_file") {
8669
8651
  const { path: filePath, content: chunk, append } = resolveWriteFileArgs(call.arguments);
8670
8652
  if (!filePath) return;
8671
- if (existsSync11(filePath)) {
8653
+ if (existsSync10(filePath)) {
8672
8654
  let oldContent;
8673
8655
  try {
8674
- oldContent = readFileSync9(filePath, "utf-8");
8656
+ oldContent = readFileSync7(filePath, "utf-8");
8675
8657
  } catch {
8676
8658
  return;
8677
8659
  }
@@ -8695,7 +8677,7 @@ var ToolExecutor = class {
8695
8677
  }
8696
8678
  } else if (call.name === "edit_file") {
8697
8679
  const filePath = String(call.arguments["path"] ?? "");
8698
- if (!filePath || !existsSync11(filePath)) return;
8680
+ if (!filePath || !existsSync10(filePath)) return;
8699
8681
  const oldStr = call.arguments["old_str"];
8700
8682
  const newStr = call.arguments["new_str"];
8701
8683
  if (oldStr !== void 0) {
@@ -8722,7 +8704,7 @@ var ToolExecutor = class {
8722
8704
  const to = Number(call.arguments["delete_to_line"] ?? from);
8723
8705
  let fileContent;
8724
8706
  try {
8725
- fileContent = readFileSync9(filePath, "utf-8");
8707
+ fileContent = readFileSync7(filePath, "utf-8");
8726
8708
  } catch {
8727
8709
  return;
8728
8710
  }
@@ -8818,7 +8800,12 @@ import { resolve as resolve5, sep as sep2, basename as basename3 } from "path";
8818
8800
  import { homedir as homedir4 } from "os";
8819
8801
  var home = homedir4();
8820
8802
  function norm(p) {
8821
- const abs = resolve5(p);
8803
+ let abs;
8804
+ try {
8805
+ abs = canonicalPath(p);
8806
+ } catch {
8807
+ abs = resolve5(p);
8808
+ }
8822
8809
  return process.platform === "win32" ? abs.toLowerCase() : abs;
8823
8810
  }
8824
8811
  function homeRel(p) {
@@ -8832,6 +8819,7 @@ function homeRel(p) {
8832
8819
  }
8833
8820
  function classifyWritePath(path3) {
8834
8821
  if (!path3) return { sensitive: false };
8822
+ if (isCredentialVault(path3)) return { sensitive: true, reason: "credential vault" };
8835
8823
  const abs = norm(path3);
8836
8824
  const base = basename3(abs);
8837
8825
  const rel = homeRel(path3);
@@ -8914,7 +8902,7 @@ function countTextLines(text) {
8914
8902
 
8915
8903
  // src/tools/builtin/write-file.ts
8916
8904
  function countFileLines(filePath) {
8917
- return countTextLines(readFileSync10(filePath, "utf-8"));
8905
+ return countTextLines(readFileSync8(filePath, "utf-8"));
8918
8906
  }
8919
8907
  var writeFileTool = {
8920
8908
  definition: {
@@ -8978,7 +8966,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
8978
8966
  const mode = appendMode ? "appended" : "written";
8979
8967
  void (async () => {
8980
8968
  try {
8981
- const { updateFile } = await import("./indexer-FPKY7LCT.js");
8969
+ const { updateFile } = await import("./indexer-VRXDZ74A.js");
8982
8970
  await updateFile(process.cwd(), filePath);
8983
8971
  } catch {
8984
8972
  }
@@ -8997,7 +8985,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
8997
8985
  };
8998
8986
 
8999
8987
  // src/tools/builtin/edit-file.ts
9000
- import { readFileSync as readFileSync11, existsSync as existsSync12 } from "fs";
8988
+ import { readFileSync as readFileSync9, existsSync as existsSync11 } from "fs";
9001
8989
 
9002
8990
  // src/tools/builtin/patch-apply.ts
9003
8991
  function parseUnifiedDiff(patch) {
@@ -9365,7 +9353,7 @@ Note: Path can be absolute or relative to cwd.`,
9365
9353
  const filePath = String(args["path"] ?? args["file"] ?? "");
9366
9354
  const encoding = args["encoding"] ?? "utf-8";
9367
9355
  if (!filePath) throw new ToolError("edit_file", "path is required");
9368
- if (!existsSync12(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
9356
+ if (!existsSync11(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
9369
9357
  const verdict = classifyWritePath(filePath);
9370
9358
  if (verdict.sensitive && subAgentGuard.active) {
9371
9359
  throw new ToolError(
@@ -9373,7 +9361,7 @@ Note: Path can be absolute or relative to cwd.`,
9373
9361
  `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.`
9374
9362
  );
9375
9363
  }
9376
- const original = readFileSync11(filePath, encoding);
9364
+ const original = readFileSync9(filePath, encoding);
9377
9365
  if (args["patch"] !== void 0) {
9378
9366
  const patchText = String(args["patch"] ?? "");
9379
9367
  const stopOnError = args["stop_on_error"] !== false;
@@ -9550,8 +9538,8 @@ function truncatePreview(str, maxLen = 80) {
9550
9538
  }
9551
9539
 
9552
9540
  // src/tools/builtin/list-dir.ts
9553
- import { readdirSync as readdirSync4, statSync as statSync5, existsSync as existsSync13 } from "fs";
9554
- import { join as join8, basename as basename4 } from "path";
9541
+ import { readdirSync as readdirSync4, statSync as statSync5, existsSync as existsSync12 } from "fs";
9542
+ import { join as join7, basename as basename4 } from "path";
9555
9543
  var listDirTool = {
9556
9544
  definition: {
9557
9545
  name: "list_dir",
@@ -9573,7 +9561,7 @@ var listDirTool = {
9573
9561
  async execute(args) {
9574
9562
  const dirPath = String(args["path"] ?? process.cwd());
9575
9563
  const recursive = Boolean(args["recursive"] ?? false);
9576
- if (!existsSync13(dirPath)) {
9564
+ if (!existsSync12(dirPath)) {
9577
9565
  const targetName = basename4(dirPath).toLowerCase();
9578
9566
  const cwd = process.cwd();
9579
9567
  const suggestions = [];
@@ -9631,11 +9619,11 @@ function listRecursive(basePath, indent, recursive, lines) {
9631
9619
  if (entry.isDirectory()) {
9632
9620
  lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
9633
9621
  if (recursive) {
9634
- listRecursive(join8(basePath, entry.name), indent + " ", true, lines);
9622
+ listRecursive(join7(basePath, entry.name), indent + " ", true, lines);
9635
9623
  }
9636
9624
  } else {
9637
9625
  try {
9638
- const stat = statSync5(join8(basePath, entry.name));
9626
+ const stat = statSync5(join7(basePath, entry.name));
9639
9627
  const size = formatSize(stat.size);
9640
9628
  lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
9641
9629
  } catch {
@@ -9650,10 +9638,77 @@ function formatSize(bytes) {
9650
9638
  return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
9651
9639
  }
9652
9640
 
9641
+ // src/tools/regex-search.ts
9642
+ import { Worker } from "worker_threads";
9643
+ var source = String.raw`
9644
+ const { parentPort, workerData: d } = require('node:worker_threads');
9645
+ const fs = require('node:fs');
9646
+ let re;
9647
+ try { re = new RegExp(d.pattern, d.ignoreCase ? 'i' : ''); }
9648
+ catch { re = new RegExp(d.pattern.replace(/[.*+?^$()|[\]\\]/g, '\\$&'), d.ignoreCase ? 'i' : ''); }
9649
+ const results = [];
9650
+ for (const f of d.files) {
9651
+ if (results.length >= d.maxResults) break;
9652
+ let fd;
9653
+ try {
9654
+ fd = fs.openSync(f.fullPath, 'r');
9655
+ const buf = Buffer.alloc(1000001);
9656
+ let size = 0;
9657
+ while (size < buf.length) { const n = fs.readSync(fd, buf, size, buf.length - size, null); if (!n) break; size += n; }
9658
+ if (size > 1000000 || buf.subarray(0, Math.min(size,512)).includes(0)) continue;
9659
+ const lines = buf.subarray(0,size).toString('utf8').split('\n');
9660
+ for (let i=0; i<lines.length && results.length<d.maxResults; i++) {
9661
+ if (!re.test(lines[i])) continue;
9662
+ const match = { file:f.relPath.replace(/\\/g,'/'), lineNumber:i+1, lineText:lines[i].slice(0,10000) };
9663
+ if (d.context) {
9664
+ match.contextBefore=[]; match.contextAfter=[];
9665
+ for(let j=Math.max(0,i-d.context);j<i;j++) match.contextBefore.push([j+1,lines[j].slice(0,10000)]);
9666
+ 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)]);
9667
+ }
9668
+ results.push(match);
9669
+ }
9670
+ } catch {} finally { if (fd !== undefined) fs.closeSync(fd); }
9671
+ }
9672
+ parentPort.postMessage(results);
9673
+ `;
9674
+ var active = 0;
9675
+ async function searchInWorker(files, pattern, ignoreCase, context, maxResults) {
9676
+ const signal = currentAbortSignal();
9677
+ if (signal.aborted) throw new Error("Search cancelled");
9678
+ if (active >= 4) throw new Error("Search capacity reached; wait for another search to finish");
9679
+ active++;
9680
+ let worker;
9681
+ try {
9682
+ return await new Promise((resolve13, reject) => {
9683
+ worker = new Worker(source, { eval: true, workerData: { files, pattern, ignoreCase, context, maxResults }, resourceLimits: { maxOldGenerationSizeMb: 64 } });
9684
+ let settled = false;
9685
+ const finish = (error, results) => {
9686
+ if (settled) return;
9687
+ settled = true;
9688
+ clearTimeout(timer);
9689
+ signal.removeEventListener("abort", abort);
9690
+ if (error) reject(error);
9691
+ else resolve13(results ?? []);
9692
+ };
9693
+ const timer = setTimeout(() => finish(new Error("Search exceeded 2 second CPU/time budget; simplify the pattern or narrow the path")), 2e3);
9694
+ const abort = () => finish(new Error("Search cancelled"));
9695
+ signal.addEventListener("abort", abort, { once: true });
9696
+ if (signal.aborted) abort();
9697
+ worker.once("message", (results) => finish(void 0, results));
9698
+ worker.once("error", (error) => finish(error));
9699
+ worker.once("exit", (code) => {
9700
+ if (!settled) finish(new Error(`Search worker exited (${code})`));
9701
+ });
9702
+ });
9703
+ } finally {
9704
+ if (worker) await worker.terminate();
9705
+ active--;
9706
+ }
9707
+ }
9708
+
9653
9709
  // src/tools/builtin/grep-files.ts
9654
- import { readdirSync as readdirSync5, readFileSync as readFileSync12, statSync as statSync6, existsSync as existsSync14 } from "fs";
9655
- import { readFile } from "fs/promises";
9656
- import { join as join9, relative } from "path";
9710
+ import { readdirSync as readdirSync5, statSync as statSync6, existsSync as existsSync13 } from "fs";
9711
+ import { join as join8, relative } from "path";
9657
9712
  var grepFilesTool = {
9658
9713
  definition: {
9659
9714
  name: "grep_files",
@@ -9701,43 +9756,24 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
9701
9756
  const rootPath = String(args["path"] ?? process.cwd());
9702
9757
  const filePattern = args["file_pattern"] ? String(args["file_pattern"]) : void 0;
9703
9758
  const ignoreCase = Boolean(args["ignore_case"] ?? false);
9704
- const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
9705
- const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
9759
+ const contextLines = Math.min(10, Math.max(0, Number(args["context_lines"]) || 0));
9760
+ const maxResults = Math.min(200, Math.max(1, Number(args["max_results"]) || 50));
9706
9761
  if (!pattern) throw new ToolError("grep_files", "pattern is required");
9707
- if (!existsSync14(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
9762
+ if (!existsSync13(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
9708
9763
  const MAX_PATTERN_LENGTH = 1e3;
9709
- if (pattern.length > MAX_PATTERN_LENGTH) {
9764
+ if (pattern.length > MAX_PATTERN_LENGTH || (filePattern?.length ?? 0) > MAX_PATTERN_LENGTH) {
9710
9765
  throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
9711
9766
  }
9712
- let regex;
9713
- try {
9714
- regex = new RegExp(pattern, ignoreCase ? "gi" : "g");
9715
- } catch {
9716
- const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9717
- regex = new RegExp(escaped, ignoreCase ? "gi" : "g");
9718
- }
9719
- const results = [];
9720
- const stat = statSync6(rootPath);
9721
- if (stat.isFile()) {
9722
- searchInFile(rootPath, rootPath, regex, contextLines, maxResults, results);
9723
- } else {
9724
- const filePaths = collectFilePaths(rootPath, filePattern, maxResults);
9725
- const BATCH_SIZE = 16;
9726
- for (let i = 0; i < filePaths.length && results.length < maxResults; i += BATCH_SIZE) {
9727
- const batch = filePaths.slice(i, i + BATCH_SIZE);
9728
- const batchResults = await Promise.all(
9729
- batch.map(
9730
- ({ fullPath, relPath }) => searchInFileAsync(fullPath, relPath, regex, contextLines, maxResults - results.length)
9731
- )
9732
- );
9733
- for (const br of batchResults) {
9734
- for (const r of br) {
9735
- if (results.length >= maxResults) break;
9736
- results.push(r);
9737
- }
9738
- }
9767
+ assertReadablePath(rootPath);
9768
+ const paths = statSync6(rootPath).isFile() ? [{ fullPath: rootPath, relPath: rootPath }] : collectFilePaths(rootPath, filePattern, maxResults);
9769
+ const files = paths.filter((f) => {
9770
+ try {
9771
+ return !isCredentialVault(f.fullPath);
9772
+ } catch {
9773
+ return false;
9739
9774
  }
9740
- }
9775
+ });
9776
+ const results = await searchInWorker(files, pattern, ignoreCase, contextLines, maxResults);
9741
9777
  if (results.length === 0) {
9742
9778
  return `No matches found for pattern: ${pattern}
9743
9779
  Searched in: ${rootPath}${filePattern ? `
@@ -9777,8 +9813,23 @@ File filter: ${filePattern}` : ""}`;
9777
9813
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", ".next", "__pycache__", ".cache", "coverage", ".nyc_output"]);
9778
9814
  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"]);
9779
9815
  function matchesFilePattern(filename, pattern) {
9780
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
9781
- return new RegExp(`^${escaped}$`, "i").test(filename);
9816
+ filename = filename.toLowerCase();
9817
+ pattern = pattern.toLowerCase();
9818
+ let i = 0, j = 0, star = -1, restart = 0;
9819
+ while (i < filename.length) {
9820
+ if (pattern[j] === filename[i]) {
9821
+ i++;
9822
+ j++;
9823
+ } else if (pattern[j] === "*") {
9824
+ star = j++;
9825
+ restart = i;
9826
+ } else if (star >= 0) {
9827
+ j = star + 1;
9828
+ i = ++restart;
9829
+ } else return false;
9830
+ }
9831
+ while (pattern[j] === "*") j++;
9832
+ return j === pattern.length;
9782
9833
  }
9783
9834
  function isBinary(filename) {
9784
9835
  const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
@@ -9786,8 +9837,9 @@ function isBinary(filename) {
9786
9837
  }
9787
9838
  function collectFilePaths(rootPath, filePattern, maxFiles) {
9788
9839
  const paths = [];
9789
- function walk(dirPath) {
9790
- if (paths.length >= maxFiles) return;
9840
+ let visited = 0;
9841
+ function walk(dirPath, depth = 0) {
9842
+ if (paths.length >= maxFiles || depth > 20 || visited >= 2e4) return;
9791
9843
  let entries;
9792
9844
  try {
9793
9845
  entries = readdirSync5(dirPath, { withFileTypes: true });
@@ -9795,14 +9847,14 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
9795
9847
  return;
9796
9848
  }
9797
9849
  for (const entry of entries) {
9798
- if (paths.length >= maxFiles) return;
9850
+ if (paths.length >= maxFiles || ++visited > 2e4) return;
9799
9851
  if (entry.isDirectory()) {
9800
9852
  if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
9801
- walk(join9(dirPath, entry.name));
9853
+ walk(join8(dirPath, entry.name), depth + 1);
9802
9854
  } else if (entry.isFile()) {
9803
9855
  if (isBinary(entry.name)) continue;
9804
9856
  if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
9805
- const fullPath = join9(dirPath, entry.name);
9857
+ const fullPath = join8(dirPath, entry.name);
9806
9858
  try {
9807
9859
  if (statSync6(fullPath).size > 1e6) continue;
9808
9860
  } catch {
@@ -9815,81 +9867,10 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
9815
9867
  walk(rootPath);
9816
9868
  return paths;
9817
9869
  }
9818
- async function searchInFileAsync(fullPath, displayPath2, regex, contextLines, maxResults) {
9819
- let content;
9820
- try {
9821
- content = await readFile(fullPath, "utf-8");
9822
- } catch {
9823
- return [];
9824
- }
9825
- const results = [];
9826
- const lines = content.split("\n");
9827
- regex.lastIndex = 0;
9828
- for (let i = 0; i < lines.length && results.length < maxResults; i++) {
9829
- regex.lastIndex = 0;
9830
- if (regex.test(lines[i])) {
9831
- const match = {
9832
- file: displayPath2.replace(/\\/g, "/"),
9833
- lineNumber: i + 1,
9834
- lineText: lines[i]
9835
- };
9836
- if (contextLines > 0) {
9837
- match.contextBefore = [];
9838
- for (let j = Math.max(0, i - contextLines); j < i; j++) {
9839
- match.contextBefore.push([j + 1, lines[j]]);
9840
- }
9841
- match.contextAfter = [];
9842
- for (let j = i + 1; j <= Math.min(lines.length - 1, i + contextLines); j++) {
9843
- match.contextAfter.push([j + 1, lines[j]]);
9844
- }
9845
- }
9846
- results.push(match);
9847
- }
9848
- }
9849
- return results;
9850
- }
9851
- function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, results) {
9852
- try {
9853
- const stat = statSync6(fullPath);
9854
- if (stat.size > 1e6) return;
9855
- } catch {
9856
- return;
9857
- }
9858
- let content;
9859
- try {
9860
- content = readFileSync12(fullPath, "utf-8");
9861
- } catch {
9862
- return;
9863
- }
9864
- const lines = content.split("\n");
9865
- regex.lastIndex = 0;
9866
- for (let i = 0; i < lines.length; i++) {
9867
- if (results.length >= maxResults) return;
9868
- regex.lastIndex = 0;
9869
- if (regex.test(lines[i])) {
9870
- const result = {
9871
- file: displayPath2,
9872
- lineNumber: i + 1,
9873
- lineText: lines[i].trimEnd()
9874
- };
9875
- if (contextLines > 0) {
9876
- result.contextBefore = [];
9877
- result.contextAfter = [];
9878
- for (let c = Math.max(0, i - contextLines); c < i; c++) {
9879
- result.contextBefore.push([c + 1, lines[c].trimEnd()]);
9880
- }
9881
- for (let c = i + 1; c <= Math.min(lines.length - 1, i + contextLines); c++) {
9882
- result.contextAfter.push([c + 1, lines[c].trimEnd()]);
9883
- }
9884
- }
9885
- results.push(result);
9886
- }
9887
- }
9888
- }
9889
9870
 
9890
9871
  // src/tools/builtin/glob-files.ts
9891
- import { readdirSync as readdirSync6, statSync as statSync7, existsSync as existsSync15 } from "fs";
9892
- import { join as join10, relative as relative2, basename as basename5 } from "path";
9872
+ import { readdirSync as readdirSync6, statSync as statSync7, existsSync as existsSync14 } from "fs";
9873
+ import { join as join9, relative as relative2, basename as basename5 } from "path";
9893
9874
  var globFilesTool = {
9894
9875
  definition: {
9895
9876
  name: "glob_files",
@@ -9922,7 +9903,7 @@ Results sorted by most recent modification time. Automatically skips node_module
9922
9903
  const rootPath = String(args["path"] ?? process.cwd());
9923
9904
  const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
9924
9905
  if (!pattern) throw new ToolError("glob_files", "pattern is required");
9925
- if (!existsSync15(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
9906
+ if (!existsSync14(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
9926
9907
  const regex = globToRegex(pattern);
9927
9908
  const matches2 = [];
9928
9909
  collectMatchingFiles(rootPath, rootPath, regex, matches2, maxResults);
@@ -9999,7 +9980,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
9999
9980
  }
10000
9981
  for (const entry of entries) {
10001
9982
  if (results.length >= maxResults) break;
10002
- const fullPath = join10(dirPath, entry.name);
9983
+ const fullPath = join9(dirPath, entry.name);
10003
9984
  if (entry.isDirectory()) {
10004
9985
  if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
10005
9986
  collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
@@ -10018,9 +9999,9 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
10018
9999
  }
10019
10000
 
10020
10001
  // src/tools/builtin/run-interactive.ts
10021
- import { spawn as spawn4 } from "child_process";
10022
- import { platform as platform4 } from "os";
10023
- var IS_WINDOWS4 = platform4() === "win32";
10002
+ import { spawn as spawn3 } from "child_process";
10003
+ import { platform as platform3 } from "os";
10004
+ var IS_WINDOWS4 = platform3() === "win32";
10024
10005
  var runInteractiveTool = {
10025
10006
  definition: {
10026
10007
  name: "run_interactive",
@@ -10082,7 +10063,7 @@ var runInteractiveTool = {
10082
10063
  };
10083
10064
  const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
10084
10065
  return new Promise((resolve13) => {
10085
- const child = spawn4(executable, cmdArgs.map(String), {
10066
+ const child = spawn3(executable, cmdArgs.map(String), {
10086
10067
  cwd: process.cwd(),
10087
10068
  env,
10088
10069
  stdio: ["pipe", "pipe", "pipe"]
@@ -10352,8 +10333,8 @@ var webFetchTool = {
10352
10333
  if (e.message.startsWith("Blocked:")) throw e;
10353
10334
  throw new NetworkError(`Invalid URL: "${url}"`);
10354
10335
  }
10355
- const controller2 = new AbortController();
10356
- const timeoutId = setTimeout(() => controller2.abort(), 2e4);
10336
+ const controller = new AbortController();
10337
+ const timeoutId = setTimeout(() => controller.abort(), 2e4);
10357
10338
  let rawHtml;
10358
10339
  let finalUrl;
10359
10340
  let contentType;
@@ -10374,7 +10355,7 @@ var webFetchTool = {
10374
10355
  }
10375
10356
  await resolveAndCheck(parsedHop.hostname);
10376
10357
  const r = await fetch(currentUrl, {
10377
- signal: controller2.signal,
10358
+ signal: controller.signal,
10378
10359
  headers: FETCH_HEADERS,
10379
10360
  redirect: "manual",
10380
10361
  // 手动控制重定向
@@ -10737,8 +10718,8 @@ function parseGoogleResults(html, maxResults) {
10737
10718
  return results;
10738
10719
  }
10739
10720
  async function fetchHtml(url) {
10740
- const controller2 = new AbortController();
10741
- const timer = setTimeout(() => controller2.abort(), REQUEST_TIMEOUT_MS);
10721
+ const controller = new AbortController();
10722
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
10742
10723
  try {
10743
10724
  const res = await fetch(url, {
10744
10725
  method: "GET",
@@ -10747,7 +10728,7 @@ async function fetchHtml(url) {
10747
10728
  "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9",
10748
10729
  "Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8"
10749
10730
  },
10750
- signal: controller2.signal
10731
+ signal: controller.signal
10751
10732
  });
10752
10733
  if (!res.ok) {
10753
10734
  throw new NetworkError(`HTTP ${res.status} ${res.statusText}`, res.status);
@@ -10841,10 +10822,8 @@ Any of these triggers means use save_last_response, NOT write_file:
10841
10822
  };
10842
10823
 
10843
10824
  // src/tools/builtin/save-memory.ts
10844
- import { join as join11 } from "path";
10845
- import { homedir as homedir5 } from "os";
10846
10825
  function getConfigDir() {
10847
- return join11(homedir5(), CONFIG_DIR_NAME);
10826
+ return getExecutionConfigDir();
10848
10827
  }
10849
10828
  function parseSensitivity(value) {
10850
10829
  return value === "low" || value === "medium" || value === "high" ? value : void 0;
@@ -11108,15 +11087,15 @@ var googleSearchTool = {
11108
11087
  url.searchParams.set("cx", cx);
11109
11088
  url.searchParams.set("q", query);
11110
11089
  url.searchParams.set("num", String(numResults));
11111
- const controller2 = new AbortController();
11112
- const timeout = setTimeout(() => controller2.abort(), REQUEST_TIMEOUT_MS2);
11090
+ const controller = new AbortController();
11091
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
11113
11092
  try {
11114
11093
  const response = await fetch(url.toString(), {
11115
11094
  method: "GET",
11116
11095
  headers: {
11117
11096
  "Accept": "application/json"
11118
11097
  },
11119
- signal: controller2.signal
11098
+ signal: controller.signal
11120
11099
  });
11121
11100
  if (!response.ok) {
11122
11101
  const errorBody = await response.text().catch(() => "");
@@ -11200,13 +11179,13 @@ function isToolCapableProvider(provider) {
11200
11179
  }
11201
11180
 
11202
11181
  // src/agents/agent-config.ts
11203
- import { existsSync as existsSync17, readdirSync as readdirSync8, readFileSync as readFileSync14 } from "fs";
11204
- import { join as join13 } from "path";
11205
- import { homedir as homedir6 } from "os";
11182
+ import { existsSync as existsSync16, readdirSync as readdirSync8, readFileSync as readFileSync11 } from "fs";
11183
+ import { join as join11 } from "path";
11184
+ import { homedir as homedir5 } from "os";
11206
11185
 
11207
11186
  // src/plugins/plugin-manager.ts
11208
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync13, realpathSync as realpathSync2, cpSync, rmSync, statSync as statSync8 } from "fs";
11209
- import { basename as basename6, dirname as dirname7, join as join12, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
11187
+ import { existsSync as existsSync15, mkdirSync as mkdirSync8, readdirSync as readdirSync7, readFileSync as readFileSync10, realpathSync as realpathSync2, cpSync, rmSync, statSync as statSync8 } from "fs";
11188
+ import { basename as basename6, dirname as dirname7, join as join10, relative as relative3, resolve as resolve6, sep as sep3 } from "path";
11210
11189
  import { createHash as createHash2 } from "crypto";
11211
11190
  import { z as z2 } from "zod";
11212
11191
  var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
@@ -11266,19 +11245,19 @@ var PluginManifestSchema = z2.object({
11266
11245
  permissionHints: z2.array(z2.string()).default([])
11267
11246
  }).strict();
11268
11247
  function pluginRoot(configDir) {
11269
- return join12(configDir, PLUGINS_DIR_NAME);
11248
+ return join10(configDir, PLUGINS_DIR_NAME);
11270
11249
  }
11271
11250
  function pluginManifestPath(pluginDir) {
11272
- return join12(pluginDir, MANIFEST_RELATIVE);
11251
+ return join10(pluginDir, MANIFEST_RELATIVE);
11273
11252
  }
11274
11253
  function statePath(configDir) {
11275
- return join12(pluginRoot(configDir), STATE_FILE);
11254
+ return join10(pluginRoot(configDir), STATE_FILE);
11276
11255
  }
11277
11256
  function loadState(configDir) {
11278
11257
  const file = statePath(configDir);
11279
- if (!existsSync16(file)) return { version: 2, plugins: [] };
11258
+ if (!existsSync15(file)) return { version: 2, plugins: [] };
11280
11259
  try {
11281
- const parsed = JSON.parse(readFileSync13(file, "utf-8"));
11260
+ const parsed = JSON.parse(readFileSync10(file, "utf-8"));
11282
11261
  const rawPlugins = Array.isArray(parsed.plugins) ? parsed.plugins : [];
11283
11262
  return { version: 2, plugins: rawPlugins.map((p) => migrateEntry(p)) };
11284
11263
  } catch {
@@ -11336,7 +11315,7 @@ function extractCommandPaths(command, pluginDir) {
11336
11315
  try {
11337
11316
  const abs = resolve6(resolvedDir, ref);
11338
11317
  if (!abs.startsWith(resolvedDir + sep3)) continue;
11339
- if (existsSync16(abs)) {
11318
+ if (existsSync15(abs)) {
11340
11319
  const real = realpathSync2(abs);
11341
11320
  if (!real.startsWith(resolvedDir + sep3)) continue;
11342
11321
  valid.push(relative3(resolvedDir, real));
@@ -11368,8 +11347,8 @@ function hashPluginContent(pluginDir, manifest) {
11368
11347
  }
11369
11348
  const sorted = [...assetRels].sort();
11370
11349
  const hasher = createHash2("sha256");
11371
- const manifestPath = join12(resolvedDir, MANIFEST_RELATIVE);
11372
- hasher.update(normalizeForHash(readFileSync13(manifestPath)));
11350
+ const manifestPath = join10(resolvedDir, MANIFEST_RELATIVE);
11351
+ hasher.update(normalizeForHash(readFileSync10(manifestPath)));
11373
11352
  for (const rel of sorted) {
11374
11353
  const abs = resolve6(resolvedDir, rel);
11375
11354
  if (!abs.startsWith(resolvedDir + sep3)) {
@@ -11389,7 +11368,7 @@ function hashPluginContent(pluginDir, manifest) {
11389
11368
  if (!realPath.startsWith(resolvedDir + sep3)) {
11390
11369
  throw new Error(`plugin asset symlink escapes plugin dir: ${rel} \u2192 ${realPath}`);
11391
11370
  }
11392
- hasher.update(normalizeForHash(readFileSync13(abs)));
11371
+ hasher.update(normalizeForHash(readFileSync10(abs)));
11393
11372
  }
11394
11373
  return hasher.digest("hex");
11395
11374
  }
@@ -11397,22 +11376,22 @@ function effectiveHash(entry) {
11397
11376
  return entry.contentHash ?? entry.hash ?? "";
11398
11377
  }
11399
11378
  function readPluginManifest(manifestPath) {
11400
- return PluginManifestSchema.parse(JSON.parse(readFileSync13(manifestPath, "utf-8")));
11379
+ return PluginManifestSchema.parse(JSON.parse(readFileSync10(manifestPath, "utf-8")));
11401
11380
  }
11402
11381
  function findPluginSourceDir(inputPath) {
11403
11382
  const abs = resolve6(inputPath);
11404
11383
  const direct = statSync8(abs).isDirectory() ? abs : dirname7(abs);
11405
- if (existsSync16(pluginManifestPath(direct))) return direct;
11406
- const nested = join12(direct, ".aicli-plugin");
11407
- if (existsSync16(join12(nested, "plugin.json"))) return direct;
11384
+ if (existsSync15(pluginManifestPath(direct))) return direct;
11385
+ const nested = join10(direct, ".aicli-plugin");
11386
+ if (existsSync15(join10(nested, "plugin.json"))) return direct;
11408
11387
  throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
11409
11388
  }
11410
11389
  function installPlugin(configDir, inputPath) {
11411
11390
  const sourceDir = findPluginSourceDir(inputPath);
11412
11391
  const manifest = readPluginManifest(pluginManifestPath(sourceDir));
11413
- const targetDir = join12(pluginRoot(configDir), manifest.name);
11392
+ const targetDir = join10(pluginRoot(configDir), manifest.name);
11414
11393
  mkdirSync8(pluginRoot(configDir), { recursive: true });
11415
- if (existsSync16(targetDir)) rmSync(targetDir, { recursive: true, force: true });
11394
+ if (existsSync15(targetDir)) rmSync(targetDir, { recursive: true, force: true });
11416
11395
  cpSync(sourceDir, targetDir, { recursive: true });
11417
11396
  const manifestPath = pluginManifestPath(targetDir);
11418
11397
  const contentHash = hashPluginContent(targetDir, manifest);
@@ -11470,18 +11449,18 @@ function getInstalledPlugin(configDir, name) {
11470
11449
  }
11471
11450
  function listInstalledPlugins(configDir) {
11472
11451
  const root = pluginRoot(configDir);
11473
- if (!existsSync16(root)) return [];
11452
+ if (!existsSync15(root)) return [];
11474
11453
  const state2 = loadState(configDir);
11475
11454
  const out = [];
11476
11455
  for (const entry of readdirSync7(root)) {
11477
- const dir = join12(root, entry);
11456
+ const dir = join10(root, entry);
11478
11457
  try {
11479
11458
  if (!statSync8(dir).isDirectory()) continue;
11480
11459
  } catch {
11481
11460
  continue;
11482
11461
  }
11483
11462
  const manifestPath = pluginManifestPath(dir);
11484
- if (!existsSync16(manifestPath)) continue;
11463
+ if (!existsSync15(manifestPath)) continue;
11485
11464
  try {
11486
11465
  const manifest = readPluginManifest(manifestPath);
11487
11466
  const contentHash = hashPluginContent(dir, manifest);
@@ -11521,7 +11500,7 @@ function uniqueDirs(plugin, rels) {
11521
11500
  for (const rel of rels) {
11522
11501
  const abs = resolve6(plugin.dir, rel);
11523
11502
  if (!abs.startsWith(resolve6(plugin.dir))) continue;
11524
- if (existsSync16(abs)) dirs.add(statSync8(abs).isDirectory() ? abs : dirname7(abs));
11503
+ if (existsSync15(abs)) dirs.add(statSync8(abs).isDirectory() ? abs : dirname7(abs));
11525
11504
  }
11526
11505
  return [...dirs];
11527
11506
  }
@@ -11661,13 +11640,13 @@ function normalizeName(name) {
11661
11640
  function isStringArray(value) {
11662
11641
  return Array.isArray(value) && value.every((item) => typeof item === "string");
11663
11642
  }
11664
- function parseAgentConfig(raw, source, path3) {
11643
+ function parseAgentConfig(raw, source2, path3) {
11665
11644
  if (!raw || typeof raw !== "object") return null;
11666
11645
  const obj = raw;
11667
11646
  if (typeof obj.name !== "string" || obj.name.trim() === "") return null;
11668
11647
  const cfg = {
11669
11648
  name: obj.name.trim(),
11670
- source,
11649
+ source: source2,
11671
11650
  path: path3
11672
11651
  };
11673
11652
  if (typeof obj.description === "string") cfg.description = obj.description;
@@ -11683,15 +11662,15 @@ function parseAgentConfig(raw, source, path3) {
11683
11662
  if (typeof obj.contextPolicy === "string") cfg.contextPolicy = obj.contextPolicy;
11684
11663
  return cfg;
11685
11664
  }
11686
- function loadAgentDir(dir, source) {
11687
- if (!existsSync17(dir)) return [];
11665
+ function loadAgentDir(dir, source2) {
11666
+ if (!existsSync16(dir)) return [];
11688
11667
  const out = [];
11689
11668
  for (const file of readdirSync8(dir)) {
11690
11669
  if (!file.endsWith(".json")) continue;
11691
- const path3 = join13(dir, file);
11670
+ const path3 = join11(dir, file);
11692
11671
  try {
11693
- const parsed = JSON.parse(readFileSync14(path3, "utf-8"));
11694
- const cfg = parseAgentConfig(parsed, source, path3);
11672
+ const parsed = JSON.parse(readFileSync11(path3, "utf-8"));
11673
+ const cfg = parseAgentConfig(parsed, source2, path3);
11695
11674
  if (cfg) out.push(cfg);
11696
11675
  } catch {
11697
11676
  }
@@ -11700,8 +11679,8 @@ function loadAgentDir(dir, source) {
11700
11679
  }
11701
11680
  function getAgentDirs(configDir, cwd = process.cwd()) {
11702
11681
  return {
11703
- user: join13(configDir ?? join13(homedir6(), CONFIG_DIR_NAME), "agents"),
11704
- project: join13(cwd, CONFIG_DIR_NAME, "agents")
11682
+ user: join11(configDir ?? join11(homedir5(), CONFIG_DIR_NAME), "agents"),
11683
+ project: join11(cwd, CONFIG_DIR_NAME, "agents")
11705
11684
  };
11706
11685
  }
11707
11686
  function listAgentConfigs(configDir, cwd = process.cwd()) {
@@ -12204,9 +12183,8 @@ var spawnAgentTool = {
12204
12183
  };
12205
12184
 
12206
12185
  // src/tools/builtin/task-manager.ts
12207
- import { spawn as spawn5 } from "child_process";
12186
+ import { spawn as spawn4 } from "child_process";
12208
12187
  import { randomUUID } from "crypto";
12209
- import { platform as platform5 } from "os";
12210
12188
  var MAX_OUTPUT_CHARS = 1e4;
12211
12189
  var MAX_OUTPUT_BYTES = 64 * 1024;
12212
12190
  var MAX_TASKS = 20;
@@ -12225,14 +12203,16 @@ function decodeField(chunks) {
12225
12203
  }
12226
12204
  function appendOutput(task, field, chunk) {
12227
12205
  const chunks = field === "stdout" ? task._stdoutChunks : task._stderrChunks;
12228
- chunks.push(chunk);
12206
+ chunks.push(chunk.length > MAX_OUTPUT_BYTES ? chunk.subarray(-MAX_OUTPUT_BYTES) : chunk);
12229
12207
  capChunks(chunks);
12230
12208
  task[field] = decodeField(chunks);
12231
12209
  }
12232
12210
  function createTask(command, description) {
12211
+ if ([...tasks.values()].filter((t) => t._active && t.owner === getExecutionConfigDir()).length >= 10) throw new Error("Background task limit reached (10 running per user).");
12212
+ 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.");
12233
12213
  if (tasks.size >= MAX_TASKS) {
12234
12214
  for (const [id2, t] of tasks) {
12235
- if (t.status !== "running") {
12215
+ if (!t._active) {
12236
12216
  tasks.delete(id2);
12237
12217
  break;
12238
12218
  }
@@ -12240,16 +12220,19 @@ function createTask(command, description) {
12240
12220
  }
12241
12221
  const id = randomUUID().slice(0, 8);
12242
12222
  const { shell, args } = shellInvocation(command);
12243
- const proc = spawn5(shell, args, {
12223
+ const proc = spawn4(shell, args, {
12244
12224
  stdio: ["ignore", "pipe", "pipe"],
12245
- detached: false,
12225
+ detached: process.platform !== "win32",
12226
+ windowsHide: true,
12246
12227
  env: { ...process.env, PYTHONUTF8: "1", PYTHONIOENCODING: "utf-8" }
12247
12228
  });
12248
12229
  const task = {
12249
12230
  id,
12231
+ owner: getExecutionConfigDir(),
12250
12232
  command,
12251
12233
  description,
12252
12234
  process: proc,
12235
+ _active: true,
12253
12236
  startTime: Date.now(),
12254
12237
  endTime: null,
12255
12238
  status: "running",
@@ -12262,11 +12245,13 @@ function createTask(command, description) {
12262
12245
  proc.stdout?.on("data", (chunk) => appendOutput(task, "stdout", chunk));
12263
12246
  proc.stderr?.on("data", (chunk) => appendOutput(task, "stderr", chunk));
12264
12247
  proc.on("close", (code) => {
12248
+ task._active = false;
12265
12249
  task.endTime = Date.now();
12266
12250
  task.exitCode = code;
12267
- task.status = code === 0 ? "completed" : "failed";
12251
+ if (task.status !== "stopped") task.status = code === 0 ? "completed" : "failed";
12268
12252
  });
12269
12253
  proc.on("error", (err) => {
12254
+ task._active = false;
12270
12255
  task.endTime = Date.now();
12271
12256
  task.status = "failed";
12272
12257
  task.stderr += `
@@ -12276,43 +12261,20 @@ Process error: ${err.message}`;
12276
12261
  return task;
12277
12262
  }
12278
12263
  function listTasks() {
12279
- return [...tasks.values()].map(
12280
- ({ process: _p, _stdoutChunks: _o, _stderrChunks: _e, ...rest }) => rest
12264
+ return [...tasks.values()].filter((t) => t.owner === getExecutionConfigDir()).map(
12265
+ ({ process: _p, _stdoutChunks: _o, _stderrChunks: _e, _active: _a, ...rest }) => rest
12281
12266
  );
12282
12267
  }
12283
12268
  function getTaskOutput(id) {
12284
12269
  const task = tasks.get(id);
12285
- if (!task) return null;
12270
+ if (!task || task.owner !== getExecutionConfigDir()) return null;
12286
12271
  return { stdout: task.stdout, stderr: task.stderr, status: task.status };
12287
12272
  }
12288
12273
  function stopTask(id) {
12289
12274
  const task = tasks.get(id);
12290
- if (!task || task.status !== "running") return false;
12275
+ if (!task || task.owner !== getExecutionConfigDir() || task.status !== "running") return false;
12291
12276
  try {
12292
- const proc = task.process;
12293
- if (platform5() === "win32" && proc.pid) {
12294
- try {
12295
- spawn5("taskkill", ["/PID", String(proc.pid), "/T", "/F"], {
12296
- windowsHide: true,
12297
- stdio: "ignore"
12298
- });
12299
- } catch {
12300
- }
12301
- try {
12302
- proc.kill();
12303
- } catch {
12304
- }
12305
- } else {
12306
- proc.kill("SIGTERM");
12307
- setTimeout(() => {
12308
- if (proc.exitCode === null && proc.signalCode === null) {
12309
- try {
12310
- proc.kill("SIGKILL");
12311
- } catch {
12312
- }
12313
- }
12314
- }, 3e3).unref();
12315
- }
12277
+ killProcessTree(task.process, true);
12316
12278
  task.status = "stopped";
12317
12279
  task.endTime = Date.now();
12318
12280
  return true;
@@ -12420,14 +12382,14 @@ var taskStopTool = {
12420
12382
 
12421
12383
  // src/tools/builtin/git-tools.ts
12422
12384
  import { execFileSync as execFileSync2 } from "child_process";
12423
- import { existsSync as existsSync18 } from "fs";
12424
- import { join as join14 } from "path";
12385
+ import { existsSync as existsSync17 } from "fs";
12386
+ import { join as join12 } from "path";
12425
12387
  function assertGitRepo(cwd) {
12426
12388
  let dir = cwd;
12427
12389
  const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
12428
12390
  while (dir && dir !== root) {
12429
- if (existsSync18(join14(dir, ".git"))) return;
12430
- const parent = join14(dir, "..");
12391
+ if (existsSync17(join12(dir, ".git"))) return;
12392
+ const parent = join12(dir, "..");
12431
12393
  if (parent === dir) break;
12432
12394
  dir = parent;
12433
12395
  }
@@ -12692,7 +12654,7 @@ ${commitOutput.trim()}`;
12692
12654
  };
12693
12655
 
12694
12656
  // src/tools/builtin/notebook-edit.ts
12695
- import { readFileSync as readFileSync15, existsSync as existsSync19 } from "fs";
12657
+ import { readFileSync as readFileSync12, existsSync as existsSync18 } from "fs";
12696
12658
  import { writeFile } from "fs/promises";
12697
12659
  import { resolve as resolve7, extname as extname2 } from "path";
12698
12660
  var notebookEditTool = {
@@ -12747,10 +12709,10 @@ var notebookEditTool = {
12747
12709
  if (extname2(absPath).toLowerCase() !== ".ipynb") {
12748
12710
  throw new ToolError("notebook_edit", "path must point to a .ipynb file");
12749
12711
  }
12750
- if (!existsSync19(absPath)) {
12712
+ if (!existsSync18(absPath)) {
12751
12713
  throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
12752
12714
  }
12753
- const raw = readFileSync15(absPath, "utf-8");
12715
+ const raw = readFileSync12(absPath, "utf-8");
12754
12716
  const nb = parseNotebook(raw);
12755
12717
  const cellIdx0 = cellIndexRaw - 1;
12756
12718
  undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
@@ -13076,8 +13038,6 @@ ${lines.join("\n")}`;
13076
13038
  };
13077
13039
 
13078
13040
  // src/tools/builtin/recall-memory.ts
13079
- import { join as join15 } from "path";
13080
- import { homedir as homedir7 } from "os";
13081
13041
  function formatHit(h, i) {
13082
13042
  const ts = h.chunk.timestamp.slice(0, 16).replace("T", " ");
13083
13043
  const title = h.chunk.sessionTitle ? ` \xB7 ${h.chunk.sessionTitle}` : "";
@@ -13127,7 +13087,7 @@ var recallMemoryTool = {
13127
13087
  const excludeCurrent = Boolean(args["excludeCurrentSession"]);
13128
13088
  const currentId = args["currentSessionId"] ? String(args["currentSessionId"]) : void 0;
13129
13089
  const minScore = args["minScore"] !== void 0 ? Number(args["minScore"]) : 0.25;
13130
- const configDir = join15(homedir7(), CONFIG_DIR_NAME);
13090
+ const configDir = getExecutionConfigDir();
13131
13091
  let persistentSection = "";
13132
13092
  try {
13133
13093
  const persistentHits = searchPersistentMemories(configDir, query, process.cwd(), 3);
@@ -13181,8 +13141,8 @@ function estimateToolDefinitionTokens(def) {
13181
13141
 
13182
13142
  // src/tools/registry.ts
13183
13143
  import { pathToFileURL } from "url";
13184
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
13185
- import { join as join16 } from "path";
13144
+ import { existsSync as existsSync19, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "fs";
13145
+ import { join as join13 } from "path";
13186
13146
  var ToolRegistry = class {
13187
13147
  tools = /* @__PURE__ */ new Map();
13188
13148
  pluginToolNames = /* @__PURE__ */ new Set();
@@ -13343,7 +13303,7 @@ var ToolRegistry = class {
13343
13303
  * Returns the number of successfully loaded plugins.
13344
13304
  */
13345
13305
  async loadPlugins(pluginsDir, allowPlugins = false) {
13346
- if (!existsSync20(pluginsDir)) {
13306
+ if (!existsSync19(pluginsDir)) {
13347
13307
  try {
13348
13308
  mkdirSync9(pluginsDir, { recursive: true });
13349
13309
  } catch {
@@ -13369,12 +13329,12 @@ var ToolRegistry = class {
13369
13329
  process.stderr.write(
13370
13330
  `
13371
13331
  [plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
13372
- ` + files.map((f) => ` + ${join16(pluginsDir, f)}`).join("\n") + "\n\n"
13332
+ ` + files.map((f) => ` + ${join13(pluginsDir, f)}`).join("\n") + "\n\n"
13373
13333
  );
13374
13334
  let loaded = 0;
13375
13335
  for (const file of files) {
13376
13336
  try {
13377
- const fileUrl = pathToFileURL(join16(pluginsDir, file)).href;
13337
+ const fileUrl = pathToFileURL(join13(pluginsDir, file)).href;
13378
13338
  const mod = await import(fileUrl);
13379
13339
  const tool = mod.tool ?? mod.default?.tool ?? mod.default;
13380
13340
  if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
@@ -13402,7 +13362,7 @@ var ToolRegistry = class {
13402
13362
  };
13403
13363
 
13404
13364
  // src/mcp/client.ts
13405
- import { spawn as spawn6 } from "child_process";
13365
+ import { spawn as spawn5 } from "child_process";
13406
13366
  var McpClient = class {
13407
13367
  serverId;
13408
13368
  config;
@@ -13440,7 +13400,7 @@ var McpClient = class {
13440
13400
  async connect() {
13441
13401
  const timeout = this.config.timeout ?? MCP_CONNECT_TIMEOUT;
13442
13402
  try {
13443
- this.process = spawn6(this.config.command, this.config.args ?? [], {
13403
+ this.process = spawn5(this.config.command, this.config.args ?? [], {
13444
13404
  stdio: ["pipe", "pipe", "pipe"],
13445
13405
  env: { ...process.env, ...this.config.env },
13446
13406
  // Windows 上 npx 等是 .cmd 脚本,需要 shell 模式
@@ -13919,11 +13879,11 @@ var McpManager = class {
13919
13879
  };
13920
13880
 
13921
13881
  // src/skills/manager.ts
13922
- import { existsSync as existsSync21, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync9 } from "fs";
13923
- import { join as join17 } from "path";
13882
+ import { existsSync as existsSync20, readdirSync as readdirSync10, mkdirSync as mkdirSync10, statSync as statSync9 } from "fs";
13883
+ import { join as join14 } from "path";
13924
13884
 
13925
13885
  // src/skills/types.ts
13926
- import { readFileSync as readFileSync16 } from "fs";
13886
+ import { readFileSync as readFileSync13 } from "fs";
13927
13887
  import { basename as basename7 } from "path";
13928
13888
  function parseSimpleYaml(yaml) {
13929
13889
  const result = {};
@@ -13945,7 +13905,7 @@ function parseYamlArray(value) {
13945
13905
  function parseSkillFile(filePath) {
13946
13906
  let raw;
13947
13907
  try {
13948
- raw = readFileSync16(filePath, "utf-8");
13908
+ raw = readFileSync13(filePath, "utf-8");
13949
13909
  } catch {
13950
13910
  return null;
13951
13911
  }
@@ -13993,7 +13953,7 @@ var SkillManager = class {
13993
13953
  return this.skills.size;
13994
13954
  }
13995
13955
  loadSkillDir(dir, createIfMissing) {
13996
- if (!existsSync21(dir)) {
13956
+ if (!existsSync20(dir)) {
13997
13957
  if (createIfMissing) {
13998
13958
  try {
13999
13959
  mkdirSync10(dir, { recursive: true });
@@ -14010,14 +13970,14 @@ var SkillManager = class {
14010
13970
  }
14011
13971
  for (const entry of entries) {
14012
13972
  let filePath;
14013
- const fullPath = join17(dir, entry);
13973
+ const fullPath = join14(dir, entry);
14014
13974
  if (entry.endsWith(".md")) {
14015
13975
  filePath = fullPath;
14016
13976
  } else {
14017
13977
  try {
14018
13978
  if (statSync9(fullPath).isDirectory()) {
14019
- const skillMd = join17(fullPath, "SKILL.md");
14020
- if (existsSync21(skillMd)) filePath = skillMd;
13979
+ const skillMd = join14(fullPath, "SKILL.md");
13980
+ if (existsSync20(skillMd)) filePath = skillMd;
14021
13981
  else continue;
14022
13982
  } else {
14023
13983
  continue;
@@ -14130,7 +14090,7 @@ function sendSocketMessage(ws, message) {
14130
14090
  }
14131
14091
 
14132
14092
  // src/web/tool-executor-web.ts
14133
- import { existsSync as existsSync22, readFileSync as readFileSync17 } from "fs";
14093
+ import { existsSync as existsSync21, readFileSync as readFileSync14 } from "fs";
14134
14094
  var ToolExecutorWeb = class _ToolExecutorWeb {
14135
14095
  constructor(registry, ws) {
14136
14096
  this.registry = registry;
@@ -14277,9 +14237,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
14277
14237
  getDiffPreview(call) {
14278
14238
  if (call.name === "write_file") {
14279
14239
  const { path: filePath, content: chunk, append } = resolveWriteFileArgs(call.arguments);
14280
- if (filePath && existsSync22(filePath)) {
14240
+ if (filePath && existsSync21(filePath)) {
14281
14241
  try {
14282
- const old = readFileSync17(filePath, "utf-8");
14242
+ const old = readFileSync14(filePath, "utf-8");
14283
14243
  return renderDiff(old, append ? old + chunk : chunk, { filePath });
14284
14244
  } catch {
14285
14245
  }
@@ -14515,16 +14475,16 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
14515
14475
  };
14516
14476
 
14517
14477
  // src/repl/dev-state.ts
14518
- import { existsSync as existsSync23, readFileSync as readFileSync18, unlinkSync as unlinkSync5, mkdirSync as mkdirSync11 } from "fs";
14519
- import { join as join18 } from "path";
14520
- import { homedir as homedir8 } from "os";
14478
+ import { existsSync as existsSync22, readFileSync as readFileSync15, unlinkSync as unlinkSync5, mkdirSync as mkdirSync11 } from "fs";
14479
+ import { join as join15 } from "path";
14480
+ import { homedir as homedir6 } from "os";
14521
14481
  function getDevStatePath() {
14522
- return join18(homedir8(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
14482
+ return join15(homedir6(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
14523
14483
  }
14524
14484
  function loadDevState() {
14525
14485
  const path3 = getDevStatePath();
14526
- if (!existsSync23(path3)) return null;
14527
- const content = readFileSync18(path3, "utf-8").trim();
14486
+ if (!existsSync22(path3)) return null;
14487
+ const content = readFileSync15(path3, "utf-8").trim();
14528
14488
  return content || null;
14529
14489
  }
14530
14490
 
@@ -14894,8 +14854,8 @@ var checkpointDeletedMessage = (name) => `\u2713 Deleted checkpoint "${name}"`;
14894
14854
  var checkpointNotFoundMessage = (name) => `Checkpoint "${name}" not found.`;
14895
14855
 
14896
14856
  // src/core/context-files.ts
14897
- import { existsSync as existsSync24, readFileSync as readFileSync19 } from "fs";
14898
- import { join as join19, relative as relative4, resolve as resolve8 } from "path";
14857
+ import { existsSync as existsSync23, readFileSync as readFileSync16 } from "fs";
14858
+ import { join as join16, relative as relative4, resolve as resolve8 } from "path";
14899
14859
  function uniqueNonEmpty(values) {
14900
14860
  const seen = /* @__PURE__ */ new Set();
14901
14861
  const out = [];
@@ -14925,7 +14885,7 @@ function readContextFile(level, filePath, cwd, maxBytes) {
14925
14885
  const name = filePath.split(/[\\/]/).pop() ?? filePath;
14926
14886
  const shown = displayPath(filePath, cwd);
14927
14887
  try {
14928
- const raw = readFileSync19(filePath);
14888
+ const raw = readFileSync16(filePath);
14929
14889
  const byteCount = raw.byteLength;
14930
14890
  if (byteCount === 0) {
14931
14891
  return {
@@ -14972,8 +14932,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
14972
14932
  function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
14973
14933
  const skipped = [];
14974
14934
  for (const candidate of candidates) {
14975
- const filePath = join19(dir, candidate);
14976
- if (!existsSync24(filePath)) continue;
14935
+ const filePath = join16(dir, candidate);
14936
+ if (!existsSync23(filePath)) continue;
14977
14937
  const result = readContextFile(level, filePath, cwd, maxBytes);
14978
14938
  if (result.skipped) skipped.push(result.skipped);
14979
14939
  if (result.layer) return { layer: result.layer, skipped };
@@ -15000,7 +14960,7 @@ function loadContextFiles(options) {
15000
14960
  });
15001
14961
  return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
15002
14962
  }
15003
- if (!existsSync24(filePath)) {
14963
+ if (!existsSync23(filePath)) {
15004
14964
  skipped.push({
15005
14965
  level: "single",
15006
14966
  filePath,
@@ -15113,8 +15073,8 @@ import { mkdirSync as mkdirSync15, createWriteStream as createWriteStream2 } fro
15113
15073
  import { dirname as dirname10 } from "path";
15114
15074
 
15115
15075
  // src/repl/commands/project-init.ts
15116
- import { existsSync as existsSync25, readFileSync as readFileSync20, readdirSync as readdirSync11, statSync as statSync11 } from "fs";
15117
- import { join as join20 } from "path";
15076
+ import { existsSync as existsSync24, readFileSync as readFileSync17, readdirSync as readdirSync11, statSync as statSync11 } from "fs";
15077
+ import { join as join17 } from "path";
15118
15078
  var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
15119
15079
  "node_modules",
15120
15080
  ".git",
@@ -15158,11 +15118,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
15158
15118
  const sorted = filtered.sort((a, b) => {
15159
15119
  let aIsDir = false, bIsDir = false;
15160
15120
  try {
15161
- aIsDir = statSync11(join20(d, a)).isDirectory();
15121
+ aIsDir = statSync11(join17(d, a)).isDirectory();
15162
15122
  } catch {
15163
15123
  }
15164
15124
  try {
15165
- bIsDir = statSync11(join20(d, b)).isDirectory();
15125
+ bIsDir = statSync11(join17(d, b)).isDirectory();
15166
15126
  } catch {
15167
15127
  }
15168
15128
  if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
@@ -15170,7 +15130,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
15170
15130
  });
15171
15131
  for (let i = 0; i < sorted.length && count < maxEntries; i++) {
15172
15132
  const name = sorted[i];
15173
- const fullPath = join20(d, name);
15133
+ const fullPath = join17(d, name);
15174
15134
  const isLast = i === sorted.length - 1;
15175
15135
  const connector = isLast ? "+-- " : "|-- ";
15176
15136
  let isDir;
@@ -15197,7 +15157,7 @@ function scanProject(cwd) {
15197
15157
  configFiles: [],
15198
15158
  directoryStructure: ""
15199
15159
  };
15200
- const check = (file) => existsSync25(join20(cwd, file));
15160
+ const check = (file) => existsSync24(join17(cwd, file));
15201
15161
  const configCandidates = [
15202
15162
  "package.json",
15203
15163
  "tsconfig.json",
@@ -15224,7 +15184,7 @@ function scanProject(cwd) {
15224
15184
  info.type = "node";
15225
15185
  info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
15226
15186
  try {
15227
- const pkg = JSON.parse(readFileSync20(join20(cwd, "package.json"), "utf-8"));
15187
+ const pkg = JSON.parse(readFileSync17(join17(cwd, "package.json"), "utf-8"));
15228
15188
  const scripts = pkg.scripts ?? {};
15229
15189
  info.buildCommand = scripts.build ? "npm run build" : void 0;
15230
15190
  info.testCommand = scripts.test ? "npm test" : void 0;
@@ -15992,7 +15952,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
15992
15952
  }
15993
15953
 
15994
15954
  // src/hub/persist.ts
15995
- import { join as join21 } from "path";
15955
+ import { join as join18 } from "path";
15996
15956
  function discussionToMessages(state2) {
15997
15957
  const out = [];
15998
15958
  const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
@@ -16030,7 +15990,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
16030
15990
  session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
16031
15991
  session.titleAiGenerated = true;
16032
15992
  await sm.save();
16033
- return { id: session.id, path: join21(config.getHistoryDir(), `${session.id}.json`) };
15993
+ return { id: session.id, path: join18(config.getHistoryDir(), `${session.id}.json`) };
16034
15994
  }
16035
15995
 
16036
15996
  // src/core/think-command.ts
@@ -16151,10 +16111,10 @@ var PLAN_MODE_TOOL_HINT = [
16151
16111
  "read_file \xB7 list_dir \xB7 grep_files \xB7 glob_files",
16152
16112
  "web_fetch \xB7 google_search \xB7 ask_user \xB7 write_todos"
16153
16113
  ];
16154
- function applyPlanCommand(sub, active) {
16155
- const key = (sub ?? "enter").toLowerCase() === "toggle" ? active ? "exit" : "enter" : (sub ?? "enter").toLowerCase();
16114
+ function applyPlanCommand(sub, active2) {
16115
+ const key = (sub ?? "enter").toLowerCase() === "toggle" ? active2 ? "exit" : "enter" : (sub ?? "enter").toLowerCase();
16156
16116
  if (key === "enter") {
16157
- if (active) {
16117
+ if (active2) {
16158
16118
  return {
16159
16119
  planMode: true,
16160
16120
  changed: false,
@@ -16177,7 +16137,7 @@ function applyPlanCommand(sub, active) {
16177
16137
  };
16178
16138
  }
16179
16139
  if (key === "execute") {
16180
- if (!active) {
16140
+ if (!active2) {
16181
16141
  return { planMode: false, changed: false, status: "not-active", summary: "Not in Plan Mode. Enter first with /plan", details: [] };
16182
16142
  }
16183
16143
  return {
@@ -16189,22 +16149,22 @@ function applyPlanCommand(sub, active) {
16189
16149
  };
16190
16150
  }
16191
16151
  if (key === "exit" || key === "cancel") {
16192
- if (!active) {
16152
+ if (!active2) {
16193
16153
  return { planMode: false, changed: false, status: "not-active", summary: "Not in Plan Mode.", details: [] };
16194
16154
  }
16195
16155
  return { planMode: false, changed: true, status: "cancelled", summary: "Plan Mode cancelled. Returning to normal mode.", details: [] };
16196
16156
  }
16197
16157
  if (key === "status" || key === "show") {
16198
16158
  return {
16199
- planMode: active,
16159
+ planMode: active2,
16200
16160
  changed: false,
16201
16161
  status: "status",
16202
- summary: `Plan Mode: ${active ? "ACTIVE \u{1F4CB}" : "inactive"}`,
16203
- details: active ? ["Tools restricted to read-only set.", "Use /plan execute to start executing, or /plan exit to cancel."] : []
16162
+ summary: `Plan Mode: ${active2 ? "ACTIVE \u{1F4CB}" : "inactive"}`,
16163
+ details: active2 ? ["Tools restricted to read-only set.", "Use /plan execute to start executing, or /plan exit to cancel."] : []
16204
16164
  };
16205
16165
  }
16206
16166
  return {
16207
- planMode: active,
16167
+ planMode: active2,
16208
16168
  changed: false,
16209
16169
  status: "unknown",
16210
16170
  summary: `Unknown /plan option "${sub}" \u2014 expected execute | exit | status`,
@@ -16476,8 +16436,8 @@ function buildSessionCostReport(input) {
16476
16436
  const cacheHitPct = usage.cacheReadTokens > 0 ? Math.round(usage.cacheReadTokens / (usage.cacheReadTokens + usage.inputTokens) * 100) : null;
16477
16437
  return { hasUsage: true, totalTokens, cacheHitPct, cost, costSource, savings };
16478
16438
  }
16479
- function costSourceNote(source) {
16480
- switch (source) {
16439
+ function costSourceNote(source2) {
16440
+ switch (source2) {
16481
16441
  case "accrued-partial":
16482
16442
  return "(partial \u2014 some turns had unknown pricing)";
16483
16443
  case "estimated":
@@ -16488,8 +16448,8 @@ function costSourceNote(source) {
16488
16448
  }
16489
16449
 
16490
16450
  // src/web/commands/_shared.ts
16491
- import { existsSync as existsSync28, readFileSync as readFileSync22, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, statSync as statSync14, createWriteStream } from "fs";
16492
- import { join as join24, resolve as resolve9, dirname as dirname8 } from "path";
16451
+ import { existsSync as existsSync27, readFileSync as readFileSync19, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, statSync as statSync14, createWriteStream } from "fs";
16452
+ import { join as join21, resolve as resolve9, dirname as dirname8 } from "path";
16493
16453
 
16494
16454
  // src/cli/review-prompts.ts
16495
16455
  function buildReviewPrompt(diff, gitContextStr, detailed) {
@@ -16561,34 +16521,34 @@ function readGitDiff(options = {}) {
16561
16521
  }
16562
16522
 
16563
16523
  // src/diagnostics/doctor-report.ts
16564
- import { existsSync as existsSync27, statSync as statSync13 } from "fs";
16565
- import { join as join23 } from "path";
16566
- import { arch as arch2, platform as platform7, release as release2 } from "os";
16524
+ import { existsSync as existsSync26, statSync as statSync13 } from "fs";
16525
+ import { join as join20 } from "path";
16526
+ import { arch as arch2, platform as platform5, release as release2 } from "os";
16567
16527
 
16568
16528
  // src/diagnostics/crash-log.ts
16569
16529
  import {
16570
- existsSync as existsSync26,
16530
+ existsSync as existsSync25,
16571
16531
  mkdirSync as mkdirSync12,
16572
16532
  readdirSync as readdirSync12,
16573
- readFileSync as readFileSync21,
16533
+ readFileSync as readFileSync18,
16574
16534
  statSync as statSync12,
16575
16535
  unlinkSync as unlinkSync6,
16576
16536
  writeFileSync as writeFileSync2
16577
16537
  } from "fs";
16578
- import { join as join22 } from "path";
16579
- import { homedir as homedir9, platform as platform6, release, arch } from "os";
16538
+ import { join as join19 } from "path";
16539
+ import { homedir as homedir7, platform as platform4, release, arch } from "os";
16580
16540
  var LOGS_DIR_NAME = "logs";
16581
16541
  var CRASH_LOG_PREFIX = "crash-";
16582
16542
  function getLogsDir(configDir) {
16583
- const base = configDir ?? join22(homedir9(), CONFIG_DIR_NAME);
16584
- return join22(base, LOGS_DIR_NAME);
16543
+ const base = configDir ?? join19(homedir7(), CONFIG_DIR_NAME);
16544
+ return join19(base, LOGS_DIR_NAME);
16585
16545
  }
16586
16546
  function truncate(s, n) {
16587
16547
  return s.length <= n ? s : s.slice(0, n) + "\u2026";
16588
16548
  }
16589
16549
  function listRecentCrashes(limit = 10, configDir) {
16590
16550
  const dir = getLogsDir(configDir);
16591
- if (!existsSync26(dir)) return [];
16551
+ if (!existsSync25(dir)) return [];
16592
16552
  const files = [];
16593
16553
  let names;
16594
16554
  try {
@@ -16598,10 +16558,10 @@ function listRecentCrashes(limit = 10, configDir) {
16598
16558
  }
16599
16559
  for (const name of names) {
16600
16560
  if (!name.startsWith(CRASH_LOG_PREFIX) || !name.endsWith(".log")) continue;
16601
- const full = join22(dir, name);
16561
+ const full = join19(dir, name);
16602
16562
  try {
16603
16563
  const st = statSync12(full);
16604
- const head = readFileSync21(full, "utf-8").split("\n").slice(0, 20);
16564
+ const head = readFileSync18(full, "utf-8").split("\n").slice(0, 20);
16605
16565
  const tsLine = head.find((l) => l.startsWith("timestamp:"));
16606
16566
  const kindLine = head.find((l) => l.startsWith("kind:"));
16607
16567
  const msgIdx = head.findIndex((l) => l.trim() === "## message");
@@ -16620,8 +16580,8 @@ function listRecentCrashes(limit = 10, configDir) {
16620
16580
  return files.slice(0, limit);
16621
16581
  }
16622
16582
  function getConfigDirUsage(configDir) {
16623
- const base = configDir ?? join22(homedir9(), CONFIG_DIR_NAME);
16624
- if (!existsSync26(base)) return { totalBytes: 0, entries: [] };
16583
+ const base = configDir ?? join19(homedir7(), CONFIG_DIR_NAME);
16584
+ if (!existsSync25(base)) return { totalBytes: 0, entries: [] };
16625
16585
  const entries = [];
16626
16586
  let total = 0;
16627
16587
  let names;
@@ -16631,7 +16591,7 @@ function getConfigDirUsage(configDir) {
16631
16591
  return { totalBytes: 0, entries: [] };
16632
16592
  }
16633
16593
  for (const name of names) {
16634
- const full = join22(base, name);
16594
+ const full = join19(base, name);
16635
16595
  const bytes = dirSize(full);
16636
16596
  entries.push({ name, bytes });
16637
16597
  total += bytes;
@@ -16646,7 +16606,7 @@ function dirSize(path3) {
16646
16606
  if (!st.isDirectory()) return 0;
16647
16607
  let sum = 0;
16648
16608
  for (const name of readdirSync12(path3)) {
16649
- sum += dirSize(join22(path3, name));
16609
+ sum += dirSize(join19(path3, name));
16650
16610
  }
16651
16611
  return sum;
16652
16612
  } catch {
@@ -16656,7 +16616,7 @@ function dirSize(path3) {
16656
16616
 
16657
16617
  // src/diagnostics/doctor-report.ts
16658
16618
  function checkFile(label, path3) {
16659
- const exists = existsSync27(path3);
16619
+ const exists = existsSync26(path3);
16660
16620
  let sizeBytes = null;
16661
16621
  if (exists) {
16662
16622
  try {
@@ -16693,18 +16653,18 @@ function buildDoctorReport(options) {
16693
16653
  version: VERSION,
16694
16654
  npmCheck: options.npmCheck,
16695
16655
  node: process.version,
16696
- platform: `${platform7()} ${release2()} (${arch2()})`,
16656
+ platform: `${platform5()} ${release2()} (${arch2()})`,
16697
16657
  cwd,
16698
16658
  projectRoot,
16699
16659
  configDir,
16700
16660
  providers: options.providers.listAll().map((p) => ({ id: p.id, displayName: p.displayName, configured: p.configured })),
16701
16661
  files: [
16702
- checkFile("config.json", join23(configDir, "config.json")),
16703
- checkFile("memory.md", join23(configDir, MEMORY_FILE_NAME)),
16704
- checkFile("memory.jsonl", join23(configDir, "memory.jsonl")),
16705
- checkFile("dev-state.md", join23(configDir, DEV_STATE_FILE_NAME)),
16706
- checkFile("hooks-trust.json", join23(configDir, "hooks-trust.json")),
16707
- checkFile("plugin-state.json", join23(configDir, "plugins", "plugin-state.json"))
16662
+ checkFile("config.json", join20(configDir, "config.json")),
16663
+ checkFile("memory.md", join20(configDir, MEMORY_FILE_NAME)),
16664
+ checkFile("memory.jsonl", join20(configDir, "memory.jsonl")),
16665
+ checkFile("dev-state.md", join20(configDir, DEV_STATE_FILE_NAME)),
16666
+ checkFile("hooks-trust.json", join20(configDir, "hooks-trust.json")),
16667
+ checkFile("plugin-state.json", join20(configDir, "plugins", "plugin-state.json"))
16708
16668
  ],
16709
16669
  context: {
16710
16670
  enabled: contextSetting !== false,
@@ -16739,7 +16699,7 @@ function buildDoctorReport(options) {
16739
16699
  disabled: hooks.filter((h) => h.disabled).length
16740
16700
  },
16741
16701
  plugins: {
16742
- root: join23(configDir, "plugins"),
16702
+ root: join20(configDir, "plugins"),
16743
16703
  installed: plugins.length,
16744
16704
  active: activeAssets.plugins.length,
16745
16705
  invalid: plugins.filter((p) => !p.valid).length,
@@ -16754,7 +16714,7 @@ function buildDoctorReport(options) {
16754
16714
  mcp: {
16755
16715
  enabled: config.get("mcpEnabled"),
16756
16716
  configured: Object.keys(mcpServers).length,
16757
- projectConfigExists: existsSync27(join23(projectRoot, MCP_PROJECT_CONFIG_NAME)),
16717
+ projectConfigExists: existsSync26(join20(projectRoot, MCP_PROJECT_CONFIG_NAME)),
16758
16718
  statuses: options.mcpStatuses ?? []
16759
16719
  },
16760
16720
  recentCrashes: listRecentCrashes(5, configDir),
@@ -17276,7 +17236,7 @@ Cache: write=${cacheCreate} read=${cacheRead}` : "";
17276
17236
  Cost: ${formatCost(cost)}` : "";
17277
17237
  let memoryLine = "";
17278
17238
  try {
17279
- const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-XZISXRWT.js");
17239
+ const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-UNLCXUWH.js");
17280
17240
  const pending = countPendingMemories2(ctx.config.getConfigDir());
17281
17241
  if (pending > 0) memoryLine = `
17282
17242
  Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
@@ -17899,7 +17859,7 @@ async function handleRewind(args, ctx) {
17899
17859
  ctx.send({ type: "error", message: intent.message });
17900
17860
  return;
17901
17861
  }
17902
- const { fileCheckpoints: fileCheckpoints2 } = await import("./file-checkpoint-CGH6OJVI.js");
17862
+ const { fileCheckpoints: fileCheckpoints2 } = await import("./file-checkpoint-64A2AXHE.js");
17903
17863
  if (intent.kind === "list") {
17904
17864
  const rows = buildRewindList(session.messages, fileCheckpoints2.getMessageIndices());
17905
17865
  ctx.send({
@@ -17956,9 +17916,9 @@ function parseAgentCommand(args) {
17956
17916
  function formatAgentList(agents, preferred) {
17957
17917
  const lines = [`Agents (${agents.length})`, ""];
17958
17918
  for (const agent of agents) {
17959
- const active = agent.name === preferred ? " *" : " ";
17919
+ const active2 = agent.name === preferred ? " *" : " ";
17960
17920
  const model = [agent.provider, agent.model].filter(Boolean).join("/") || "inherit";
17961
- lines.push(`${active} ${agent.name} [${agent.source ?? "builtin"}] \u2014 ${agent.description ?? ""}`);
17921
+ lines.push(`${active2} ${agent.name} [${agent.source ?? "builtin"}] \u2014 ${agent.description ?? ""}`);
17962
17922
  lines.push(
17963
17923
  ` model: ${model} \xB7 permission: ${agent.permissionProfile ?? "workspace-write"} \xB7 maxRounds: ${agent.maxToolRounds ?? "inherit"}`
17964
17924
  );
@@ -18017,7 +17977,7 @@ async function handlePlugin(args, ctx) {
18017
17977
  lines.push("");
18018
17978
  }
18019
17979
  if (pluginTools.length > 0) {
18020
- const legacyDir = join24(configDir, PLUGINS_DIR_NAME);
17980
+ const legacyDir = join21(configDir, PLUGINS_DIR_NAME);
18021
17981
  lines.push(`**Legacy JS tools** (${pluginTools.length}):`);
18022
17982
  lines.push(`Dir: ${legacyDir}`);
18023
17983
  for (const t of pluginTools) lines.push(` \u2022 ${t.definition.name} \u2014 ${t.definition.description}`);
@@ -18099,14 +18059,14 @@ async function handleSkill(args, ctx) {
18099
18059
  return;
18100
18060
  }
18101
18061
  const intent = parseSkillCommand(args);
18102
- const active = ctx.skillManager.getActive();
18062
+ const active2 = ctx.skillManager.getActive();
18103
18063
  if (intent.kind === "list") {
18104
18064
  const skills = ctx.skillManager.listSkills();
18105
18065
  if (skills.length === 0) {
18106
18066
  ctx.send({ type: "info", message: "No skills available. Add .md files to ~/.aicli/skills/" });
18107
18067
  } else {
18108
18068
  const lines = skills.map((s) => {
18109
- const isActive = active?.meta.name === s.meta.name;
18069
+ const isActive = active2?.meta.name === s.meta.name;
18110
18070
  const marker = isActive ? " \u2705 (active)" : "";
18111
18071
  return ` ${s.meta.name}${marker} \u2014 ${s.meta.description || "(no description)"}`;
18112
18072
  });
@@ -18122,12 +18082,12 @@ ${lines.join("\n")}` });
18122
18082
  return;
18123
18083
  }
18124
18084
  const note = skillHistoryNote(
18125
- active?.meta.name ?? "(none)",
18085
+ active2?.meta.name ?? "(none)",
18126
18086
  intent.kind === "deactivate" ? "(none)" : intent.name,
18127
18087
  ctx.sessions.current?.messages.length ?? 0
18128
18088
  );
18129
18089
  if (intent.kind === "deactivate") {
18130
- if (!active) {
18090
+ if (!active2) {
18131
18091
  ctx.send({ type: "info", message: "No skill is currently active." });
18132
18092
  return;
18133
18093
  }
@@ -18138,7 +18098,7 @@ ${note}` : "\u{1F50C} Skill deactivated." });
18138
18098
  ctx.sendStatus();
18139
18099
  return;
18140
18100
  }
18141
- if (active?.meta.name === intent.name) {
18101
+ if (active2?.meta.name === intent.name) {
18142
18102
  ctx.send({ type: "info", message: `Skill '${intent.name}' is already active.` });
18143
18103
  return;
18144
18104
  }
@@ -18157,8 +18117,8 @@ ${note}` : head });
18157
18117
  }
18158
18118
  async function handleCommands(_args, ctx) {
18159
18119
  const configDir = ctx.config.getConfigDir();
18160
- const commandsDir = join24(configDir, CUSTOM_COMMANDS_DIR_NAME);
18161
- if (!existsSync28(commandsDir)) {
18120
+ const commandsDir = join21(configDir, CUSTOM_COMMANDS_DIR_NAME);
18121
+ if (!existsSync27(commandsDir)) {
18162
18122
  ctx.send({ type: "info", message: `No custom commands directory.
18163
18123
  Create: ${commandsDir}/ with .md files.` });
18164
18124
  return;
@@ -18264,7 +18224,7 @@ async function handleTest(args, ctx) {
18264
18224
  const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
18265
18225
  testArgs = isCommand ? { command: argStr } : { filter: argStr };
18266
18226
  }
18267
- const runTests = ctx.runTests ?? (await import("./run-tests-UEE3OMLS.js")).executeTests;
18227
+ const runTests = ctx.runTests ?? (await import("./run-tests-KRNMFXAN.js")).executeTests;
18268
18228
  const report = await runTests(testArgs);
18269
18229
  ctx.send({ type: "info", message: report });
18270
18230
  } catch (err) {
@@ -18288,9 +18248,9 @@ async function handleDiff(args, ctx) {
18288
18248
  let modifiedFiles = 0;
18289
18249
  const diffLines2 = [];
18290
18250
  for (const [filePath, { earliest }] of fileMap) {
18291
- const currentContent = existsSync28(filePath) ? (() => {
18251
+ const currentContent = existsSync27(filePath) ? (() => {
18292
18252
  try {
18293
- return readFileSync22(filePath, "utf-8");
18253
+ return readFileSync19(filePath, "utf-8");
18294
18254
  } catch {
18295
18255
  return null;
18296
18256
  }
@@ -18325,9 +18285,9 @@ async function handleDiff(args, ctx) {
18325
18285
  }
18326
18286
  async function handleInit(args, ctx) {
18327
18287
  const cwd = process.cwd();
18328
- const targetPath = join24(cwd, "AICLI.md");
18288
+ const targetPath = join21(cwd, "AICLI.md");
18329
18289
  const force = args.includes("--force");
18330
- if (existsSync28(targetPath) && !force) {
18290
+ if (existsSync27(targetPath) && !force) {
18331
18291
  ctx.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
18332
18292
  Use /init --force to overwrite.` });
18333
18293
  return;
@@ -18556,9 +18516,9 @@ async function handleContext(_args, ctx) {
18556
18516
  async function handleIndex(args, ctx) {
18557
18517
  const sub = (args[0] ?? "status").toLowerCase();
18558
18518
  const root = process.cwd();
18559
- const { loadIndex: loadIndex2, clearIndex } = await import("./store-SIKHCUYJ.js");
18560
- const { indexProject: indexProject2 } = await import("./indexer-FPKY7LCT.js");
18561
- const { loadVectorStore, clearVectorStore } = await import("./vector-store-JAXWDZYW.js");
18519
+ const { loadIndex: loadIndex2, clearIndex } = await import("./store-2N6PPPBP.js");
18520
+ const { indexProject: indexProject2 } = await import("./indexer-VRXDZ74A.js");
18521
+ const { loadVectorStore, clearVectorStore } = await import("./vector-store-T2DVUMY4.js");
18562
18522
  if (sub === "status") {
18563
18523
  const idx = loadIndex2(root);
18564
18524
  const vec = loadVectorStore(root);
@@ -18608,7 +18568,7 @@ async function handleIndex(args, ctx) {
18608
18568
  message: `Building semantic index for ${idx.symbolCount} symbols\u2026 (first run downloads ~117 MB model)`
18609
18569
  });
18610
18570
  try {
18611
- const { rebuildSemanticIndex } = await import("./semantic-QOBAXEQ4.js");
18571
+ const { rebuildSemanticIndex } = await import("./semantic-TRQAZSRY.js");
18612
18572
  const stats = await rebuildSemanticIndex(root);
18613
18573
  const first = stats.modelFirstLoadMs ? ` (model load+first batch ${stats.modelFirstLoadMs}ms)` : "";
18614
18574
  ctx.send({
@@ -18639,7 +18599,7 @@ async function handleMemory(args, ctx) {
18639
18599
  if (intent.kind === "rebuild") {
18640
18600
  ctx.send({ type: "info", message: rebuildStartMessage(intent.full) });
18641
18601
  try {
18642
- const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-DGUOUUQW.js");
18602
+ const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
18643
18603
  const stats = await buildChatIndex2({ full: intent.full });
18644
18604
  ctx.send({ type: "info", message: rebuildDoneMessage(stats) });
18645
18605
  } catch (err) {
@@ -18648,14 +18608,14 @@ async function handleMemory(args, ctx) {
18648
18608
  return;
18649
18609
  }
18650
18610
  if (intent.kind === "index-clear") {
18651
- const { clearChatIndex: clearChatIndex2 } = await import("./chat-index-DGUOUUQW.js");
18611
+ const { clearChatIndex: clearChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
18652
18612
  clearChatIndex2();
18653
18613
  ctx.send({ type: "info", message: "Chat memory index cleared (persistent memory untouched)." });
18654
18614
  return;
18655
18615
  }
18656
18616
  if (intent.kind === "status") {
18657
- const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-DGUOUUQW.js");
18658
- const { listMemoryEntries: listMemoryEntries2, isMemoryExpired: isMemoryExpired2, formatMemoryForPrompt: formatMemoryForPrompt2 } = await import("./persistent-memory-XZISXRWT.js");
18617
+ const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-RLVLQRRC.js");
18618
+ const { listMemoryEntries: listMemoryEntries2, isMemoryExpired: isMemoryExpired2, formatMemoryForPrompt: formatMemoryForPrompt2 } = await import("./persistent-memory-UNLCXUWH.js");
18659
18619
  const memories = listMemoryEntries2(configDir, { includeExpired: true, includeRejected: true });
18660
18620
  const injected = formatMemoryForPrompt2(configDir, process.cwd(), ctx.config.get("memory")?.maxChars);
18661
18621
  const view = buildMemoryStatusView({
@@ -18678,8 +18638,8 @@ async function handleMemory(args, ctx) {
18678
18638
  return;
18679
18639
  }
18680
18640
  if (intent.kind === "recall") {
18681
- const { searchPersistentMemories: searchPersistentMemories2, touchMemoryReferences: touchMemoryReferences2 } = await import("./persistent-memory-XZISXRWT.js");
18682
- const { searchChatMemory: searchChatMemory2 } = await import("./chat-index-DGUOUUQW.js");
18641
+ const { searchPersistentMemories: searchPersistentMemories2, touchMemoryReferences: touchMemoryReferences2 } = await import("./persistent-memory-UNLCXUWH.js");
18642
+ const { searchChatMemory: searchChatMemory2 } = await import("./chat-index-RLVLQRRC.js");
18683
18643
  const persistentHits = searchPersistentMemories2(configDir, intent.query, process.cwd(), 3);
18684
18644
  if (persistentHits.length > 0) {
18685
18645
  touchMemoryReferences2(configDir, persistentHits.map((h) => h.entry.id));
@@ -18799,7 +18759,7 @@ async function handleMemory(args, ctx) {
18799
18759
  ctx.handleMemoryManage(intent.action, args[1], intent.action === "expire" ? args[2] : void 0);
18800
18760
  } else if (intent.kind === "export") {
18801
18761
  if (args[1] === "md") {
18802
- const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-XZISXRWT.js");
18762
+ const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-UNLCXUWH.js");
18803
18763
  ctx.send({
18804
18764
  type: "export_data",
18805
18765
  format: "md",
@@ -18837,7 +18797,7 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
18837
18797
  return;
18838
18798
  }
18839
18799
  const dirPath = resolve9(sub);
18840
- if (!existsSync28(dirPath)) {
18800
+ if (!existsSync27(dirPath)) {
18841
18801
  ctx.send({ type: "error", message: `Directory not found: ${dirPath}` });
18842
18802
  return;
18843
18803
  }
@@ -19201,7 +19161,7 @@ async function handleAgent(args, ctx) {
19201
19161
 
19202
19162
  // src/repl/commands/utils.ts
19203
19163
  import { execSync as execSync2 } from "child_process";
19204
- import { platform as platform8 } from "os";
19164
+ import { platform as platform6 } from "os";
19205
19165
 
19206
19166
  // src/repl/commands/constants.ts
19207
19167
  var SESSION_PAGE_SIZE = 20;
@@ -19214,7 +19174,7 @@ function fmtCtx(tokens) {
19214
19174
  return `${tokens}`;
19215
19175
  }
19216
19176
  function copyToClipboard(text) {
19217
- const plat = platform8();
19177
+ const plat = platform6();
19218
19178
  if (plat === "win32") {
19219
19179
  execSync2("clip", { input: text, stdio: ["pipe", "ignore", "ignore"] });
19220
19180
  } else if (plat === "darwin") {
@@ -19801,16 +19761,16 @@ No tools match "${filter}".
19801
19761
  usage: "/mcp [reconnect [serverId] | trust-project]",
19802
19762
  async execute(args, ctx) {
19803
19763
  if (args[0] === "trust-project") {
19804
- const { join: join29 } = await import("path");
19805
- const { existsSync: existsSync33 } = await import("fs");
19764
+ const { join: join26 } = await import("path");
19765
+ const { existsSync: existsSync32 } = await import("fs");
19806
19766
  const { getGitRoot: getGitRoot2 } = await import("./git-context-VNCA2GU7.js");
19807
- const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-AXBD2EEB.js");
19808
- const { approveProject, hashMcpFile } = await import("./project-trust-MUG325AW.js");
19767
+ const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-FG27DQMS.js");
19768
+ const { approveProject, hashMcpFile } = await import("./project-trust-6CJB5ZMX.js");
19809
19769
  const cwd = process.cwd();
19810
19770
  const projectRoot = getGitRoot2(cwd) ?? cwd;
19811
- const mcpPath = join29(projectRoot, MCP_PROJECT_CONFIG_NAME2);
19771
+ const mcpPath = join26(projectRoot, MCP_PROJECT_CONFIG_NAME2);
19812
19772
  console.log();
19813
- if (!existsSync33(mcpPath)) {
19773
+ if (!existsSync32(mcpPath)) {
19814
19774
  console.log(theme.dim(` No .mcp.json in ${projectRoot}.`));
19815
19775
  console.log();
19816
19776
  return;
@@ -19883,8 +19843,8 @@ No tools match "${filter}".
19883
19843
  const errorStr = s.error ? theme.error(`
19884
19844
  Error: ${s.error}`) : "";
19885
19845
  const serverLabel = s.serverName !== s.serverId ? ` (${s.serverName})` : "";
19886
- const source = ctx.getMcpServerSource(s.serverId);
19887
- const sourceTag = source === "project" ? theme.accent(" [project]") : theme.dim(" [global]");
19846
+ const source2 = ctx.getMcpServerSource(s.serverId);
19847
+ const sourceTag = source2 === "project" ? theme.accent(" [project]") : theme.dim(" [global]");
19888
19848
  console.log(` ${theme.accent(s.serverId)}${theme.dim(serverLabel)}${sourceTag} [${statusBadge}]${toolCountStr}${errorStr}`);
19889
19849
  if (s.connected) {
19890
19850
  const mcpTools = ctx.tools.listMcpTools().filter(
@@ -19912,7 +19872,7 @@ No tools match "${filter}".
19912
19872
  }
19913
19873
  if (!sub || sub === "list") {
19914
19874
  const skills = manager.listSkills();
19915
- const active = manager.getActive();
19875
+ const active2 = manager.getActive();
19916
19876
  console.log();
19917
19877
  console.log(theme.heading(" \u{1F3AF} Agent Skills"));
19918
19878
  console.log(theme.dim(" " + "\u2500".repeat(50)));
@@ -19922,7 +19882,7 @@ No tools match "${filter}".
19922
19882
  console.log(theme.dim(" Format: YAML frontmatter (name/description/tools) + prompt body"));
19923
19883
  } else {
19924
19884
  for (const s of skills) {
19925
- const isActive = active?.meta.name === s.meta.name;
19885
+ const isActive = active2?.meta.name === s.meta.name;
19926
19886
  const badge = isActive ? theme.success(" \u2713 active") : "";
19927
19887
  const toolInfo = s.meta.tools ? theme.dim(` [${s.meta.tools.join(", ")}]`) : "";
19928
19888
  console.log(
@@ -20010,7 +19970,7 @@ No tools match "${filter}".
20010
19970
  }
20011
19971
 
20012
19972
  // src/repl/commands/security-commands.ts
20013
- import { platform as platform9 } from "os";
19973
+ import { platform as platform7 } from "os";
20014
19974
  import chalk6 from "chalk";
20015
19975
  function createSecurityCommands() {
20016
19976
  return [
@@ -20091,7 +20051,7 @@ ${outcome.summary}
20091
20051
  usage: "/bug [--copy]",
20092
20052
  execute(args, ctx) {
20093
20053
  const doCopy = args.includes("--copy");
20094
- const os = platform9();
20054
+ const os = platform7();
20095
20055
  const nodeVer = process.version;
20096
20056
  const provider = ctx.getCurrentProvider();
20097
20057
  const model = ctx.getCurrentModel();
@@ -20245,12 +20205,12 @@ ${outcome.summary}
20245
20205
  if (scanAll) {
20246
20206
  const metas = ctx.sessions.listSessions();
20247
20207
  console.log(theme.info(` Scanning ${metas.length} session(s)\u2026`));
20248
- const { readFileSync: readFileSync26 } = await import("fs");
20249
- const { join: join29 } = await import("path");
20208
+ const { readFileSync: readFileSync23 } = await import("fs");
20209
+ const { join: join26 } = await import("path");
20250
20210
  const historyDir = ctx.config.getHistoryDir();
20251
20211
  for (const m of metas) {
20252
20212
  try {
20253
- const content = readFileSync26(join29(historyDir, `${m.id}.json`), "utf-8");
20213
+ const content = readFileSync23(join26(historyDir, `${m.id}.json`), "utf-8");
20254
20214
  const hits2 = scanString(content, opts);
20255
20215
  if (hits2.length) {
20256
20216
  filesWithHits++;
@@ -20297,8 +20257,8 @@ ${outcome.summary}
20297
20257
  }
20298
20258
 
20299
20259
  // src/repl/commands/review-commands.ts
20300
- import { existsSync as existsSync29, readFileSync as readFileSync23, writeFileSync as writeFileSync4 } from "fs";
20301
- import { join as join25 } from "path";
20260
+ import { existsSync as existsSync28, readFileSync as readFileSync20, writeFileSync as writeFileSync4 } from "fs";
20261
+ import { join as join22 } from "path";
20302
20262
  function createReviewCommands() {
20303
20263
  return [
20304
20264
  {
@@ -20472,11 +20432,11 @@ function createReviewCommands() {
20472
20432
  Branches (${branches.length}):
20473
20433
  `));
20474
20434
  for (const b of branches) {
20475
- const active = b.id === session.activeBranchId ? theme.success("\u25CF ") : " ";
20435
+ const active2 = b.id === session.activeBranchId ? theme.success("\u25CF ") : " ";
20476
20436
  const parent = b.parentBranchId ? theme.dim(` \u2190 ${b.parentBranchId}@${b.parentMessageIndex}`) : "";
20477
20437
  const count = b.id === session.activeBranchId ? session.messages.length : session.getBranchMessages(b.id)?.length ?? 0;
20478
20438
  console.log(
20479
- ` ${active}${theme.accent(b.id.padEnd(10))} ${b.title.padEnd(20)} ` + theme.dim(`(${count} msgs)`) + parent
20439
+ ` ${active2}${theme.accent(b.id.padEnd(10))} ${b.title.padEnd(20)} ` + theme.dim(`(${count} msgs)`) + parent
20480
20440
  );
20481
20441
  }
20482
20442
  console.log();
@@ -20589,9 +20549,9 @@ function createReviewCommands() {
20589
20549
  ctx.renderer.renderError(`Branch "${id}" not found.`);
20590
20550
  return;
20591
20551
  }
20592
- const active = session.activeBranchId;
20552
+ const active2 = session.activeBranchId;
20593
20553
  console.log(`
20594
- ${theme.info("diff")} ${id} (source) \u2194 ${active} (active)`);
20554
+ ${theme.info("diff")} ${id} (source) \u2194 ${active2} (active)`);
20595
20555
  console.log(theme.dim(` Common prefix: ${d.commonPrefix} message(s)`));
20596
20556
  if (d.sourceOnly.length === 0 && d.targetOnly.length === 0) {
20597
20557
  console.log(theme.success(" Branches are identical.\n"));
@@ -20610,7 +20570,7 @@ function createReviewCommands() {
20610
20570
  }
20611
20571
  if (d.targetOnly.length > 0) {
20612
20572
  console.log(theme.dim(`
20613
- Only in ${active} (active) (${d.targetOnly.length}):`));
20573
+ Only in ${active2} (active) (${d.targetOnly.length}):`));
20614
20574
  d.targetOnly.forEach((m, i) => {
20615
20575
  console.log(` ${theme.dim(`[${d.commonPrefix + i}]`)} ${m.role}: ${preview(m)}`);
20616
20576
  });
@@ -20657,9 +20617,9 @@ function createReviewCommands() {
20657
20617
  usage: "/init [--force]",
20658
20618
  async execute(args, ctx) {
20659
20619
  const cwd = process.cwd();
20660
- const targetPath = join25(cwd, "AICLI.md");
20620
+ const targetPath = join22(cwd, "AICLI.md");
20661
20621
  const force = args.includes("--force");
20662
- if (existsSync29(targetPath) && !force) {
20622
+ if (existsSync28(targetPath) && !force) {
20663
20623
  ctx.renderer.printInfo(`AICLI.md already exists at ${targetPath}`);
20664
20624
  ctx.renderer.printInfo("Use /init --force to overwrite, or edit it manually.");
20665
20625
  return;
@@ -20723,7 +20683,7 @@ function createReviewCommands() {
20723
20683
  usage: "/test [command|filter]",
20724
20684
  async execute(args, ctx) {
20725
20685
  try {
20726
- const { executeTests } = await import("./run-tests-UEE3OMLS.js");
20686
+ const { executeTests } = await import("./run-tests-KRNMFXAN.js");
20727
20687
  const argStr = args.join(" ").trim();
20728
20688
  let testArgs = {};
20729
20689
  if (argStr) {
@@ -20758,9 +20718,9 @@ function createReviewCommands() {
20758
20718
  let newFiles = 0;
20759
20719
  let modifiedFiles = 0;
20760
20720
  for (const [filePath, { earliest }] of fileMap) {
20761
- const currentContent = existsSync29(filePath) ? (() => {
20721
+ const currentContent = existsSync28(filePath) ? (() => {
20762
20722
  try {
20763
- return readFileSync23(filePath, "utf-8");
20723
+ return readFileSync20(filePath, "utf-8");
20764
20724
  } catch {
20765
20725
  return null;
20766
20726
  }
@@ -20974,9 +20934,9 @@ function createMemoryCommands() {
20974
20934
  async execute(args, ctx) {
20975
20935
  const sub = (args[0] ?? "status").toLowerCase();
20976
20936
  const root = process.cwd();
20977
- const { loadIndex: loadIndex2, clearIndex } = await import("./store-SIKHCUYJ.js");
20978
- const { indexProject: indexProject2 } = await import("./indexer-FPKY7LCT.js");
20979
- const { loadVectorStore, clearVectorStore } = await import("./vector-store-JAXWDZYW.js");
20937
+ const { loadIndex: loadIndex2, clearIndex } = await import("./store-2N6PPPBP.js");
20938
+ const { indexProject: indexProject2 } = await import("./indexer-VRXDZ74A.js");
20939
+ const { loadVectorStore, clearVectorStore } = await import("./vector-store-T2DVUMY4.js");
20980
20940
  if (sub === "status") {
20981
20941
  const idx = loadIndex2(root);
20982
20942
  const vec = loadVectorStore(root);
@@ -21036,7 +20996,7 @@ function createMemoryCommands() {
21036
20996
  }
21037
20997
  console.log(theme.dim(` Building semantic index for ${idx.symbolCount} symbols\u2026`));
21038
20998
  console.log(theme.dim(" (First run downloads ~117 MB embedding model to ~/.aicli/models/)"));
21039
- const { rebuildSemanticIndex } = await import("./semantic-QOBAXEQ4.js");
20999
+ const { rebuildSemanticIndex } = await import("./semantic-TRQAZSRY.js");
21040
21000
  try {
21041
21001
  const stats = await rebuildSemanticIndex(root, {
21042
21002
  onProgress: (done, total) => {
@@ -21235,14 +21195,14 @@ ${view.chatHeading}
21235
21195
  console.log(theme.heading("\n\u{1F4DD} Governed Persistent Memory:") + filterNote + "\n" + theme.dim("\u2500".repeat(72)));
21236
21196
  for (const entry of entries) {
21237
21197
  const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
21238
- const source = [
21198
+ const source2 = [
21239
21199
  entry.source ?? "unknown",
21240
21200
  entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : "",
21241
21201
  entry.sourceProject ? `project ${entry.sourceProject}` : ""
21242
21202
  ].filter(Boolean).join(" \xB7 ");
21243
21203
  console.log(theme.accent(` ${entry.id.slice(0, 8)} ${status} scope:${entry.scope} sensitivity:${entry.sensitivity}`));
21244
21204
  const lastRef = entry.lastReferencedAt ? ` \xB7 last-referenced: ${entry.lastReferencedAt.slice(0, 10)}` : "";
21245
- console.log(theme.dim(` source: ${source} \xB7 created: ${entry.createdAt.replace("T", " ").slice(0, 19)}${lastRef}${entry.expiresAt ? ` \xB7 expires: ${entry.expiresAt}` : ""}`));
21205
+ console.log(theme.dim(` source: ${source2} \xB7 created: ${entry.createdAt.replace("T", " ").slice(0, 19)}${lastRef}${entry.expiresAt ? ` \xB7 expires: ${entry.expiresAt}` : ""}`));
21246
21206
  console.log(" " + entry.content.replace(/\n/g, "\n "));
21247
21207
  console.log();
21248
21208
  }
@@ -21923,16 +21883,16 @@ function createSessionCommands() {
21923
21883
 
21924
21884
  // src/repl/commands/misc-commands.ts
21925
21885
  import { basename as basename9 } from "path";
21926
- import { platform as platform10 } from "os";
21886
+ import { platform as platform8 } from "os";
21927
21887
 
21928
21888
  // src/repl/clipboard.ts
21929
21889
  import { execSync as execSync3 } from "child_process";
21930
- import { existsSync as existsSync30, statSync as statSync15, unlinkSync as unlinkSync7 } from "fs";
21890
+ import { existsSync as existsSync29, statSync as statSync15, unlinkSync as unlinkSync7 } from "fs";
21931
21891
  import { tmpdir as tmpdir3 } from "os";
21932
- import { join as join26 } from "path";
21892
+ import { join as join23 } from "path";
21933
21893
  var CLIPBOARD_TIMEOUT = 5e3;
21934
21894
  function readClipboardImage() {
21935
- const outPath = join26(tmpdir3(), `aicli-paste-${Date.now()}.png`);
21895
+ const outPath = join23(tmpdir3(), `aicli-paste-${Date.now()}.png`);
21936
21896
  try {
21937
21897
  if (process.platform === "win32") {
21938
21898
  _readWin32(outPath);
@@ -21945,7 +21905,7 @@ function readClipboardImage() {
21945
21905
  _cleanup(outPath);
21946
21906
  return null;
21947
21907
  }
21948
- if (!existsSync30(outPath) || statSync15(outPath).size === 0) {
21908
+ if (!existsSync29(outPath) || statSync15(outPath).size === 0) {
21949
21909
  _cleanup(outPath);
21950
21910
  return null;
21951
21911
  }
@@ -21998,7 +21958,7 @@ function _readLinux(outPath) {
21998
21958
  }
21999
21959
  function _cleanup(path3) {
22000
21960
  try {
22001
- if (existsSync30(path3)) unlinkSync7(path3);
21961
+ if (existsSync29(path3)) unlinkSync7(path3);
22002
21962
  } catch {
22003
21963
  }
22004
21964
  }
@@ -22245,7 +22205,7 @@ No commands match "${filter}".
22245
22205
  ctx.renderer.printSuccess(`Copied to clipboard (${lines} lines, ${chars} chars)`);
22246
22206
  } catch (err) {
22247
22207
  const msg = err instanceof Error ? err.message : String(err);
22248
- if (platform10() === "linux" && msg.includes("not found")) {
22208
+ if (platform8() === "linux" && msg.includes("not found")) {
22249
22209
  ctx.renderer.renderError("Clipboard tool not found. Install xclip or xsel: sudo apt install xclip");
22250
22210
  } else {
22251
22211
  ctx.renderer.renderError(`Failed to copy: ${msg}`);
@@ -22840,7 +22800,18 @@ var SessionHandler = class _SessionHandler {
22840
22800
  }))
22841
22801
  });
22842
22802
  }
22803
+ executionState = /* @__PURE__ */ new Map();
22804
+ connectionAbort = new AbortController();
22843
22805
  async handleMessage(raw) {
22806
+ if (this.connectionAbort.signal.aborted && /"type"\s*:\s*"(?:chat|command|hub_start)"/.test(raw)) this.connectionAbort = new AbortController();
22807
+ return runWithExecutionContext({
22808
+ configDir: this.config.getConfigDir(),
22809
+ sessionKey: this._bashSessionKey,
22810
+ state: this.executionState,
22811
+ signal: this.connectionAbort.signal
22812
+ }, () => this.handleMessageScoped(raw));
22813
+ }
22814
+ async handleMessageScoped(raw) {
22844
22815
  if (this.disconnected) return;
22845
22816
  let msg;
22846
22817
  try {
@@ -22851,6 +22822,7 @@ var SessionHandler = class _SessionHandler {
22851
22822
  }
22852
22823
  switch (msg.type) {
22853
22824
  case "chat": {
22825
+ if (this.connectionAbort.signal.aborted) this.connectionAbort = new AbortController();
22854
22826
  if (msg.messageId) {
22855
22827
  if (typeof msg.messageId !== "string" || msg.messageId.length > 200) throw new Error("Invalid message ID");
22856
22828
  const scope = this.config.getConfigDir();
@@ -22911,6 +22883,7 @@ var SessionHandler = class _SessionHandler {
22911
22883
  return;
22912
22884
  }
22913
22885
  case "abort":
22886
+ this.connectionAbort.abort();
22914
22887
  if (this.abortController) {
22915
22888
  this.abortController.abort();
22916
22889
  }
@@ -22934,8 +22907,16 @@ var SessionHandler = class _SessionHandler {
22934
22907
  }
22935
22908
  }
22936
22909
  onDisconnect(closeSocket = true) {
22910
+ runWithExecutionContext({
22911
+ configDir: this.config.getConfigDir(),
22912
+ sessionKey: this._bashSessionKey,
22913
+ state: this.executionState
22914
+ }, () => this.disconnectScoped(closeSocket));
22915
+ }
22916
+ disconnectScoped(closeSocket) {
22937
22917
  if (this.disconnected) return;
22938
22918
  this.disconnected = true;
22919
+ this.connectionAbort.abort();
22939
22920
  if (closeSocket && this.ws.readyState === this.ws.OPEN) this.ws.close(1001, "Connection closed; reconnect to restore session");
22940
22921
  runLifecycleHooks(mergePluginHooks(this.config.get("hooks") ?? void 0, this.config.getConfigDir()), "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
22941
22922
  this.toolExecutor.cancelAll();
@@ -23914,20 +23895,20 @@ ${summaryContent}`,
23914
23895
  this.send({ type: "error", message: "Usage: /memory show --stale <days> (positive number)" });
23915
23896
  return;
23916
23897
  }
23917
- const source = filters.orphaned ? listOrphanedProjectMemories(configDir) : listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
23918
- const { entries, applied } = filterMemoryEntries(source, filters);
23898
+ const source2 = filters.orphaned ? listOrphanedProjectMemories(configDir) : listMemoryEntries(configDir, { includeExpired: true, includeRejected: true });
23899
+ const { entries, applied } = filterMemoryEntries(source2, filters);
23919
23900
  const filterNote = applied.length > 0 ? ` (filtered: ${applied.join(", ")})` : "";
23920
23901
  const content = entries.length === 0 ? applied.length > 0 ? `(no entries match [${applied.join(", ")}])` : "(empty \u2014 no persistent memory entries yet)" : `Governed persistent memory${filterNote}
23921
23902
 
23922
23903
  ` + entries.map((entry) => {
23923
23904
  const status = isMemoryExpired(entry) ? "expired" : entry.approved ? "approved" : "pending";
23924
- const source2 = [
23905
+ const source3 = [
23925
23906
  entry.source ?? "unknown",
23926
23907
  entry.sourceSession ? `session ${entry.sourceSession.slice(0, 8)}` : "",
23927
23908
  entry.sourceProject ? `project ${entry.sourceProject}` : ""
23928
23909
  ].filter(Boolean).join(" \xB7 ");
23929
23910
  return `## ${entry.id.slice(0, 8)} \xB7 ${status} \xB7 scope:${entry.scope} \xB7 sensitivity:${entry.sensitivity}
23930
- Source: ${source2}
23911
+ Source: ${source3}
23931
23912
  ${entry.content}`;
23932
23913
  }).join("\n\n");
23933
23914
  this.send({
@@ -24037,7 +24018,7 @@ Backup saved: ${result.backupPath}` : "";
24037
24018
  return;
24038
24019
  }
24039
24020
  try {
24040
- const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-DGUOUUQW.js");
24021
+ const { searchChatMemory: searchChatMemory2, loadChatIndex: loadChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
24041
24022
  const loaded = loadChatIndex2();
24042
24023
  if (!loaded || loaded.idx.chunks.length === 0) {
24043
24024
  this.send({ type: "memory_hits", query: q, hits: [], indexMissing: true });
@@ -24073,7 +24054,7 @@ Backup saved: ${result.backupPath}` : "";
24073
24054
  }
24074
24055
  async handleMemoryStatus() {
24075
24056
  try {
24076
- const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-DGUOUUQW.js");
24057
+ const { getChatIndexStatus: getChatIndexStatus2 } = await import("./chat-index-RLVLQRRC.js");
24077
24058
  const s = getChatIndexStatus2();
24078
24059
  this.send({
24079
24060
  type: "memory_status",
@@ -24098,7 +24079,7 @@ Backup saved: ${result.backupPath}` : "";
24098
24079
  type: "info",
24099
24080
  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"
24100
24081
  });
24101
- const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-DGUOUUQW.js");
24082
+ const { buildChatIndex: buildChatIndex2 } = await import("./chat-index-RLVLQRRC.js");
24102
24083
  const stats = await buildChatIndex2({
24103
24084
  full,
24104
24085
  onProgress: (p) => {
@@ -24256,9 +24237,9 @@ async function setupProxy(configProxy) {
24256
24237
  }
24257
24238
 
24258
24239
  // src/web/auth.ts
24259
- import { existsSync as existsSync31, readFileSync as readFileSync24, writeFileSync as writeFileSync6, mkdirSync as mkdirSync16, readdirSync as readdirSync14, copyFileSync } from "fs";
24260
- import { isAbsolute as isAbsolute3, join as join27, relative as relative5 } from "path";
24261
- import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
24240
+ import { existsSync as existsSync30, readFileSync as readFileSync21, mkdirSync as mkdirSync16, readdirSync as readdirSync14, openSync, closeSync, unlinkSync as unlinkSync8 } from "fs";
24241
+ import { isAbsolute as isAbsolute3, join as join24, relative as relative5 } from "path";
24242
+ import { createHmac, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2, pbkdf2Sync, pbkdf2 } from "crypto";
24262
24243
  var USERS_FILE = "users.json";
24263
24244
  var TOKEN_EXPIRY_HOURS = 24;
24264
24245
  var TOKEN_EXPIRY_MS = TOKEN_EXPIRY_HOURS * 3600 * 1e3;
@@ -24270,14 +24251,16 @@ var AuthManager = class {
24270
24251
  usersFile;
24271
24252
  baseDir;
24272
24253
  db;
24254
+ loadedRaw;
24273
24255
  constructor(baseDir) {
24274
24256
  this.baseDir = baseDir;
24275
- this.usersFile = join27(baseDir, USERS_FILE);
24257
+ this.usersFile = join24(baseDir, USERS_FILE);
24276
24258
  this.db = this.loadOrCreate();
24277
24259
  }
24278
24260
  // ── Public API ─────────────────────────────────────────────────
24279
24261
  /** Check if any users exist (first-run detection) */
24280
24262
  hasUsers() {
24263
+ this.refresh();
24281
24264
  return this.db.users.length > 0;
24282
24265
  }
24283
24266
  /** Check if auth is enabled (has users.json with at least 1 user) */
@@ -24286,6 +24269,18 @@ var AuthManager = class {
24286
24269
  }
24287
24270
  /** Register a new user. Returns error string or null on success. */
24288
24271
  register(username, password) {
24272
+ return this.registerPrepared(username, password);
24273
+ }
24274
+ async registerAsync(username, password) {
24275
+ if (typeof username !== "string" || typeof password !== "string" || password.length > 1024) return "Invalid username or password";
24276
+ if (!/^[a-z0-9_-]{2,32}$/i.test(username.trim()) || password.length < 4) return "Invalid username or password";
24277
+ const salt = randomBytes2(16).toString("hex");
24278
+ const hash = await hashPasswordAsync(password, salt);
24279
+ return this.registerPrepared(username, password, { salt, hash });
24280
+ }
24281
+ registerPrepared(username, password, prepared) {
24282
+ this.refresh();
24283
+ if (typeof username !== "string" || typeof password !== "string" || password.length > 1024) return "Invalid username or password";
24289
24284
  username = username.trim().toLowerCase();
24290
24285
  if (!username || username.length < 2 || username.length > 32) {
24291
24286
  return "Username must be 2-32 characters";
@@ -24299,11 +24294,12 @@ var AuthManager = class {
24299
24294
  if (this.db.users.find((u) => u.username === username)) {
24300
24295
  return "Username already exists";
24301
24296
  }
24302
- const salt = randomBytes(16).toString("hex");
24303
- const passwordHash = this.hashPassword(password, salt);
24304
- const dataDir = join27(USERS_DIR, username);
24305
- const fullDataDir = join27(this.baseDir, dataDir);
24306
- mkdirSync16(join27(fullDataDir, "history"), { recursive: true });
24297
+ const salt = prepared?.salt ?? randomBytes2(16).toString("hex");
24298
+ const passwordHash = prepared?.hash ?? this.hashPassword(password, salt);
24299
+ const dataDir = join24(USERS_DIR, username);
24300
+ const fullDataDir = join24(this.baseDir, dataDir);
24301
+ mkdirSync16(fullDataDir, { recursive: true, mode: 448 });
24302
+ mkdirSync16(join24(fullDataDir, "history"), { recursive: true, mode: 448 });
24307
24303
  const user = {
24308
24304
  username,
24309
24305
  passwordHash,
@@ -24326,6 +24322,8 @@ var AuthManager = class {
24326
24322
  * resets the counter.
24327
24323
  */
24328
24324
  login(username, password) {
24325
+ this.refresh();
24326
+ if (!validCredentials(username, password)) return null;
24329
24327
  username = username.trim().toLowerCase();
24330
24328
  const lockState = this.getLockState(username);
24331
24329
  if (lockState.locked) return null;
@@ -24338,30 +24336,62 @@ var AuthManager = class {
24338
24336
  const hash = isLegacy ? this.hashPasswordLegacy(password, user.salt) : this.hashPassword(password, user.salt);
24339
24337
  const a = Buffer.from(hash, "utf-8");
24340
24338
  const b = Buffer.from(user.passwordHash, "utf-8");
24341
- if (a.length !== b.length || !timingSafeEqual(a, b)) {
24339
+ if (a.length !== b.length || !timingSafeEqual2(a, b)) {
24342
24340
  this.recordFailedLogin(username);
24343
24341
  return null;
24344
24342
  }
24345
24343
  if (isLegacy) {
24346
- const newSalt = randomBytes(16).toString("hex");
24344
+ const newSalt = randomBytes2(16).toString("hex");
24347
24345
  user.passwordHash = this.hashPassword(password, newSalt);
24348
24346
  user.salt = newSalt;
24349
24347
  user.hashVersion = 2;
24350
24348
  this.save();
24351
24349
  }
24352
- loginAttempts.delete(username);
24350
+ loginAttempts.delete(this.attemptKey(username));
24353
24351
  return this.createToken(username);
24354
24352
  }
24355
24353
  /**
24356
24354
  * Returns current lockout state for a username and lazily expires it.
24357
24355
  * Exposed (read-only) for tests and the `aicli user` CLI status output.
24358
24356
  */
24357
+ async loginAsync(username, password) {
24358
+ this.refresh();
24359
+ if (!validCredentials(username, password)) return null;
24360
+ username = username.trim().toLowerCase();
24361
+ if (this.getLockState(username).locked) return null;
24362
+ const user = this.db.users.find((u) => u.username === username);
24363
+ if (!user) {
24364
+ this.recordFailedLogin(username);
24365
+ return null;
24366
+ }
24367
+ const snapshot = JSON.stringify(user);
24368
+ const legacy = !user.hashVersion || user.hashVersion < 2;
24369
+ const hash = legacy ? this.hashPasswordLegacy(password, user.salt) : await hashPasswordAsync(password, user.salt);
24370
+ const salt = legacy ? randomBytes2(16).toString("hex") : user.salt;
24371
+ const upgraded = legacy ? await hashPasswordAsync(password, salt) : hash;
24372
+ this.refresh();
24373
+ const current = this.db.users.find((u) => u.username === username);
24374
+ if (!current || JSON.stringify(current) !== snapshot || this.getLockState(username).locked) return null;
24375
+ const a = Buffer.from(hash), b = Buffer.from(current.passwordHash);
24376
+ if (a.length !== b.length || !timingSafeEqual2(a, b)) {
24377
+ this.recordFailedLogin(username);
24378
+ return null;
24379
+ }
24380
+ if (legacy) {
24381
+ current.salt = salt;
24382
+ current.passwordHash = upgraded;
24383
+ current.hashVersion = 2;
24384
+ this.save();
24385
+ }
24386
+ loginAttempts.delete(this.attemptKey(username));
24387
+ return this.createToken(username);
24388
+ }
24359
24389
  getLockState(username) {
24360
24390
  username = username.trim().toLowerCase();
24361
- const state2 = loginAttempts.get(username);
24391
+ const state2 = loginAttempts.get(this.attemptKey(username));
24362
24392
  if (!state2) return { locked: false, remainingMs: 0, fails: 0 };
24363
- if (state2.lockedUntil > 0 && Date.now() >= state2.lockedUntil) {
24364
- loginAttempts.delete(username);
24393
+ if (Date.now() >= state2.expiresAt) {
24394
+ loginAttempts.delete(this.attemptKey(username));
24365
24395
  return { locked: false, remainingMs: 0, fails: 0 };
24366
24396
  }
24367
24397
  if (state2.lockedUntil > 0) {
@@ -24369,8 +24399,15 @@ var AuthManager = class {
24369
24399
  }
24370
24400
  return { locked: false, remainingMs: 0, fails: state2.fails };
24371
24401
  }
24402
+ attemptKey(username) {
24403
+ return this.usersFile + "\0" + username;
24404
+ }
24372
24405
  recordFailedLogin(username) {
24373
- const state2 = loginAttempts.get(username) ?? { fails: 0, lockedUntil: 0 };
24406
+ username = this.attemptKey(username);
24407
+ const now = Date.now();
24408
+ for (const [key, value] of loginAttempts) if (value.expiresAt <= now) loginAttempts.delete(key);
24409
+ if (!loginAttempts.has(username) && loginAttempts.size >= 1e4) loginAttempts.delete(loginAttempts.keys().next().value);
24410
+ const state2 = loginAttempts.get(username) ?? { fails: 0, lockedUntil: 0, expiresAt: now + LOGIN_LOCKOUT_MS };
24374
24411
  state2.fails += 1;
24375
24412
  if (state2.fails >= LOGIN_MAX_FAILS) {
24376
24413
  state2.lockedUntil = Date.now() + LOGIN_LOCKOUT_MS;
@@ -24380,17 +24417,19 @@ var AuthManager = class {
24380
24417
  /** Verify a token. Returns username or null. */
24381
24418
  verifyToken(token) {
24382
24419
  try {
24420
+ this.refresh();
24421
+ if (typeof token !== "string" || token.length > 4096) return null;
24383
24422
  const parts = token.split(".");
24384
24423
  if (parts.length !== 2) return null;
24385
24424
  const [payloadB64, signature] = parts;
24386
24425
  const expectedSig = this.sign(payloadB64);
24387
24426
  const sigBuf = Buffer.from(signature ?? "", "utf-8");
24388
24427
  const expBuf = Buffer.from(expectedSig, "utf-8");
24389
- if (sigBuf.length !== expBuf.length || !timingSafeEqual(sigBuf, expBuf)) return null;
24428
+ if (sigBuf.length !== expBuf.length || !timingSafeEqual2(sigBuf, expBuf)) return null;
24390
24429
  const payload = JSON.parse(
24391
24430
  Buffer.from(payloadB64, "base64url").toString("utf-8")
24392
24431
  );
24393
- if (Date.now() > payload.exp) return null;
24432
+ if (!Number.isFinite(payload.exp) || Date.now() >= payload.exp) return null;
24394
24433
  const user = this.db.users.find((u) => u.username === payload.username);
24395
24434
  if (!user) return null;
24396
24435
  if (user.tokensRevokedBefore && (!payload.iat || payload.iat < user.tokensRevokedBefore)) {
@@ -24407,6 +24446,7 @@ var AuthManager = class {
24407
24446
  * v0.4.114). Returns true if the user existed.
24408
24447
  */
24409
24448
  logoutAll(username) {
24449
+ this.refresh();
24410
24450
  username = username.trim().toLowerCase();
24411
24451
  const user = this.db.users.find((u) => u.username === username);
24412
24452
  if (!user) return false;
@@ -24416,16 +24456,19 @@ var AuthManager = class {
24416
24456
  }
24417
24457
  /** Get user's data directory (absolute path) */
24418
24458
  getUserDataDir(username) {
24459
+ this.refresh();
24419
24460
  const user = this.db.users.find((u) => u.username === username);
24420
24461
  if (!user) throw new Error(`User not found: ${username}`);
24421
- return join27(this.baseDir, user.dataDir);
24462
+ return join24(this.baseDir, user.dataDir);
24422
24463
  }
24423
24464
  /** List all usernames */
24424
24465
  listUsers() {
24466
+ this.refresh();
24425
24467
  return this.db.users.map((u) => u.username);
24426
24468
  }
24427
24469
  /** Delete a user. Returns error string or null on success. */
24428
24470
  deleteUser(username) {
24471
+ this.refresh();
24429
24472
  username = username.trim().toLowerCase();
24430
24473
  const idx = this.db.users.findIndex((u) => u.username === username);
24431
24474
  if (idx === -1) return `User '${username}' not found`;
@@ -24435,13 +24478,14 @@ var AuthManager = class {
24435
24478
  }
24436
24479
  /** Reset a user's password. Returns error string or null on success. */
24437
24480
  resetPassword(username, newPassword) {
24481
+ this.refresh();
24438
24482
  username = username.trim().toLowerCase();
24439
24483
  const user = this.db.users.find((u) => u.username === username);
24440
24484
  if (!user) return `User '${username}' not found`;
24441
- if (!newPassword || newPassword.length < 4) {
24485
+ if (typeof newPassword !== "string" || newPassword.length > 1024 || newPassword.length < 4) {
24442
24486
  return "Password must be at least 4 characters";
24443
24487
  }
24444
- const salt = randomBytes(16).toString("hex");
24488
+ const salt = randomBytes2(16).toString("hex");
24445
24489
  user.passwordHash = this.hashPassword(newPassword, salt);
24446
24490
  user.salt = salt;
24447
24491
  user.hashVersion = 2;
@@ -24454,30 +24498,30 @@ var AuthManager = class {
24454
24498
  const err = this.register(username, password);
24455
24499
  if (err) return err;
24456
24500
  const userDir = this.getUserDataDir(username);
24457
- const globalConfig = join27(this.baseDir, "config.json");
24458
- if (existsSync31(globalConfig)) {
24501
+ const globalConfig = join24(this.baseDir, "config.json");
24502
+ if (existsSync30(globalConfig)) {
24459
24503
  try {
24460
- const content = readFileSync24(globalConfig, "utf-8");
24461
- writeFileSync6(join27(userDir, "config.json"), content, "utf-8");
24504
+ const content = readFileSync21(globalConfig, "utf-8");
24505
+ atomicWriteFileSync(join24(userDir, "config.json"), content, { mode: 384 });
24462
24506
  } catch {
24463
24507
  }
24464
24508
  }
24465
- const globalMemory = join27(this.baseDir, "memory.md");
24466
- if (existsSync31(globalMemory)) {
24509
+ const globalMemory = join24(this.baseDir, "memory.md");
24510
+ if (existsSync30(globalMemory)) {
24467
24511
  try {
24468
- const content = readFileSync24(globalMemory, "utf-8");
24469
- writeFileSync6(join27(userDir, "memory.md"), content, "utf-8");
24512
+ const content = readFileSync21(globalMemory, "utf-8");
24513
+ atomicWriteFileSync(join24(userDir, "memory.md"), content, { mode: 384 });
24470
24514
  } catch {
24471
24515
  }
24472
24516
  }
24473
- const globalHistory = join27(this.baseDir, "history");
24474
- if (existsSync31(globalHistory)) {
24517
+ const globalHistory = join24(this.baseDir, "history");
24518
+ if (existsSync30(globalHistory)) {
24475
24519
  try {
24476
24520
  const files = readdirSync14(globalHistory).filter((f) => f.endsWith(".json"));
24477
- const userHistory = join27(userDir, "history");
24521
+ const userHistory = join24(userDir, "history");
24478
24522
  for (const f of files) {
24479
24523
  try {
24480
- copyFileSync(join27(globalHistory, f), join27(userHistory, f));
24524
+ atomicWriteFileSync(join24(userHistory, f), readFileSync21(join24(globalHistory, f), "utf8"), { mode: 384 });
24481
24525
  } catch {
24482
24526
  }
24483
24527
  }
@@ -24487,13 +24531,22 @@ var AuthManager = class {
24487
24531
  return null;
24488
24532
  }
24489
24533
  // ── Private methods ────────────────────────────────────────────
24534
+ refresh() {
24535
+ const raw = readFileSync21(this.usersFile, "utf8");
24536
+ const parsed = JSON.parse(raw);
24537
+ if (!this.isValidUsersDB(parsed)) throw new Error("Authentication database is invalid; access refused");
24538
+ this.db = parsed;
24539
+ this.loadedRaw = raw;
24540
+ }
24490
24541
  loadOrCreate() {
24491
- if (existsSync31(this.usersFile)) {
24542
+ if (existsSync30(this.usersFile)) {
24492
24543
  try {
24493
- const parsed = JSON.parse(readFileSync24(this.usersFile, "utf-8"));
24544
+ const raw = readFileSync21(this.usersFile, "utf-8");
24545
+ const parsed = JSON.parse(raw);
24494
24546
  if (!this.isValidUsersDB(parsed)) {
24495
24547
  throw new Error("database structure is invalid");
24496
24548
  }
24549
+ this.loadedRaw = raw;
24497
24550
  return parsed;
24498
24551
  } catch (err) {
24499
24552
  const detail = err instanceof Error ? err.message : String(err);
@@ -24505,7 +24558,7 @@ var AuthManager = class {
24505
24558
  }
24506
24559
  const db = {
24507
24560
  version: 1,
24508
- secret: randomBytes(32).toString("hex"),
24561
+ secret: randomBytes2(32).toString("hex"),
24509
24562
  users: []
24510
24563
  };
24511
24564
  this.saveDB(db);
@@ -24524,7 +24577,7 @@ var AuthManager = class {
24524
24577
  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)) {
24525
24578
  return false;
24526
24579
  }
24527
- const dataDirFromBase = relative5(this.baseDir, join27(this.baseDir, record.dataDir));
24580
+ const dataDirFromBase = relative5(this.baseDir, join24(this.baseDir, record.dataDir));
24528
24581
  if (dataDirFromBase.startsWith("..") || isAbsolute3(dataDirFromBase)) return false;
24529
24582
  if (record.hashVersion !== void 0 && record.hashVersion !== 1 && record.hashVersion !== 2) return false;
24530
24583
  if (record.tokensRevokedBefore !== void 0 && !Number.isFinite(record.tokensRevokedBefore)) return false;
@@ -24536,8 +24589,19 @@ var AuthManager = class {
24536
24589
  this.saveDB(this.db);
24537
24590
  }
24538
24591
  saveDB(db) {
24539
- mkdirSync16(this.baseDir, { recursive: true });
24540
- atomicWriteFileSync(this.usersFile, JSON.stringify(db, null, 2));
24592
+ mkdirSync16(this.baseDir, { recursive: true, mode: 448 });
24593
+ const lock = this.usersFile + ".lock";
24594
+ const fd = openSync(lock, "wx", 384);
24595
+ try {
24596
+ const current = existsSync30(this.usersFile) ? readFileSync21(this.usersFile, "utf8") : void 0;
24597
+ if (current !== this.loadedRaw) throw new Error("Authentication database changed concurrently; retry the operation");
24598
+ const raw = JSON.stringify(db, null, 2);
24599
+ atomicWriteFileSync(this.usersFile, raw, { mode: 384 });
24600
+ this.loadedRaw = raw;
24601
+ } finally {
24602
+ closeSync(fd);
24603
+ unlinkSync8(lock);
24604
+ }
24541
24605
  }
24542
24606
  /** Legacy hash — kept only for migrating old users (v0.2.x) */
24543
24607
  hashPasswordLegacy(password, salt) {
@@ -24561,6 +24625,19 @@ var AuthManager = class {
24561
24625
  return createHmac("sha256", this.db.secret).update(data).digest("base64url");
24562
24626
  }
24563
24627
  };
24628
+ function validCredentials(username, password) {
24629
+ return typeof username === "string" && /^[a-z0-9_-]{2,32}$/i.test(username.trim()) && typeof password === "string" && password.length > 0 && password.length <= 1024;
24630
+ }
24631
+ var activeHashes = 0;
24632
+ async function hashPasswordAsync(password, salt) {
24633
+ if (activeHashes >= 4) throw new Error("Authentication capacity reached; try again later");
24634
+ activeHashes++;
24635
+ try {
24636
+ return await new Promise((resolve13, reject) => pbkdf2(password, salt, 1e5, 64, "sha512", (error, hash) => error ? reject(error) : resolve13(hash.toString("hex"))));
24637
+ } finally {
24638
+ activeHashes--;
24639
+ }
24640
+ }
24564
24641
 
24565
24642
  // src/web/server.ts
24566
24643
  function getModuleDir() {
@@ -24580,6 +24657,10 @@ async function startWebServer(options = {}) {
24580
24657
  console.log(`\u{1F916} ai-cli Web UI v${VERSION}`);
24581
24658
  console.log(` Initializing...`);
24582
24659
  const config = new ConfigManager();
24660
+ const authManager = new AuthManager(config.getConfigDir());
24661
+ if (!authManager.isEnabled() && !["localhost", "127.0.0.1", "::1"].includes(host)) {
24662
+ throw new Error("Network listening requires authentication. Create a user with aicli user create first.");
24663
+ }
24583
24664
  await setupProxy(config.get("proxy"));
24584
24665
  const providers = new ProviderRegistry();
24585
24666
  const sessions = new SessionManager(config);
@@ -24613,7 +24694,7 @@ async function startWebServer(options = {}) {
24613
24694
  const projectMcpResolved = mcpEnabled ? resolveProjectMcpPath() : null;
24614
24695
  let projectMcpServers = {};
24615
24696
  if (projectMcpResolved) {
24616
- const { checkTrust } = await import("./project-trust-MUG325AW.js");
24697
+ const { checkTrust } = await import("./project-trust-6CJB5ZMX.js");
24617
24698
  const verdict = checkTrust(config.getConfigDir(), projectMcpResolved);
24618
24699
  if (verdict.trusted) {
24619
24700
  projectMcpServers = loadProjectMcpConfig() ?? {};
@@ -24640,8 +24721,8 @@ async function startWebServer(options = {}) {
24640
24721
  }
24641
24722
  }
24642
24723
  let skillManager = null;
24643
- const skillsDir = join28(config.getConfigDir(), SKILLS_DIR_NAME);
24644
- if (existsSync32(skillsDir)) {
24724
+ const skillsDir = join25(config.getConfigDir(), SKILLS_DIR_NAME);
24725
+ if (existsSync31(skillsDir)) {
24645
24726
  skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
24646
24727
  skillManager.loadSkills(pluginAssets.skillDirs);
24647
24728
  const count = skillManager.listSkills().length;
@@ -24660,6 +24741,16 @@ async function startWebServer(options = {}) {
24660
24741
  skillManager
24661
24742
  };
24662
24743
  const app = express();
24744
+ const access = new WebAccess(host, options.externalOrigin);
24745
+ const authLimits = new AuthRateLimiter();
24746
+ app.use((req, res, next) => {
24747
+ if (!access.accepts(req)) {
24748
+ res.status(403).json({ error: "Untrusted request origin or host" });
24749
+ return;
24750
+ }
24751
+ res.locals.externalSecure = !!options.externalOrigin?.startsWith("https://") && req.headers.host === new URL(options.externalOrigin).host;
24752
+ next();
24753
+ });
24663
24754
  app.use((_req, res, next) => {
24664
24755
  res.setHeader(
24665
24756
  "Content-Security-Policy",
@@ -24688,19 +24779,23 @@ async function startWebServer(options = {}) {
24688
24779
  const WS_MSG_RATE_PER_SEC = 30;
24689
24780
  const WS_MSG_BURST = 60;
24690
24781
  const WS_PREAUTH_TIMEOUT_MS = 3e4;
24691
- const wss = new WebSocketServer({ server, maxPayload: WS_MAX_PAYLOAD });
24692
- const authManager = new AuthManager(config.getConfigDir());
24782
+ const wss = new WebSocketServer({ server, maxPayload: WS_MAX_PAYLOAD, verifyClient: ({ req }) => {
24783
+ if (!access.accepts(req) || wss.clients.size >= 128) return false;
24784
+ if (req.headers.origin) return true;
24785
+ const cookie = parseCookie(req.headers.cookie ?? "");
24786
+ return equalToken(cookie.aicli_local, access.localToken) || !!authManager.verifyToken(cookie.aicli_token ?? "");
24787
+ } });
24693
24788
  if (authManager.isEnabled()) {
24694
24789
  console.log(` Auth: ${authManager.listUsers().length} user(s) registered`);
24695
24790
  } else {
24696
- console.log(` Auth: disabled (no users registered)`);
24697
- if (host === "0.0.0.0" || host === "::") {
24698
- console.log(" WARNING: Web UI is reachable from the network without authentication.");
24699
- console.log(" Create a user first for LAN/team use: aicli user create <name>");
24700
- }
24791
+ console.log(" Auth: local launch token (no users registered)");
24701
24792
  }
24702
24793
  const requireAuth = (req, res, next) => {
24703
- if (!authManager.isEnabled()) return next();
24794
+ if (!authManager.isEnabled()) {
24795
+ if (equalToken(parseCookie(req.headers.cookie ?? "").aicli_local, access.localToken)) return next();
24796
+ res.status(401).json({ error: "Open the launch URL printed by aicli to authorize this browser" });
24797
+ return;
24798
+ }
24704
24799
  const authHeader = req.headers.authorization;
24705
24800
  const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
24706
24801
  const cookieToken = parseCookie(req.headers.cookie ?? "")["aicli_token"];
@@ -24718,19 +24813,20 @@ async function startWebServer(options = {}) {
24718
24813
  next();
24719
24814
  };
24720
24815
  const moduleDir = getModuleDir();
24721
- let clientDir = join28(moduleDir, "web", "client");
24722
- if (!existsSync32(clientDir)) {
24723
- clientDir = join28(moduleDir, "client");
24816
+ let clientDir = join25(moduleDir, "web", "client");
24817
+ if (!existsSync31(clientDir)) {
24818
+ clientDir = join25(moduleDir, "client");
24724
24819
  }
24725
- if (!existsSync32(clientDir)) {
24726
- clientDir = join28(moduleDir, "..", "..", "src", "web", "client");
24820
+ if (!existsSync31(clientDir)) {
24821
+ clientDir = join25(moduleDir, "..", "..", "src", "web", "client");
24727
24822
  }
24728
- if (!existsSync32(clientDir)) {
24729
- clientDir = join28(process.cwd(), "src", "web", "client");
24823
+ if (!existsSync31(clientDir)) {
24824
+ clientDir = join25(process.cwd(), "src", "web", "client");
24730
24825
  }
24731
24826
  console.log(` Static files: ${clientDir}`);
24732
24827
  const PRECOMPRESSED_TYPES = {
24733
24828
  ".js": "text/javascript; charset=utf-8",
24829
+ ".mjs": "text/javascript; charset=utf-8",
24734
24830
  ".css": "text/css; charset=utf-8",
24735
24831
  ".html": "text/html; charset=utf-8",
24736
24832
  ".json": "application/json; charset=utf-8",
@@ -24745,14 +24841,14 @@ async function startWebServer(options = {}) {
24745
24841
  const encoding = /\bbr\b/.test(accept) ? "br" : /\bgzip\b/.test(accept) ? "gzip" : null;
24746
24842
  if (!encoding) return next();
24747
24843
  const target = resolvePath(clientDir, `.${req.path}`) + (encoding === "br" ? ".br" : ".gz");
24748
- if (!target.startsWith(clientDir) || !existsSync32(target)) return next();
24844
+ if (!target.startsWith(clientDir) || !existsSync31(target)) return next();
24749
24845
  res.setHeader("Content-Type", contentType);
24750
24846
  res.setHeader("Content-Encoding", encoding);
24751
24847
  res.setHeader("Vary", "Accept-Encoding");
24752
24848
  if (req.path.startsWith("/vendor/")) res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
24753
24849
  createReadStream(target).pipe(res);
24754
24850
  });
24755
- app.use("/vendor", express.static(join28(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
24851
+ app.use("/vendor", express.static(join25(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
24756
24852
  app.use(express.static(clientDir));
24757
24853
  app.get("/api/status", (_req, res) => {
24758
24854
  res.json({
@@ -24771,9 +24867,28 @@ async function startWebServer(options = {}) {
24771
24867
  }
24772
24868
  });
24773
24869
  });
24774
- app.use(express.json());
24775
- app.post("/api/auth/register", (req, res) => {
24870
+ app.use(express.json({ limit: "16kb" }));
24871
+ app.post("/api/auth/bootstrap", (req, res) => {
24872
+ if (!equalToken(req.body?.token, access.localToken)) {
24873
+ res.status(403).json({ error: "Invalid launch token" });
24874
+ return;
24875
+ }
24876
+ res.cookie("aicli_local", access.localToken, { httpOnly: true, sameSite: "strict", secure: isHttpsRequest(req) });
24877
+ res.json({ success: true });
24878
+ });
24879
+ app.use("/api/auth", (req, res, next) => {
24880
+ if (!authLimits.take(req.socket.remoteAddress ?? "unknown")) {
24881
+ res.status(429).json({ error: "Too many authentication attempts; try again later" });
24882
+ return;
24883
+ }
24884
+ next();
24885
+ });
24886
+ app.post("/api/auth/register", async (req, res) => {
24776
24887
  const firstRun = !authManager.hasUsers();
24888
+ if (firstRun && !equalToken(parseCookie(req.headers.cookie ?? "").aicli_local, access.localToken)) {
24889
+ res.status(403).json({ error: "Open the launch URL before enabling authentication" });
24890
+ return;
24891
+ }
24777
24892
  if (!firstRun) {
24778
24893
  const authHeader = req.headers.authorization;
24779
24894
  const token2 = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
@@ -24789,23 +24904,24 @@ async function startWebServer(options = {}) {
24789
24904
  res.status(400).json({ error: "Username and password required" });
24790
24905
  return;
24791
24906
  }
24792
- const err = authManager.register(username, password);
24907
+ const err = await authManager.registerAsync(username, password);
24793
24908
  if (err) {
24794
24909
  res.status(400).json({ error: err });
24795
24910
  return;
24796
24911
  }
24797
- const token = authManager.login(username, password);
24912
+ const token = await authManager.loginAsync(username, password);
24913
+ invalidateConnections();
24798
24914
  console.log(` \u2713 User registered via API: ${username}${firstRun ? " (first-run)" : ""}`);
24799
24915
  res.cookie("aicli_token", token, authCookieOptions(req));
24800
24916
  res.json({ success: true, username });
24801
24917
  });
24802
- app.post("/api/auth/login", (req, res) => {
24918
+ app.post("/api/auth/login", async (req, res) => {
24803
24919
  const { username, password } = req.body ?? {};
24804
24920
  if (!username || !password) {
24805
24921
  res.status(400).json({ error: "Username and password required" });
24806
24922
  return;
24807
24923
  }
24808
- const token = authManager.login(username, password);
24924
+ const token = await authManager.loginAsync(username, password);
24809
24925
  if (!token) {
24810
24926
  res.status(401).json({ error: "Invalid username or password" });
24811
24927
  return;
@@ -24814,13 +24930,16 @@ async function startWebServer(options = {}) {
24814
24930
  res.json({ success: true, username });
24815
24931
  });
24816
24932
  app.post("/api/auth/logout", (req, res) => {
24933
+ const user = authManager.verifyToken(parseCookie(req.headers.cookie ?? "").aicli_token ?? "");
24934
+ if (user) authManager.logoutAll(user);
24935
+ invalidateConnections();
24817
24936
  res.clearCookie("aicli_token", clearAuthCookieOptions(req));
24818
24937
  res.json({ success: true });
24819
24938
  });
24820
24939
  app.get("/api/files", requireAuth, (req, res) => {
24821
24940
  const cwd = process.cwd();
24822
24941
  const prefix = req.query.prefix || "";
24823
- const targetDir = join28(cwd, prefix);
24942
+ const targetDir = join25(cwd, prefix);
24824
24943
  try {
24825
24944
  const canonicalTarget = realpathSync3(resolve12(targetDir));
24826
24945
  const canonicalCwd = realpathSync3(resolve12(cwd));
@@ -24837,7 +24956,7 @@ async function startWebServer(options = {}) {
24837
24956
  const entries = readdirSync15(targetDir, { withFileTypes: true });
24838
24957
  const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
24839
24958
  name: e.name,
24840
- path: relative6(cwd, join28(targetDir, e.name)).replace(/\\/g, "/"),
24959
+ path: relative6(cwd, join25(targetDir, e.name)).replace(/\\/g, "/"),
24841
24960
  isDir: e.isDirectory()
24842
24961
  }));
24843
24962
  res.json({ files });
@@ -24877,8 +24996,8 @@ async function startWebServer(options = {}) {
24877
24996
  try {
24878
24997
  const authUser = req._authUser;
24879
24998
  const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
24880
- const filePath = join28(histDir, `${id}.json`);
24881
- if (!existsSync32(filePath)) {
24999
+ const filePath = join25(histDir, `${id}.json`);
25000
+ if (!existsSync31(filePath)) {
24882
25001
  res.status(404).json({ error: "Session not found" });
24883
25002
  return;
24884
25003
  }
@@ -24893,7 +25012,7 @@ async function startWebServer(options = {}) {
24893
25012
  res.status(404).json({ error: "Session not found" });
24894
25013
  return;
24895
25014
  }
24896
- const data = JSON.parse(readFileSync25(filePath, "utf-8"));
25015
+ const data = JSON.parse(readFileSync22(filePath, "utf-8"));
24897
25016
  res.json({ session: data });
24898
25017
  } catch (err) {
24899
25018
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
@@ -24906,9 +25025,10 @@ async function startWebServer(options = {}) {
24906
25025
  return;
24907
25026
  }
24908
25027
  const cwd = process.cwd();
24909
- const fullPath = resolve12(join28(cwd, filePath));
25028
+ const fullPath = resolve12(join25(cwd, filePath));
24910
25029
  try {
24911
25030
  const canonicalFull = realpathSync3(fullPath);
25031
+ assertReadablePath(canonicalFull);
24912
25032
  const canonicalCwd = realpathSync3(resolve12(cwd));
24913
25033
  if (!canonicalFull.startsWith(canonicalCwd + sep5) && canonicalFull !== canonicalCwd) {
24914
25034
  res.json({ error: "Access denied" });
@@ -24924,7 +25044,7 @@ async function startWebServer(options = {}) {
24924
25044
  res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
24925
25045
  return;
24926
25046
  }
24927
- const content = readFileSync25(fullPath, "utf-8");
25047
+ const content = readFileSync22(fullPath, "utf-8");
24928
25048
  res.json({ content, size: stat.size });
24929
25049
  } catch {
24930
25050
  res.json({ error: "Cannot read file" });
@@ -24936,6 +25056,9 @@ async function startWebServer(options = {}) {
24936
25056
  if (cached) return cached;
24937
25057
  const userDataDir = authManager.getUserDataDir(username);
24938
25058
  const userConfig = new ConfigManager(userDataDir);
25059
+ for (const key of ["defaultPermissionProfile", "allowedPermissionProfiles", "permissionProfiles", "permissionRules", "defaultPermission", "networkPolicy"]) {
25060
+ userConfig.setTransient(key, structuredClone(config.get(key)));
25061
+ }
24939
25062
  if (userConfig.isFirstRun()) {
24940
25063
  userConfig.setTransient("defaultProvider", config.get("defaultProvider"));
24941
25064
  userConfig.setTransient("defaultModels", config.get("defaultModels"));
@@ -24946,7 +25069,7 @@ async function startWebServer(options = {}) {
24946
25069
  // 每用户各自的 config 目录 → 各自的 cost-history.json
24947
25070
  costTracker: new CostTracker(userConfig.getConfigDir()),
24948
25071
  providers,
24949
- // Shared — providers re-read API keys per call
25072
+ // Host-owned service credentials; shared explicitly, never per-user keys.
24950
25073
  sessions: userSessions,
24951
25074
  toolRegistry,
24952
25075
  // Shared
@@ -24959,6 +25082,21 @@ async function startWebServer(options = {}) {
24959
25082
  return userShared;
24960
25083
  }
24961
25084
  const handlers = /* @__PURE__ */ new Map();
25085
+ const validators = /* @__PURE__ */ new Set();
25086
+ function invalidateConnections() {
25087
+ for (const validate of validators) validate();
25088
+ }
25089
+ let authRefreshTimer;
25090
+ server.on("listening", () => {
25091
+ if (!authRefreshTimer) {
25092
+ authRefreshTimer = setInterval(invalidateConnections, 1e3);
25093
+ authRefreshTimer.unref();
25094
+ }
25095
+ });
25096
+ server.on("close", () => {
25097
+ clearInterval(authRefreshTimer);
25098
+ authRefreshTimer = void 0;
25099
+ });
24962
25100
  wss.on("connection", (ws, req) => {
24963
25101
  const urlStr = req.url ?? "";
24964
25102
  const qMark = urlStr.indexOf("?");
@@ -24966,6 +25104,8 @@ async function startWebServer(options = {}) {
24966
25104
  const tabId = params.get("tabId") || `tab-${Date.now()}`;
24967
25105
  const cookies = parseCookie(req.headers.cookie ?? "");
24968
25106
  const token = cookies["aicli_token"] || "";
25107
+ let activeToken = token;
25108
+ const localAuthorized = equalToken(cookies.aicli_local, access.localToken);
24969
25109
  let tokens = WS_MSG_BURST;
24970
25110
  let lastRefill = Date.now();
24971
25111
  const takeToken = () => {
@@ -25025,7 +25165,7 @@ async function startWebServer(options = {}) {
25025
25165
  authenticatedUser = authManager.verifyToken(token);
25026
25166
  }
25027
25167
  const authRequired = authManager.isEnabled();
25028
- if (!authRequired) {
25168
+ if (!authRequired && localAuthorized) {
25029
25169
  console.log(` \u2713 Tab connected: ${tabId.slice(0, 12)} (no auth) (total: ${handlers.size + 1})`);
25030
25170
  handler = attachHandler(shared);
25031
25171
  clearPreAuthTimer();
@@ -25043,7 +25183,26 @@ async function startWebServer(options = {}) {
25043
25183
  }));
25044
25184
  }
25045
25185
  }
25186
+ const validate = () => {
25187
+ if (!handler) return;
25188
+ let valid = false;
25189
+ try {
25190
+ valid = authManager.isEnabled() ? !!activeToken && authManager.verifyToken(activeToken) === authenticatedUser : localAuthorized && !authenticatedUser;
25191
+ } catch {
25192
+ }
25193
+ if (!valid) {
25194
+ handler.onDisconnect(false);
25195
+ if (handlers.get(handlerKey) === handler) handlers.delete(handlerKey);
25196
+ handler = null;
25197
+ ws.close(1008, "Authentication changed; reconnect");
25198
+ }
25199
+ };
25200
+ validators.add(validate);
25201
+ ws.on("close", () => validators.delete(validate));
25202
+ let authBusy = false;
25046
25203
  ws.on("message", async (data) => {
25204
+ validate();
25205
+ if (ws.readyState !== ws.OPEN) return;
25047
25206
  if (!takeToken()) {
25048
25207
  if (ws.readyState === ws.OPEN) {
25049
25208
  try {
@@ -25063,57 +25222,73 @@ async function startWebServer(options = {}) {
25063
25222
  return;
25064
25223
  }
25065
25224
  if (parsed.type === "auth") {
25066
- const { action, username, password } = parsed;
25067
- if (action === "register") {
25068
- if (authManager.hasUsers() && !authenticatedUser) {
25069
- ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Registration is closed. Use `aicli user create` on the host to add users." }));
25225
+ if (authBusy) return;
25226
+ authBusy = true;
25227
+ try {
25228
+ if (!authLimits.take(req.socket.remoteAddress ?? "unknown")) {
25229
+ ws.close(1008, "Authentication rate limit");
25070
25230
  return;
25071
25231
  }
25072
- const err = authManager.register(username, password);
25073
- if (err) {
25074
- ws.send(JSON.stringify({ type: "auth_result", success: false, error: err }));
25232
+ const { action, password } = parsed;
25233
+ const username = typeof parsed.username === "string" ? parsed.username.trim().toLowerCase() : "";
25234
+ if (action === "register") {
25235
+ if (!authenticatedUser && (authManager.hasUsers() || !localAuthorized)) {
25236
+ ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Registration is closed. Use `aicli user create` on the host to add users." }));
25237
+ return;
25238
+ }
25239
+ const err = await authManager.registerAsync(username, password);
25240
+ if (err) {
25241
+ ws.send(JSON.stringify({ type: "auth_result", success: false, error: err }));
25242
+ return;
25243
+ }
25244
+ activeToken = await authManager.loginAsync(username, password) ?? "";
25245
+ if (ws.readyState !== ws.OPEN || authManager.verifyToken(activeToken) !== username) return;
25246
+ authenticatedUser = username;
25247
+ const userShared = getUserShared(username);
25248
+ handler = attachHandler(userShared);
25249
+ clearPreAuthTimer();
25250
+ invalidateConnections();
25251
+ console.log(` \u2713 User registered & connected: ${username} (tab: ${tabId.slice(0, 12)})`);
25252
+ ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
25075
25253
  return;
25076
25254
  }
25077
- authManager.login(username, password);
25078
- authenticatedUser = username;
25079
- const userShared = getUserShared(username);
25080
- handler = attachHandler(userShared);
25081
- clearPreAuthTimer();
25082
- console.log(` \u2713 User registered & connected: ${username} (tab: ${tabId.slice(0, 12)})`);
25083
- ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
25084
- return;
25085
- }
25086
- if (action === "token") {
25087
- const { token: clientToken } = parsed;
25088
- const verifiedUser = authManager.verifyToken(clientToken);
25089
- if (!verifiedUser) {
25090
- ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Invalid or expired token" }));
25255
+ if (action === "token") {
25256
+ const { token: clientToken } = parsed;
25257
+ const verifiedUser = authManager.verifyToken(clientToken);
25258
+ if (!verifiedUser) {
25259
+ ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Invalid or expired token" }));
25260
+ return;
25261
+ }
25262
+ activeToken = clientToken;
25263
+ authenticatedUser = verifiedUser;
25264
+ const userShared = getUserShared(verifiedUser);
25265
+ handler = attachHandler(userShared);
25266
+ clearPreAuthTimer();
25267
+ console.log(` \u2713 Token auth: ${verifiedUser} (tab: ${tabId.slice(0, 12)})`);
25268
+ ws.send(JSON.stringify({ type: "auth_result", success: true, username: verifiedUser }));
25091
25269
  return;
25092
25270
  }
25093
- authenticatedUser = verifiedUser;
25094
- const userShared = getUserShared(verifiedUser);
25095
- handler = attachHandler(userShared);
25096
- clearPreAuthTimer();
25097
- console.log(` \u2713 Token auth: ${verifiedUser} (tab: ${tabId.slice(0, 12)})`);
25098
- ws.send(JSON.stringify({ type: "auth_result", success: true, username: verifiedUser }));
25099
- return;
25100
- }
25101
- if (action === "login") {
25102
- const loginToken = authManager.login(username, password);
25103
- if (!loginToken) {
25104
- ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Invalid username or password" }));
25271
+ if (action === "login") {
25272
+ const loginToken = await authManager.loginAsync(username, password);
25273
+ if (!loginToken) {
25274
+ ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Invalid username or password" }));
25275
+ return;
25276
+ }
25277
+ if (ws.readyState !== ws.OPEN || authManager.verifyToken(loginToken) !== username) return;
25278
+ activeToken = loginToken;
25279
+ authenticatedUser = username;
25280
+ const userShared = getUserShared(username);
25281
+ handler = attachHandler(userShared);
25282
+ clearPreAuthTimer();
25283
+ console.log(` \u2713 User logged in: ${username} (tab: ${tabId.slice(0, 12)})`);
25284
+ ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
25105
25285
  return;
25106
25286
  }
25107
- authenticatedUser = username;
25108
- const userShared = getUserShared(username);
25109
- handler = attachHandler(userShared);
25110
- clearPreAuthTimer();
25111
- console.log(` \u2713 User logged in: ${username} (tab: ${tabId.slice(0, 12)})`);
25112
- ws.send(JSON.stringify({ type: "auth_result", success: true, username }));
25287
+ ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Unknown auth action" }));
25113
25288
  return;
25289
+ } finally {
25290
+ authBusy = false;
25114
25291
  }
25115
- ws.send(JSON.stringify({ type: "auth_result", success: false, error: "Unknown auth action" }));
25116
- return;
25117
25292
  }
25118
25293
  if (!handler) {
25119
25294
  if (ws.readyState === ws.OPEN) {
@@ -25159,13 +25334,15 @@ async function startWebServer(options = {}) {
25159
25334
  }
25160
25335
  });
25161
25336
  server.listen(actualPort, host, () => {
25162
- const url = `http://${host === "0.0.0.0" ? "localhost" : host}:${actualPort}`;
25337
+ actualPort = server.address().port;
25338
+ const address = host.includes(":") ? `[${host}]` : host === "0.0.0.0" ? "localhost" : host;
25339
+ const url = `http://${address}:${actualPort}` + (!authManager.isEnabled() ? `#token=${access.localToken}` : "");
25163
25340
  console.log(`
25164
25341
  \u{1F310} Web UI ready: ${url}`);
25165
25342
  if (actualPort !== port) console.log(` (port ${port} was in use, using ${actualPort})`);
25166
25343
  if (host === "0.0.0.0" || host === "::") {
25167
25344
  try {
25168
- const nets = networkInterfaces();
25345
+ const nets = networkInterfaces2();
25169
25346
  for (const name of Object.keys(nets)) {
25170
25347
  for (const net2 of nets[name] ?? []) {
25171
25348
  if (net2.family === "IPv4" && !net2.internal) {
@@ -25198,27 +25375,24 @@ function resolveProjectMcpPath() {
25198
25375
  const cwd = process.cwd();
25199
25376
  const gitRoot = getGitRoot(cwd);
25200
25377
  const projectRoot = gitRoot ?? cwd;
25201
- const configPath = join28(projectRoot, MCP_PROJECT_CONFIG_NAME);
25202
- return existsSync32(configPath) ? configPath : null;
25378
+ const configPath = join25(projectRoot, MCP_PROJECT_CONFIG_NAME);
25379
+ return existsSync31(configPath) ? configPath : null;
25203
25380
  }
25204
25381
  function loadProjectMcpConfig() {
25205
25382
  const cwd = process.cwd();
25206
25383
  const gitRoot = getGitRoot(cwd);
25207
25384
  const projectRoot = gitRoot ?? cwd;
25208
- const configPath = join28(projectRoot, MCP_PROJECT_CONFIG_NAME);
25209
- if (!existsSync32(configPath)) return null;
25385
+ const configPath = join25(projectRoot, MCP_PROJECT_CONFIG_NAME);
25386
+ if (!existsSync31(configPath)) return null;
25210
25387
  try {
25211
- const raw = JSON.parse(readFileSync25(configPath, "utf-8"));
25388
+ const raw = JSON.parse(readFileSync22(configPath, "utf-8"));
25212
25389
  return raw.mcpServers ?? raw;
25213
25390
  } catch {
25214
25391
  return null;
25215
25392
  }
25216
25393
  }
25217
25394
  function isHttpsRequest(req) {
25218
- if (req.secure) return true;
25219
- const forwardedProto = req.headers["x-forwarded-proto"];
25220
- const proto = Array.isArray(forwardedProto) ? forwardedProto[0] : forwardedProto;
25221
- return proto?.split(",")[0]?.trim().toLowerCase() === "https";
25395
+ return req.secure || req.res?.locals.externalSecure === true;
25222
25396
  }
25223
25397
  function authCookieOptions(req) {
25224
25398
  return {
@@ -25245,13 +25419,13 @@ function parseCookie(cookie) {
25245
25419
  }
25246
25420
  async function openBrowser(url) {
25247
25421
  try {
25248
- const { spawn: spawn7 } = await import("child_process");
25422
+ const { spawn: spawn6 } = await import("child_process");
25249
25423
  if (process.platform === "win32") {
25250
- spawn7("cmd", ["/c", "start", '""', url], { detached: true, stdio: "ignore" }).unref();
25424
+ spawn6("cmd", ["/c", "start", '""', url], { detached: true, stdio: "ignore" }).unref();
25251
25425
  } else if (process.platform === "darwin") {
25252
- spawn7("open", [url], { detached: true, stdio: "ignore" }).unref();
25426
+ spawn6("open", [url], { detached: true, stdio: "ignore" }).unref();
25253
25427
  } else {
25254
- spawn7("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
25428
+ spawn6("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
25255
25429
  }
25256
25430
  } catch {
25257
25431
  }