@aurora-foundation/obsidian-next 0.4.7 → 0.4.9

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 (232) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/LICENSE +628 -190
  3. package/README.md +23 -9
  4. package/dist/auditLog-6WDBDNYL.js +8 -0
  5. package/dist/auditLog-HGPVDSDC.js +8 -0
  6. package/dist/auditLog-TDIKFBM4.js +8 -0
  7. package/dist/auditLog-XC2KY3ZZ.js +8 -0
  8. package/dist/chunk-2I235WNB.js +133 -0
  9. package/dist/chunk-2JWDGXTR.js +42 -0
  10. package/dist/chunk-2NOB6W2B.js +133 -0
  11. package/dist/chunk-3LFKVKKL.js +7199 -0
  12. package/dist/chunk-3U6WHPDX.js +4695 -0
  13. package/dist/chunk-3UCL6RYE.js +7272 -0
  14. package/dist/chunk-4GN2UQLI.js +130 -0
  15. package/dist/chunk-4MW33MZD.js +516 -0
  16. package/dist/chunk-4PUJBUKZ.js +4716 -0
  17. package/dist/chunk-4QHK6H6O.js +130 -0
  18. package/dist/chunk-55CQIHCO.js +133 -0
  19. package/dist/chunk-5LWINFWI.js +676 -0
  20. package/dist/chunk-5OKGLNQW.js +439 -0
  21. package/dist/chunk-5T6ETZEO.js +6183 -0
  22. package/dist/chunk-5WGIFUVL.js +4234 -0
  23. package/dist/chunk-66EW47T3.js +4237 -0
  24. package/dist/chunk-6TXUOTT2.js +581 -0
  25. package/dist/chunk-6YUYSYDA.js +130 -0
  26. package/dist/chunk-74VPNFMX.js +133 -0
  27. package/dist/chunk-77CGJRGV.js +6188 -0
  28. package/dist/chunk-7DS3VT4C.js +7135 -0
  29. package/dist/chunk-7FHX3VBT.js +133 -0
  30. package/dist/chunk-7MHF56YU.js +6178 -0
  31. package/dist/chunk-ABLPMV7G.js +133 -0
  32. package/dist/chunk-B77K6OQZ.js +687 -0
  33. package/dist/chunk-BKOXH66O.js +133 -0
  34. package/dist/chunk-BPP76UN2.js +130 -0
  35. package/dist/chunk-C4D56GRC.js +5936 -0
  36. package/dist/chunk-CCDPY4WE.js +370 -0
  37. package/dist/chunk-CHNVBJN3.js +7272 -0
  38. package/dist/chunk-CKBZI576.js +7229 -0
  39. package/dist/chunk-CW5HBSJ2.js +7198 -0
  40. package/dist/chunk-DGHDJEY7.js +133 -0
  41. package/dist/chunk-DPNIQWKZ.js +439 -0
  42. package/dist/chunk-DU4T3V2T.js +214 -0
  43. package/dist/chunk-DV3WFKNB.js +4679 -0
  44. package/dist/chunk-DZI2OVN2.js +516 -0
  45. package/dist/chunk-E45VILML.js +7198 -0
  46. package/dist/chunk-ECEUUYXC.js +7199 -0
  47. package/dist/chunk-EJRRSHPW.js +685 -0
  48. package/dist/chunk-EMBMLZFE.js +370 -0
  49. package/dist/chunk-EPG5V5OO.js +285 -0
  50. package/dist/chunk-F2R4HXXW.js +130 -0
  51. package/dist/chunk-FK6N66ES.js +581 -0
  52. package/dist/chunk-G3CZKGYA.js +197 -0
  53. package/dist/chunk-GUUPG4A7.js +7111 -0
  54. package/dist/chunk-HBAAUGUN.js +7230 -0
  55. package/dist/chunk-HHFJMK2Q.js +6177 -0
  56. package/dist/chunk-HINRQTCZ.js +196 -0
  57. package/dist/chunk-HRKJ3R2U.js +288 -0
  58. package/dist/chunk-HWVK4CVE.js +439 -0
  59. package/dist/chunk-JEYSADNZ.js +581 -0
  60. package/dist/chunk-JNEIL7UN.js +4252 -0
  61. package/dist/chunk-JTWSK277.js +676 -0
  62. package/dist/chunk-K4CHTTCJ.js +942 -0
  63. package/dist/chunk-K7R5KUDS.js +4695 -0
  64. package/dist/chunk-KNJFOURE.js +7151 -0
  65. package/dist/chunk-KY22FIT3.js +7256 -0
  66. package/dist/chunk-L2OTIJSF.js +4228 -0
  67. package/dist/chunk-LEEBUHP6.js +4655 -0
  68. package/dist/chunk-LK7UP2T7.js +130 -0
  69. package/dist/chunk-LPGNO3PK.js +284 -0
  70. package/dist/chunk-LYQYJMWS.js +133 -0
  71. package/dist/chunk-MBYFJXR3.js +130 -0
  72. package/dist/chunk-N3WX44L3.js +130 -0
  73. package/dist/chunk-N6AQWES3.js +6197 -0
  74. package/dist/chunk-NW4XSTQZ.js +130 -0
  75. package/dist/chunk-NWG2XURH.js +130 -0
  76. package/dist/chunk-O3GF3LJD.js +6142 -0
  77. package/dist/chunk-OHP5LD3Y.js +6188 -0
  78. package/dist/chunk-P5PQSFZT.js +6182 -0
  79. package/dist/chunk-PAADOWNP.js +130 -0
  80. package/dist/chunk-PERGND7L.js +7213 -0
  81. package/dist/chunk-PWA7V4XX.js +179 -0
  82. package/dist/chunk-QGCWEP6L.js +7111 -0
  83. package/dist/chunk-QVT2IHNJ.js +175 -0
  84. package/dist/chunk-QZNGYPMS.js +6161 -0
  85. package/dist/chunk-R6P2E2ZQ.js +207 -0
  86. package/dist/chunk-ROSDMGIL.js +4679 -0
  87. package/dist/chunk-RQZP7IKG.js +196 -0
  88. package/dist/chunk-RUQSPX3U.js +133 -0
  89. package/dist/chunk-S3BYHP5M.js +130 -0
  90. package/dist/chunk-S6GNETVE.js +438 -0
  91. package/dist/chunk-SDT2ZE2R.js +133 -0
  92. package/dist/chunk-SHQBXJFC.js +6166 -0
  93. package/dist/chunk-TJNISYTE.js +42 -0
  94. package/dist/chunk-TJW74HFF.js +130 -0
  95. package/dist/chunk-TPP72DTK.js +7096 -0
  96. package/dist/chunk-UOESII6R.js +42 -0
  97. package/dist/chunk-UWEDGLYJ.js +6142 -0
  98. package/dist/chunk-V5FYNAFX.js +133 -0
  99. package/dist/chunk-VPURF6UT.js +7198 -0
  100. package/dist/chunk-VQH6LWIZ.js +6184 -0
  101. package/dist/chunk-VS22YVX6.js +7111 -0
  102. package/dist/chunk-VSF5KBW7.js +367 -0
  103. package/dist/chunk-VV3JMCKY.js +214 -0
  104. package/dist/chunk-W5L7HOE3.js +133 -0
  105. package/dist/chunk-WFEVQISK.js +676 -0
  106. package/dist/chunk-WJZPSCEP.js +516 -0
  107. package/dist/chunk-WLV4MKEF.js +16 -0
  108. package/dist/chunk-WSEVQFFI.js +5428 -0
  109. package/dist/chunk-X7N2RNR3.js +5428 -0
  110. package/dist/chunk-XKZNMRNO.js +133 -0
  111. package/dist/chunk-Y7BVEC36.js +130 -0
  112. package/dist/chunk-YG7YSNNU.js +4226 -0
  113. package/dist/chunk-YHM62466.js +261 -0
  114. package/dist/chunk-YLTYJLDZ.js +7208 -0
  115. package/dist/chunk-YPMJD4YE.js +56 -0
  116. package/dist/chunk-YTX3FU2A.js +7199 -0
  117. package/dist/chunk-ZEQ3EBBN.js +214 -0
  118. package/dist/chunk-ZIWLQSLK.js +42 -0
  119. package/dist/chunk-ZJELNTEO.js +516 -0
  120. package/dist/chunk-ZOSSVNGK.js +370 -0
  121. package/dist/config-EYK32F2E.js +10 -0
  122. package/dist/config-FJPPPYTY.js +10 -0
  123. package/dist/config-VAHPVILX.js +10 -0
  124. package/dist/context-2YGE4U75.js +10 -0
  125. package/dist/context-5UFVYKES.js +9 -0
  126. package/dist/context-ANZF4J72.js +10 -0
  127. package/dist/context-GLUNCUBQ.js +10 -0
  128. package/dist/context-M5ULPZKQ.js +10 -0
  129. package/dist/context-NYOIRZKV.js +10 -0
  130. package/dist/context-YP2REI6A.js +10 -0
  131. package/dist/database-MP2JBLMF.js +8 -0
  132. package/dist/index.js +6177 -4688
  133. package/dist/keyManager-P2SZONKE.js +8 -0
  134. package/dist/mcp/index.js +127 -47
  135. package/dist/memory-BPGJAL4J.js +13 -0
  136. package/dist/memory-FRQOUI6W.js +13 -0
  137. package/dist/memory-OAMK27IZ.js +13 -0
  138. package/dist/memory-OZ734ALL.js +13 -0
  139. package/dist/memory-PQ2EWRMU.js +13 -0
  140. package/dist/memory-PXL45M6W.js +13 -0
  141. package/dist/memory-QZTBTYPH.js +13 -0
  142. package/dist/migrations-TLJ3WRVW.js +188 -0
  143. package/dist/resume-2NHDK6EI.js +17 -0
  144. package/dist/resume-44L2PDB2.js +17 -0
  145. package/dist/resume-4MXIWUJO.js +7 -0
  146. package/dist/resume-72VJX66I.js +17 -0
  147. package/dist/resume-7ZW4XM3B.js +15 -0
  148. package/dist/resume-AZHYQ657.js +17 -0
  149. package/dist/resume-CNLXSYHV.js +17 -0
  150. package/dist/resume-DPN4Q777.js +16 -0
  151. package/dist/resume-DSFHVNPI.js +15 -0
  152. package/dist/resume-GHLQJJTO.js +17 -0
  153. package/dist/resume-HJ6SBWTF.js +17 -0
  154. package/dist/resume-HRLYHY2L.js +17 -0
  155. package/dist/resume-ISIQFKO6.js +17 -0
  156. package/dist/resume-JQDEA6PS.js +15 -0
  157. package/dist/resume-KP3Y3Y7P.js +17 -0
  158. package/dist/resume-M4KHR5OI.js +17 -0
  159. package/dist/resume-N62OAMBG.js +17 -0
  160. package/dist/resume-NNMA5POT.js +17 -0
  161. package/dist/resume-ODYT3J4H.js +17 -0
  162. package/dist/resume-PCFJXA5O.js +17 -0
  163. package/dist/resume-PLF4XGBD.js +15 -0
  164. package/dist/resume-Q2PFX57V.js +17 -0
  165. package/dist/resume-QB4XI2J5.js +17 -0
  166. package/dist/resume-R5MFTUPF.js +17 -0
  167. package/dist/resume-SVA7223Z.js +17 -0
  168. package/dist/resume-TYKKDJZI.js +17 -0
  169. package/dist/resume-XIS45HKV.js +17 -0
  170. package/dist/resume-YCSEJTU7.js +17 -0
  171. package/dist/resume-YD76GI2J.js +15 -0
  172. package/dist/resume-YDN7EL77.js +17 -0
  173. package/dist/resume-YE7DB4ZA.js +17 -0
  174. package/dist/resume-YKAKOXWV.js +15 -0
  175. package/dist/resume-ZHBCVFDY.js +17 -0
  176. package/dist/scheduler-2CK24A2Q.js +14 -0
  177. package/dist/scheduler-7OAF2XKX.js +14 -0
  178. package/dist/scheduler-AS23AAB5.js +14 -0
  179. package/dist/scheduler-PCOYQJA5.js +14 -0
  180. package/dist/scheduler-V2ECBQPK.js +14 -0
  181. package/dist/scheduler-VEWZ6L7V.js +13 -0
  182. package/dist/scheduler-W37QMGDQ.js +14 -0
  183. package/dist/session-2E2JKPD7.js +15 -0
  184. package/dist/session-2VSF257B.js +14 -0
  185. package/dist/session-4APFTDJU.js +14 -0
  186. package/dist/session-5YS5LNNL.js +16 -0
  187. package/dist/session-6MO5ZPOB.js +16 -0
  188. package/dist/session-6XMGPRTQ.js +14 -0
  189. package/dist/session-7DJR77R7.js +16 -0
  190. package/dist/session-7DQHPWTR.js +14 -0
  191. package/dist/session-ADKIQCR5.js +16 -0
  192. package/dist/session-AOGH2GGI.js +16 -0
  193. package/dist/session-C4W6GDYG.js +16 -0
  194. package/dist/session-DCGNGGMV.js +14 -0
  195. package/dist/session-F5JKZAN2.js +16 -0
  196. package/dist/session-G6F3O2FQ.js +16 -0
  197. package/dist/session-GFBSARRO.js +16 -0
  198. package/dist/session-H5IWAIUI.js +16 -0
  199. package/dist/session-IPFA6AHC.js +14 -0
  200. package/dist/session-IWG2UOAX.js +14 -0
  201. package/dist/session-KJ2K4Y4M.js +14 -0
  202. package/dist/session-KPXFBW6Q.js +14 -0
  203. package/dist/session-KR256UL5.js +16 -0
  204. package/dist/session-M72LJXPR.js +16 -0
  205. package/dist/session-MBK3FODN.js +14 -0
  206. package/dist/session-MOUFAU7G.js +16 -0
  207. package/dist/session-NRC6ZXFQ.js +16 -0
  208. package/dist/session-NRPQMV4K.js +16 -0
  209. package/dist/session-O5IFFJZQ.js +14 -0
  210. package/dist/session-OF5BGKDE.js +16 -0
  211. package/dist/session-OGRZMIM7.js +14 -0
  212. package/dist/session-OJOFAJG3.js +16 -0
  213. package/dist/session-OKU4N3SP.js +16 -0
  214. package/dist/session-P2VAOSFB.js +14 -0
  215. package/dist/session-PKOVZD4M.js +16 -0
  216. package/dist/session-POAIMUVN.js +16 -0
  217. package/dist/session-PSHFONFE.js +16 -0
  218. package/dist/session-QKYVVZFV.js +16 -0
  219. package/dist/session-QPWGBMUS.js +14 -0
  220. package/dist/session-R5UG5PZR.js +14 -0
  221. package/dist/session-RAY6BZRQ.js +16 -0
  222. package/dist/session-S3VATHMU.js +16 -0
  223. package/dist/session-SYTD7RHW.js +14 -0
  224. package/dist/session-UHMMVO4J.js +16 -0
  225. package/dist/session-WEX5K3ZY.js +14 -0
  226. package/dist/session-XFLOXGU3.js +14 -0
  227. package/dist/session-XV2A4HHG.js +14 -0
  228. package/dist/settings-3VPJYD4D.js +8 -0
  229. package/dist/settings-GZTJJTBK.js +8 -0
  230. package/dist/settings-YKJFSKMO.js +8 -0
  231. package/dist/shell-FM34624T.js +8 -0
  232. package/package.json +14 -4
@@ -0,0 +1,4226 @@
1
+ import {
2
+ scheduler
3
+ } from "./chunk-XOMN4VJS.js";
4
+ import {
5
+ auditLog,
6
+ redactor
7
+ } from "./chunk-5SWSM5VM.js";
8
+ import {
9
+ context
10
+ } from "./chunk-MSDU36RT.js";
11
+ import {
12
+ activateApp,
13
+ calculateScaleForAPI,
14
+ clickElementByLabel,
15
+ computer,
16
+ findClickableByLabel,
17
+ getButtons,
18
+ getDisplayDimensions,
19
+ getFocusedApp,
20
+ getToolConfig,
21
+ getUIContext,
22
+ takeScreenshotForAPI
23
+ } from "./chunk-MTJI7ZQR.js";
24
+ import {
25
+ config
26
+ } from "./chunk-7MMY74WO.js";
27
+ import {
28
+ settings
29
+ } from "./chunk-7RCNGBCH.js";
30
+ import {
31
+ detectEnvFile,
32
+ keyManager
33
+ } from "./chunk-57D77KRX.js";
34
+ import {
35
+ bus
36
+ } from "./chunk-WQM6FFSD.js";
37
+ import {
38
+ db
39
+ } from "./chunk-FNLWB54Z.js";
40
+
41
+ // src/core/history.ts
42
+ var HistoryManager = class {
43
+ saveTimer = null;
44
+ constructor() {
45
+ }
46
+ async load() {
47
+ const currentSessionId = context.get().session_id;
48
+ if (!currentSessionId) return [];
49
+ try {
50
+ const rows = db.getDb().prepare(`
51
+ SELECT content FROM events
52
+ WHERE session_id = ?
53
+ ORDER BY timestamp ASC
54
+ `).all(currentSessionId);
55
+ return rows.map((r) => JSON.parse(r.content));
56
+ } catch (e) {
57
+ console.error("Failed to load history:", e);
58
+ return [];
59
+ }
60
+ }
61
+ async save(events) {
62
+ if (this.saveTimer) clearTimeout(this.saveTimer);
63
+ this.saveTimer = setTimeout(async () => {
64
+ const currentSessionId = context.get().session_id;
65
+ if (!currentSessionId) return;
66
+ try {
67
+ const transaction = db.getDb().transaction(() => {
68
+ db.getDb().prepare("DELETE FROM events WHERE session_id = ?").run(currentSessionId);
69
+ const insert = db.getDb().prepare(`
70
+ INSERT INTO events (session_id, type, content, timestamp)
71
+ VALUES (?, ?, ?, ?)
72
+ `);
73
+ for (const event of events) {
74
+ insert.run(
75
+ currentSessionId,
76
+ event.type,
77
+ JSON.stringify(event),
78
+ event.timestamp || Date.now()
79
+ );
80
+ }
81
+ });
82
+ transaction();
83
+ } catch (error) {
84
+ console.error("Failed to save history:", error);
85
+ }
86
+ }, 500);
87
+ }
88
+ async archive(events) {
89
+ return null;
90
+ }
91
+ async clear() {
92
+ if (this.saveTimer) clearTimeout(this.saveTimer);
93
+ const currentSessionId = context.get().session_id;
94
+ if (!currentSessionId) return;
95
+ try {
96
+ db.getDb().prepare("DELETE FROM events WHERE session_id = ?").run(currentSessionId);
97
+ } catch {
98
+ }
99
+ }
100
+ };
101
+ var history = new HistoryManager();
102
+
103
+ // src/core/tasks.ts
104
+ var TaskTracker = class {
105
+ task = null;
106
+ constructor() {
107
+ }
108
+ async init() {
109
+ await this.load();
110
+ }
111
+ async load() {
112
+ try {
113
+ const currentSessionId = context.get().session_id;
114
+ if (!currentSessionId) {
115
+ this.task = null;
116
+ return;
117
+ }
118
+ const row = db.getDb().prepare(`
119
+ SELECT * FROM tasks
120
+ WHERE session_id = ?
121
+ AND status != 'done' -- Only load active task? Or the most recent one?
122
+ ORDER BY created_at DESC
123
+ LIMIT 1
124
+ `).get(currentSessionId);
125
+ if (row) {
126
+ const subtasks = db.getDb().prepare(`
127
+ SELECT text, done FROM subtasks
128
+ WHERE task_id = ?
129
+ ORDER BY position ASC
130
+ `).all(row.id);
131
+ this.task = {
132
+ id: row.id,
133
+ title: row.title,
134
+ status: row.status,
135
+ subtasks: subtasks.map((s) => ({ text: s.text, done: s.done === 1 })),
136
+ context: row.context ? JSON.parse(row.context) : [],
137
+ created_at: new Date(row.created_at).toISOString(),
138
+ updated_at: new Date(row.updated_at).toISOString()
139
+ };
140
+ } else {
141
+ this.task = null;
142
+ }
143
+ } catch (e) {
144
+ console.error("Failed to load task:", e);
145
+ this.task = null;
146
+ }
147
+ bus.emitAgent({ type: "task_update", task: this.task });
148
+ }
149
+ async save() {
150
+ if (!this.task) return;
151
+ try {
152
+ const currentSessionId = context.get().session_id;
153
+ const updateSubtasks = db.getDb().transaction(() => {
154
+ db.getDb().prepare(`
155
+ INSERT INTO tasks (id, session_id, title, status, context, created_at, updated_at)
156
+ VALUES (?, ?, ?, ?, ?, ?, ?)
157
+ ON CONFLICT(id) DO UPDATE SET
158
+ status = excluded.status,
159
+ context = excluded.context,
160
+ updated_at = excluded.updated_at
161
+ `).run(
162
+ this.task.id,
163
+ currentSessionId,
164
+ this.task.title,
165
+ this.task.status,
166
+ JSON.stringify(this.task.context),
167
+ new Date(this.task.created_at).getTime(),
168
+ new Date(this.task.updated_at).getTime()
169
+ );
170
+ db.getDb().prepare("DELETE FROM subtasks WHERE task_id = ?").run(this.task.id);
171
+ const stmt = db.getDb().prepare("INSERT INTO subtasks (id, task_id, text, done, position) VALUES (?, ?, ?, ?, ?)");
172
+ this.task.subtasks.forEach((st, idx) => {
173
+ stmt.run(
174
+ this.task.id + "_" + idx,
175
+ // Simple deterministic ID
176
+ this.task.id,
177
+ st.text,
178
+ st.done ? 1 : 0,
179
+ idx
180
+ );
181
+ });
182
+ });
183
+ updateSubtasks();
184
+ bus.emitAgent({ type: "task_update", task: this.task });
185
+ } catch (e) {
186
+ console.error("Failed to save task:", e);
187
+ }
188
+ }
189
+ async archive() {
190
+ }
191
+ // Task management
192
+ async create(title) {
193
+ const now = (/* @__PURE__ */ new Date()).toISOString();
194
+ this.task = {
195
+ id: Date.now().toString(36),
196
+ // Use simple ID, or UUID?
197
+ title,
198
+ status: "in_progress",
199
+ subtasks: [],
200
+ context: [],
201
+ created_at: now,
202
+ updated_at: now
203
+ };
204
+ await this.save();
205
+ return this.task;
206
+ }
207
+ async addSubtask(text) {
208
+ if (!this.task) return;
209
+ this.task.subtasks.push({ text, done: false });
210
+ this.task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
211
+ await this.save();
212
+ }
213
+ async completeSubtask(index) {
214
+ if (!this.task || index >= this.task.subtasks.length) return;
215
+ this.task.subtasks[index].done = true;
216
+ this.task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
217
+ await this.save();
218
+ }
219
+ async setStatus(status) {
220
+ if (!this.task) return;
221
+ this.task.status = status;
222
+ this.task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
223
+ await this.save();
224
+ }
225
+ async addContext(ctx) {
226
+ if (!this.task) return;
227
+ if (!this.task.context.includes(ctx)) {
228
+ this.task.context.push(ctx);
229
+ await this.save();
230
+ }
231
+ }
232
+ async complete() {
233
+ if (!this.task) return;
234
+ this.task.status = "done";
235
+ for (const st of this.task.subtasks) {
236
+ st.done = true;
237
+ }
238
+ await this.save();
239
+ }
240
+ async clear() {
241
+ this.task = null;
242
+ bus.emitAgent({ type: "task_update", task: null });
243
+ }
244
+ // Getters
245
+ get() {
246
+ return this.task ? { ...this.task } : null;
247
+ }
248
+ getProgress() {
249
+ if (!this.task) return "No active task";
250
+ const done = this.task.subtasks.filter((s) => s.done).length;
251
+ const total = this.task.subtasks.length;
252
+ return `${this.task.title} [${done}/${total}]`;
253
+ }
254
+ hasActiveTask() {
255
+ return this.task !== null && this.task.status !== "done";
256
+ }
257
+ };
258
+ var tasks = new TaskTracker();
259
+
260
+ // src/core/usage.ts
261
+ import { z } from "zod";
262
+ var UsageSchema = z.object({
263
+ totalSessions: z.number().default(0),
264
+ totalRequests: z.number().default(0),
265
+ totalInputTokens: z.number().default(0),
266
+ totalOutputTokens: z.number().default(0),
267
+ totalCacheReadTokens: z.number().default(0),
268
+ totalCacheCreationTokens: z.number().default(0),
269
+ totalCost: z.number().default(0)
270
+ });
271
+ var MODEL_PRICES = {
272
+ // Claude 4.5 Family (2025-2026)
273
+ "claude-sonnet-4-5-20250929": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
274
+ "claude-haiku-4-5-20251001": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
275
+ "claude-opus-4-5-20251101": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
276
+ // Claude 3.5 Family
277
+ "claude-3-5-sonnet": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
278
+ "claude-3-5-haiku": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 },
279
+ "claude-3-haiku": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 },
280
+ "claude-3-opus": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
281
+ "claude-3-sonnet": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }
282
+ };
283
+ var CONTEXT_WINDOW_SIZES = {
284
+ "claude-sonnet-4-5-20250929": 2e5,
285
+ "claude-haiku-4-5-20251001": 2e5,
286
+ "claude-opus-4-5-20251101": 2e5,
287
+ "claude-3-5-sonnet": 2e5,
288
+ "claude-3-5-haiku": 2e5
289
+ };
290
+ var UsageTracker = class {
291
+ // In-memory session stats (for quick access/display)
292
+ sessionCost = 0;
293
+ sessionInputTokens = 0;
294
+ sessionOutputTokens = 0;
295
+ sessionCacheReadTokens = 0;
296
+ sessionCacheCreationTokens = 0;
297
+ sessionDuration = 0;
298
+ lastContextSize = 0;
299
+ lastCacheRead = 0;
300
+ lastCacheCreation = 0;
301
+ constructor() {
302
+ }
303
+ async init() {
304
+ }
305
+ async track(model, input, output, cacheRead = 0, cacheCreation = 0, contextSize) {
306
+ let prices = MODEL_PRICES[model];
307
+ if (!prices) {
308
+ const key = Object.keys(MODEL_PRICES).find((k) => model.includes(k));
309
+ prices = key ? MODEL_PRICES[key] : { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
310
+ }
311
+ const costInput = input / 1e6 * prices.input;
312
+ const costOutput = output / 1e6 * prices.output;
313
+ const costCacheRead = cacheRead / 1e6 * prices.cacheRead;
314
+ const costCacheWrite = cacheCreation / 1e6 * prices.cacheWrite;
315
+ const totalReqCost = costInput + costOutput + costCacheRead + costCacheWrite;
316
+ try {
317
+ const currentSessionId = context.get().session_id;
318
+ db.getDb().prepare(`
319
+ INSERT INTO usage_stats (session_id, model, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, cost, timestamp)
320
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
321
+ `).run(currentSessionId, model, input, output, cacheRead, cacheCreation, totalReqCost, Date.now());
322
+ } catch (e) {
323
+ console.error("Failed to log usage to DB:", e);
324
+ }
325
+ this.sessionCost += totalReqCost;
326
+ this.sessionInputTokens += input;
327
+ this.sessionOutputTokens += output;
328
+ this.sessionCacheReadTokens += cacheRead;
329
+ this.sessionCacheCreationTokens += cacheCreation;
330
+ this.lastContextSize = contextSize !== void 0 ? contextSize : input + cacheRead;
331
+ this.lastCacheRead = cacheRead;
332
+ this.lastCacheCreation = cacheCreation;
333
+ }
334
+ getSessionCost() {
335
+ return this.sessionCost;
336
+ }
337
+ getSessionTokens() {
338
+ return {
339
+ input: this.sessionInputTokens,
340
+ output: this.sessionOutputTokens,
341
+ cacheRead: this.sessionCacheReadTokens,
342
+ cacheCreation: this.sessionCacheCreationTokens,
343
+ total: this.sessionInputTokens + this.sessionOutputTokens + this.sessionCacheReadTokens + this.sessionCacheCreationTokens
344
+ };
345
+ }
346
+ addSessionDuration(ms) {
347
+ this.sessionDuration += ms;
348
+ }
349
+ getSessionDuration() {
350
+ return this.sessionDuration;
351
+ }
352
+ getContextUsage(model) {
353
+ let limit = 2e5;
354
+ if (CONTEXT_WINDOW_SIZES[model]) {
355
+ limit = CONTEXT_WINDOW_SIZES[model];
356
+ } else {
357
+ const key = Object.keys(CONTEXT_WINDOW_SIZES).find((k) => model.includes(k));
358
+ if (key) limit = CONTEXT_WINDOW_SIZES[key];
359
+ }
360
+ const used = this.lastContextSize;
361
+ const cached = this.lastCacheRead + this.lastCacheCreation;
362
+ const remaining = Math.max(0, limit - used);
363
+ const percentUsed = used / limit * 100;
364
+ return {
365
+ used,
366
+ cached,
367
+ limit,
368
+ remaining,
369
+ percentRemaining: 100 - percentUsed
370
+ };
371
+ }
372
+ async trackSession() {
373
+ }
374
+ getStats() {
375
+ try {
376
+ const result = db.getDb().prepare(`
377
+ SELECT
378
+ SUM(input_tokens) as totalInputTokens,
379
+ SUM(output_tokens) as totalOutputTokens,
380
+ COALESCE(SUM(cache_read_tokens), 0) as totalCacheReadTokens,
381
+ COALESCE(SUM(cache_creation_tokens), 0) as totalCacheCreationTokens,
382
+ SUM(cost) as totalCost,
383
+ COUNT(*) as totalRequests,
384
+ COUNT(DISTINCT session_id) as totalSessions
385
+ FROM usage_stats
386
+ `).get();
387
+ return {
388
+ totalSessions: result.totalSessions || 0,
389
+ totalRequests: result.totalRequests || 0,
390
+ totalInputTokens: result.totalInputTokens || 0,
391
+ totalOutputTokens: result.totalOutputTokens || 0,
392
+ totalCacheReadTokens: result.totalCacheReadTokens || 0,
393
+ totalCacheCreationTokens: result.totalCacheCreationTokens || 0,
394
+ totalCost: result.totalCost || 0
395
+ };
396
+ } catch (e) {
397
+ return UsageSchema.parse({});
398
+ }
399
+ }
400
+ /**
401
+ * Get cache hit rate for current session
402
+ */
403
+ getCacheHitRate() {
404
+ const totalCached = this.sessionCacheReadTokens;
405
+ const totalInput = this.sessionInputTokens + this.sessionCacheReadTokens + this.sessionCacheCreationTokens;
406
+ const hitRate = totalInput > 0 ? totalCached / totalInput * 100 : 0;
407
+ return { hitRate, totalCached, totalInput };
408
+ }
409
+ restoreSessionState(stats) {
410
+ this.sessionCost = stats.cost;
411
+ this.sessionInputTokens = stats.inputTokens;
412
+ this.sessionOutputTokens = stats.outputTokens;
413
+ this.sessionCacheReadTokens = stats.cacheReadTokens;
414
+ this.sessionCacheCreationTokens = stats.cacheCreationTokens;
415
+ this.sessionDuration = stats.duration;
416
+ this.lastContextSize = stats.inputTokens + stats.cacheReadTokens + stats.cacheCreationTokens;
417
+ this.lastCacheRead = stats.cacheReadTokens;
418
+ }
419
+ };
420
+ var usage = new UsageTracker();
421
+
422
+ // src/core/llm.ts
423
+ import Anthropic from "@anthropic-ai/sdk";
424
+
425
+ // src/core/tools.ts
426
+ import { exec as exec2 } from "child_process";
427
+ import { promisify as promisify2 } from "util";
428
+ import fs5 from "fs/promises";
429
+ import * as fsSync from "fs";
430
+ import path5 from "path";
431
+ import os4 from "os";
432
+
433
+ // src/core/auditor.ts
434
+ import path from "path";
435
+ import fs from "fs/promises";
436
+ var BLOCKED_PATTERNS = [
437
+ "rm -rf /",
438
+ "rm -fr /",
439
+ ":(){:|:&};:",
440
+ // Fork bomb
441
+ "> /dev/sda",
442
+ // Disk overwrite
443
+ "mkfs",
444
+ "dd if=",
445
+ "chmod -R 777",
446
+ ":(){ :|:& };:"
447
+ ];
448
+ var BLOCKED_REGEX_PATTERNS = [
449
+ /curl\s+[^\|]+\|\s*(sh|bash)/i,
450
+ // curl URL | sh/bash
451
+ /wget\s+[^\|]+\|\s*(sh|bash)/i,
452
+ // wget URL | sh/bash
453
+ /curl\s*\|\s*(sh|bash)/i,
454
+ // curl | sh/bash (direct)
455
+ /wget\s*\|\s*(sh|bash)/i
456
+ // wget | sh/bash (direct)
457
+ ];
458
+ var APPROVAL_PATTERNS = [
459
+ { pattern: "rm -rf", reason: "Recursive delete operation" },
460
+ { pattern: "rm -r", reason: "Recursive delete operation" },
461
+ { pattern: "git push --force", reason: "Force push to remote" },
462
+ { pattern: "git reset --hard", reason: "Hard reset (loses changes)" },
463
+ { pattern: "npm publish", reason: "Publishing to npm registry" },
464
+ { pattern: "docker rm", reason: "Removing Docker containers" },
465
+ { pattern: "DROP TABLE", reason: "SQL table deletion" },
466
+ { pattern: "DROP DATABASE", reason: "SQL database deletion" },
467
+ { pattern: "truncate", reason: "Truncating data" }
468
+ ];
469
+ var Auditor = class {
470
+ workspaceRoot;
471
+ constructor(root = process.cwd()) {
472
+ this.workspaceRoot = path.resolve(root);
473
+ }
474
+ setWorkspaceRoot(root) {
475
+ this.workspaceRoot = path.resolve(root);
476
+ }
477
+ async checkCommand(command) {
478
+ const s = await settings.load();
479
+ const lowerCommand = command.toLowerCase();
480
+ if (BLOCKED_PATTERNS.some((p) => command.includes(p))) {
481
+ return {
482
+ approved: false,
483
+ reason: "Detected destructive command pattern",
484
+ isCritical: true
485
+ };
486
+ }
487
+ if (BLOCKED_REGEX_PATTERNS.some((p) => p.test(command))) {
488
+ return {
489
+ approved: false,
490
+ reason: "Detected dangerous pipe-to-shell pattern",
491
+ isCritical: true
492
+ };
493
+ }
494
+ if (await settings.isDenied("bash", command)) {
495
+ return {
496
+ approved: false,
497
+ reason: "Command blocked by settings",
498
+ isCritical: false
499
+ };
500
+ }
501
+ if (s.mode === "safe") {
502
+ if (await settings.isSessionAuthorized("bash", command)) {
503
+ return {
504
+ approved: true,
505
+ autoApproved: true
506
+ };
507
+ }
508
+ } else {
509
+ if (await settings.isAllowed("bash", command)) {
510
+ return {
511
+ approved: true,
512
+ autoApproved: true
513
+ };
514
+ }
515
+ }
516
+ for (const { pattern, reason } of APPROVAL_PATTERNS) {
517
+ if (lowerCommand.includes(pattern.toLowerCase())) {
518
+ return {
519
+ approved: false,
520
+ requiresApproval: true,
521
+ reason
522
+ };
523
+ }
524
+ }
525
+ return { approved: true };
526
+ }
527
+ checkPath(filePath) {
528
+ try {
529
+ const resolved = path.resolve(this.workspaceRoot, filePath);
530
+ const relative = path.relative(this.workspaceRoot, resolved);
531
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
532
+ return { approved: false, reason: `Access denied: Path '${filePath}' is outside the workspace.`, isCritical: true };
533
+ }
534
+ const parts = relative.split(path.sep);
535
+ if (parts.some((p) => p.startsWith(".") && p !== ".obsidian" && p !== ".agent" && p !== ".claude")) {
536
+ }
537
+ return { approved: true };
538
+ } catch (error) {
539
+ return { approved: false, reason: `Invalid path: ${filePath}`, isCritical: false };
540
+ }
541
+ }
542
+ async checkFileEdit(filePath) {
543
+ const pathCheck = this.checkPath(filePath);
544
+ if (!pathCheck.approved) return pathCheck;
545
+ try {
546
+ await fs.access(filePath);
547
+ return { approved: true };
548
+ } catch {
549
+ return { approved: false, reason: `File not found: ${filePath}`, isCritical: false };
550
+ }
551
+ }
552
+ };
553
+ var auditor = new Auditor();
554
+
555
+ // src/core/sandbox.ts
556
+ import { exec } from "child_process";
557
+ import { promisify } from "util";
558
+ import os from "os";
559
+ var execAsync = promisify(exec);
560
+ var DEFAULT_SANDBOX_CONFIG = {
561
+ mode: "local",
562
+ allowedDomains: [
563
+ "*.github.com",
564
+ "*.npmjs.org",
565
+ "*.npmjs.com",
566
+ "api.anthropic.com",
567
+ "registry.npmjs.org"
568
+ ],
569
+ deniedDomains: [],
570
+ denyRead: [
571
+ "~/.ssh",
572
+ "~/.aws",
573
+ "~/.config/gcloud",
574
+ "~/.kube",
575
+ "~/.gnupg"
576
+ ],
577
+ allowWrite: [
578
+ ".",
579
+ // Current workspace
580
+ "/tmp"
581
+ ],
582
+ denyWrite: [
583
+ ".env",
584
+ ".env.*",
585
+ "*.key",
586
+ "*.pem",
587
+ ".git/config"
588
+ ]
589
+ };
590
+ var SandboxExecutor = class {
591
+ initialized = false;
592
+ mode = "local";
593
+ sandboxManager = null;
594
+ config = DEFAULT_SANDBOX_CONFIG;
595
+ /**
596
+ * Initialize sandbox with configuration
597
+ */
598
+ async initialize() {
599
+ try {
600
+ const cfg = await config.load();
601
+ const s = await settings.load();
602
+ if (cfg.executionMode === "sandbox" || s.security.sandbox) {
603
+ this.mode = "sandbox";
604
+ } else {
605
+ this.mode = "local";
606
+ }
607
+ this.config = {
608
+ ...DEFAULT_SANDBOX_CONFIG,
609
+ ...cfg.sandbox || {}
610
+ };
611
+ if (this.mode !== "sandbox") {
612
+ this.initialized = true;
613
+ return true;
614
+ }
615
+ try {
616
+ const { SandboxManager } = await import("@anthropic-ai/sandbox-runtime");
617
+ const runtimeConfig = {
618
+ network: {
619
+ allowedDomains: this.config.allowedDomains,
620
+ deniedDomains: this.config.deniedDomains
621
+ },
622
+ filesystem: {
623
+ denyRead: this.config.denyRead,
624
+ allowWrite: this.config.allowWrite,
625
+ denyWrite: this.config.denyWrite
626
+ }
627
+ };
628
+ await SandboxManager.initialize(runtimeConfig);
629
+ this.sandboxManager = SandboxManager;
630
+ this.initialized = true;
631
+ bus.emitAgent({
632
+ type: "thought",
633
+ content: "[SANDBOX] Initialized with OS-level isolation"
634
+ });
635
+ return true;
636
+ } catch (importError) {
637
+ bus.emitAgent({
638
+ type: "thought",
639
+ content: `[SANDBOX] Runtime library unavailable (Error: ${importError.message}). Falling back to native OS sandbox.`
640
+ });
641
+ this.sandboxManager = null;
642
+ this.initialized = true;
643
+ return true;
644
+ }
645
+ } catch (error) {
646
+ bus.emitAgent({
647
+ type: "error",
648
+ message: `Sandbox initialization failed: ${error.message}`
649
+ });
650
+ return false;
651
+ }
652
+ }
653
+ /**
654
+ * Wrap a command with sandbox protection
655
+ */
656
+ async wrapCommand(command, bypass = false) {
657
+ if (!this.initialized) {
658
+ await this.initialize();
659
+ }
660
+ if (this.mode === "local" || bypass) {
661
+ return command;
662
+ }
663
+ if (this.sandboxManager) {
664
+ try {
665
+ return await this.sandboxManager.wrapWithSandbox(command);
666
+ } catch (error) {
667
+ }
668
+ }
669
+ return this.wrapWithNativeSandbox(command);
670
+ }
671
+ /**
672
+ * Wrap command with native OS sandbox (macOS sandbox-exec or Linux firejail)
673
+ */
674
+ async wrapWithNativeSandbox(command) {
675
+ const platform = os.platform();
676
+ const cfg = await config.load();
677
+ if (platform === "darwin") {
678
+ const profile = `(version 1)
679
+ (deny default)
680
+ (allow process-exec*)
681
+ (allow process-fork)
682
+ (allow signal)
683
+ (allow syscall-unix)
684
+ (allow sysctl-read)
685
+ (deny file-read* (subpath "${os.homedir()}"))
686
+ (allow file-read* (subpath "${cfg.workspaceRoot}"))
687
+ (allow file-read* (subpath "/tmp"))
688
+ (allow file-read* (subpath "/usr"))
689
+ (allow file-read* (subpath "/bin"))
690
+ (allow file-read* (subpath "/sbin"))
691
+ (allow file-read* (subpath "/lib"))
692
+ (allow file-read* (subpath "/private/var/run"))
693
+ (allow file-read* (subpath "/Library/Preferences"))
694
+ (allow file-read* (subpath "/dev"))
695
+ (allow file-write* (subpath "/tmp"))
696
+ (allow file-write* (subpath "${cfg.workspaceRoot}"))
697
+ (deny file-write* (literal "${cfg.workspaceRoot}/.env"))
698
+ (allow network-outbound (remote tcp "*:80" "*:443"))
699
+ (allow network*)
700
+ (allow mach-lookup*)
701
+ (allow iokit*)
702
+ `.trim();
703
+ const profilePath = `/tmp/obsidian-sandbox-${Date.now()}.sb`;
704
+ const fs6 = await import("fs/promises");
705
+ await fs6.writeFile(profilePath, profile);
706
+ const escapedCommand = command.replace(/'/g, "'\\''");
707
+ return `sandbox-exec -f "${profilePath}" bash -c '${escapedCommand}'`;
708
+ }
709
+ if (platform === "linux") {
710
+ try {
711
+ await execAsync("which firejail");
712
+ const escapedCommand = command.replace(/'/g, "'\\''");
713
+ return `firejail --net=none --blacklist=~/.ssh --blacklist=~/.aws --blacklist=~/.gnupg bash -c '${escapedCommand}'`;
714
+ } catch {
715
+ }
716
+ }
717
+ return command;
718
+ }
719
+ /**
720
+ * Get current execution mode
721
+ */
722
+ getMode() {
723
+ return this.mode;
724
+ }
725
+ /**
726
+ * Set execution mode
727
+ */
728
+ async setMode(mode) {
729
+ this.mode = mode;
730
+ if (mode === "sandbox" && !this.sandboxManager) {
731
+ await this.initialize();
732
+ }
733
+ bus.emitAgent({
734
+ type: "thought",
735
+ content: `[sandbox] Execution mode set to: ${mode}`
736
+ });
737
+ }
738
+ /**
739
+ * Get current sandbox configuration
740
+ */
741
+ getConfig() {
742
+ return { ...this.config };
743
+ }
744
+ /**
745
+ * Update sandbox configuration
746
+ */
747
+ updateConfig(updates) {
748
+ this.config = { ...this.config, ...updates };
749
+ }
750
+ /**
751
+ * Reset and cleanup sandbox resources
752
+ */
753
+ async reset() {
754
+ if (this.sandboxManager) {
755
+ try {
756
+ await this.sandboxManager.reset();
757
+ } catch {
758
+ }
759
+ }
760
+ this.initialized = false;
761
+ this.sandboxManager = null;
762
+ }
763
+ /**
764
+ * Check if sandbox mode is available on this system
765
+ */
766
+ async isAvailable() {
767
+ try {
768
+ await import("@anthropic-ai/sandbox-runtime");
769
+ return true;
770
+ } catch {
771
+ return false;
772
+ }
773
+ }
774
+ };
775
+ var sandbox = new SandboxExecutor();
776
+
777
+ // src/core/undo.ts
778
+ import fs2 from "fs/promises";
779
+ import path2 from "path";
780
+ var UndoManager = class {
781
+ sessionId = null;
782
+ changes = [];
783
+ // In-memory stack for fast access
784
+ async init(sessionId) {
785
+ this.sessionId = sessionId;
786
+ this.changes = [];
787
+ }
788
+ async recordChange(filePath, operation, beforeContent, afterContent) {
789
+ const id = `chg_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
790
+ const cfg = await config.load();
791
+ const change = {
792
+ id,
793
+ filePath: path2.relative(cfg.workspaceRoot, path2.resolve(filePath)),
794
+ operation,
795
+ beforeContent,
796
+ afterContent,
797
+ timestamp: /* @__PURE__ */ new Date(),
798
+ undone: false
799
+ };
800
+ this.changes.unshift(change);
801
+ if (this.changes.length > 100) {
802
+ this.changes = this.changes.slice(0, 100);
803
+ }
804
+ return id;
805
+ }
806
+ async undo(count = 1) {
807
+ const toUndo = this.changes.filter((c) => !c.undone).slice(0, count);
808
+ if (toUndo.length === 0) {
809
+ return { success: false, message: "Nothing to undo" };
810
+ }
811
+ const results = [];
812
+ const cfg = await config.load();
813
+ for (const change of toUndo) {
814
+ try {
815
+ const fullPath = path2.resolve(cfg.workspaceRoot, change.filePath);
816
+ switch (change.operation) {
817
+ case "create":
818
+ await fs2.unlink(fullPath);
819
+ results.push(`Deleted: ${change.filePath}`);
820
+ break;
821
+ case "edit":
822
+ if (change.beforeContent !== null) {
823
+ await fs2.writeFile(fullPath, change.beforeContent, "utf-8");
824
+ results.push(`Restored: ${change.filePath}`);
825
+ }
826
+ break;
827
+ case "delete":
828
+ if (change.beforeContent !== null) {
829
+ await fs2.mkdir(path2.dirname(fullPath), { recursive: true });
830
+ await fs2.writeFile(fullPath, change.beforeContent, "utf-8");
831
+ results.push(`Restored: ${change.filePath}`);
832
+ }
833
+ break;
834
+ }
835
+ change.undone = true;
836
+ } catch (error) {
837
+ results.push(`Failed: ${change.filePath} - ${error.message}`);
838
+ }
839
+ }
840
+ return {
841
+ success: true,
842
+ message: results.join("\n")
843
+ };
844
+ }
845
+ getHistory(limit = 10) {
846
+ return this.changes.filter((c) => !c.undone).slice(0, limit);
847
+ }
848
+ async close() {
849
+ }
850
+ };
851
+ var undo = new UndoManager();
852
+
853
+ // src/core/diff.ts
854
+ import fs3 from "fs/promises";
855
+ import path3 from "path";
856
+ import os2 from "os";
857
+ var DIFF_DIR = ".obsidian-next/diffs";
858
+ var MAX_DIFF_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
859
+ var MAX_DIFFS = 100;
860
+ function generateDiff(oldContent, newContent, filePath) {
861
+ const oldLines = oldContent.split("\n");
862
+ const newLines = newContent.split("\n");
863
+ const hunks = computeHunks(oldLines, newLines);
864
+ if (hunks.length === 0) {
865
+ return "";
866
+ }
867
+ const header = [
868
+ `--- a/${filePath}`,
869
+ `+++ b/${filePath}`
870
+ ];
871
+ const diffLines = [...header];
872
+ for (const hunk of hunks) {
873
+ const hunkHeader = `@@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@`;
874
+ diffLines.push(hunkHeader);
875
+ diffLines.push(...hunk.lines);
876
+ }
877
+ return diffLines.join("\n");
878
+ }
879
+ function computeHunks(oldLines, newLines) {
880
+ const hunks = [];
881
+ const lcs = computeLCS(oldLines, newLines);
882
+ let oldIdx = 0;
883
+ let newIdx = 0;
884
+ let lcsIdx = 0;
885
+ let currentHunk = null;
886
+ const contextLines = 3;
887
+ while (oldIdx < oldLines.length || newIdx < newLines.length) {
888
+ const lcsLine = lcsIdx < lcs.length ? lcs[lcsIdx] : null;
889
+ const oldMatches = lcsLine !== null && oldIdx < oldLines.length && oldLines[oldIdx] === lcsLine;
890
+ const newMatches = lcsLine !== null && newIdx < newLines.length && newLines[newIdx] === lcsLine;
891
+ if (oldMatches && newMatches) {
892
+ if (currentHunk) {
893
+ currentHunk.lines.push(` ${oldLines[oldIdx]}`);
894
+ currentHunk.oldCount++;
895
+ currentHunk.newCount++;
896
+ const remainingChanges = hasMoreChanges(oldLines, newLines, lcs, oldIdx + 1, newIdx + 1, lcsIdx + 1);
897
+ if (!remainingChanges || currentHunk.lines.length > 50) {
898
+ hunks.push(currentHunk);
899
+ currentHunk = null;
900
+ }
901
+ }
902
+ oldIdx++;
903
+ newIdx++;
904
+ lcsIdx++;
905
+ } else if (!oldMatches && oldIdx < oldLines.length && (lcsLine === null || oldLines[oldIdx] !== lcsLine)) {
906
+ if (!currentHunk) {
907
+ currentHunk = createHunk(oldIdx + 1, newIdx + 1);
908
+ }
909
+ currentHunk.lines.push(`-${oldLines[oldIdx]}`);
910
+ currentHunk.oldCount++;
911
+ oldIdx++;
912
+ } else if (!newMatches && newIdx < newLines.length && (lcsLine === null || newLines[newIdx] !== lcsLine)) {
913
+ if (!currentHunk) {
914
+ currentHunk = createHunk(oldIdx + 1, newIdx + 1);
915
+ }
916
+ currentHunk.lines.push(`+${newLines[newIdx]}`);
917
+ currentHunk.newCount++;
918
+ newIdx++;
919
+ } else {
920
+ if (oldIdx < oldLines.length) oldIdx++;
921
+ if (newIdx < newLines.length) newIdx++;
922
+ }
923
+ }
924
+ if (currentHunk && currentHunk.lines.length > 0) {
925
+ hunks.push(currentHunk);
926
+ }
927
+ return hunks;
928
+ }
929
+ function createHunk(oldStart, newStart) {
930
+ return {
931
+ oldStart,
932
+ oldCount: 0,
933
+ newStart,
934
+ newCount: 0,
935
+ lines: []
936
+ };
937
+ }
938
+ function hasMoreChanges(oldLines, newLines, lcs, oldIdx, newIdx, lcsIdx) {
939
+ const lookAhead = 6;
940
+ for (let i = 0; i < lookAhead; i++) {
941
+ const oi = oldIdx + i;
942
+ const ni = newIdx + i;
943
+ const li = lcsIdx + i;
944
+ if (oi >= oldLines.length && ni >= newLines.length) return false;
945
+ const lcsLine = li < lcs.length ? lcs[li] : null;
946
+ if (lcsLine === null) return oi < oldLines.length || ni < newLines.length;
947
+ if (oi < oldLines.length && oldLines[oi] !== lcsLine) return true;
948
+ if (ni < newLines.length && ni < newLines.length && newLines[ni] !== lcsLine) return true;
949
+ }
950
+ return false;
951
+ }
952
+ function computeLCS(a, b) {
953
+ const m = a.length;
954
+ const n = b.length;
955
+ if (m > 1e3 || n > 1e3) {
956
+ return simpleLCS(a, b);
957
+ }
958
+ const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
959
+ for (let i2 = 1; i2 <= m; i2++) {
960
+ for (let j2 = 1; j2 <= n; j2++) {
961
+ if (a[i2 - 1] === b[j2 - 1]) {
962
+ dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
963
+ } else {
964
+ dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
965
+ }
966
+ }
967
+ }
968
+ const lcs = [];
969
+ let i = m, j = n;
970
+ while (i > 0 && j > 0) {
971
+ if (a[i - 1] === b[j - 1]) {
972
+ lcs.unshift(a[i - 1]);
973
+ i--;
974
+ j--;
975
+ } else if (dp[i - 1][j] > dp[i][j - 1]) {
976
+ i--;
977
+ } else {
978
+ j--;
979
+ }
980
+ }
981
+ return lcs;
982
+ }
983
+ function simpleLCS(a, b) {
984
+ const bSet = new Set(b);
985
+ return a.filter((line) => bSet.has(line));
986
+ }
987
+ function countChanges(diff) {
988
+ const lines = diff.split("\n");
989
+ let additions = 0;
990
+ let deletions = 0;
991
+ for (const line of lines) {
992
+ if (line.startsWith("+") && !line.startsWith("+++")) {
993
+ additions++;
994
+ } else if (line.startsWith("-") && !line.startsWith("---")) {
995
+ deletions++;
996
+ }
997
+ }
998
+ return { additions, deletions };
999
+ }
1000
+ var DiffManager = class {
1001
+ diffDir = null;
1002
+ constructor() {
1003
+ }
1004
+ async getDiffDir() {
1005
+ if (this.diffDir) return this.diffDir;
1006
+ this.diffDir = path3.join(os2.homedir(), DIFF_DIR);
1007
+ return this.diffDir;
1008
+ }
1009
+ /**
1010
+ * Save a diff to storage
1011
+ */
1012
+ async saveDiff(filePath, oldContent, newContent) {
1013
+ const diff = generateDiff(oldContent, newContent, filePath);
1014
+ if (!diff) {
1015
+ return null;
1016
+ }
1017
+ const diffDir = await this.getDiffDir();
1018
+ await fs3.mkdir(diffDir, { recursive: true });
1019
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1020
+ const { additions, deletions } = countChanges(diff);
1021
+ const entry = {
1022
+ timestamp,
1023
+ filePath,
1024
+ beforeLines: oldContent.split("\n").length,
1025
+ afterLines: newContent.split("\n").length,
1026
+ additions,
1027
+ deletions,
1028
+ diff
1029
+ };
1030
+ const sanitizedPath = filePath.replace(/[/\\]/g, "_").replace(/[^a-zA-Z0-9_.-]/g, "");
1031
+ const filename = `${Date.now()}_${sanitizedPath}.diff.json`;
1032
+ await fs3.writeFile(
1033
+ path3.join(diffDir, filename),
1034
+ JSON.stringify(entry, null, 2)
1035
+ );
1036
+ await this.cleanup();
1037
+ return entry;
1038
+ }
1039
+ /**
1040
+ * List recent diffs
1041
+ */
1042
+ async listDiffs(limit = 20) {
1043
+ try {
1044
+ const diffDir = await this.getDiffDir();
1045
+ await fs3.mkdir(diffDir, { recursive: true });
1046
+ const files = await fs3.readdir(diffDir);
1047
+ const diffs = [];
1048
+ for (const file of files.slice(-limit * 2)) {
1049
+ if (!file.endsWith(".diff.json")) continue;
1050
+ try {
1051
+ const content = await fs3.readFile(
1052
+ path3.join(diffDir, file),
1053
+ "utf-8"
1054
+ );
1055
+ diffs.push(JSON.parse(content));
1056
+ } catch {
1057
+ }
1058
+ }
1059
+ return diffs.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()).slice(0, limit);
1060
+ } catch {
1061
+ return [];
1062
+ }
1063
+ }
1064
+ /**
1065
+ * Get diff for a specific file
1066
+ */
1067
+ async getDiffForFile(filePath) {
1068
+ const diffs = await this.listDiffs(100);
1069
+ return diffs.find((d) => d.filePath === filePath) || null;
1070
+ }
1071
+ /**
1072
+ * Cleanup old diffs
1073
+ */
1074
+ async cleanup() {
1075
+ try {
1076
+ const diffDir = await this.getDiffDir();
1077
+ const files = await fs3.readdir(diffDir);
1078
+ const now = Date.now();
1079
+ const validFiles = [];
1080
+ for (const file of files) {
1081
+ if (!file.endsWith(".diff.json")) continue;
1082
+ const match = file.match(/^(\d+)_/);
1083
+ if (match) {
1084
+ const time = parseInt(match[1], 10);
1085
+ if (now - time > MAX_DIFF_AGE_MS) {
1086
+ await fs3.unlink(path3.join(diffDir, file));
1087
+ } else {
1088
+ validFiles.push({ name: file, time });
1089
+ }
1090
+ }
1091
+ }
1092
+ if (validFiles.length > MAX_DIFFS) {
1093
+ validFiles.sort((a, b) => a.time - b.time);
1094
+ const toDelete = validFiles.slice(0, validFiles.length - MAX_DIFFS);
1095
+ for (const { name } of toDelete) {
1096
+ await fs3.unlink(path3.join(diffDir, name));
1097
+ }
1098
+ }
1099
+ } catch {
1100
+ }
1101
+ }
1102
+ /**
1103
+ * Clear all diffs
1104
+ */
1105
+ async clearAll() {
1106
+ try {
1107
+ const diffDir = await this.getDiffDir();
1108
+ const files = await fs3.readdir(diffDir);
1109
+ for (const file of files) {
1110
+ if (file.endsWith(".diff.json")) {
1111
+ await fs3.unlink(path3.join(diffDir, file));
1112
+ }
1113
+ }
1114
+ } catch {
1115
+ }
1116
+ }
1117
+ };
1118
+ var diffManager = new DiffManager();
1119
+
1120
+ // src/core/mcp.ts
1121
+ import path4 from "path";
1122
+ import fs4 from "fs/promises";
1123
+ import os3 from "os";
1124
+ import { z as z2 } from "zod";
1125
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1126
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
1127
+ import { CallToolResultSchema, ListToolsResultSchema } from "@modelcontextprotocol/sdk/types.js";
1128
+ var MCPServerConfigSchema = z2.object({
1129
+ command: z2.string(),
1130
+ args: z2.array(z2.string()).default([]),
1131
+ env: z2.record(z2.string()).optional(),
1132
+ secureEnv: z2.array(z2.string()).optional(),
1133
+ // List of env vars to load from KeyManager
1134
+ autoConnect: z2.boolean().default(false)
1135
+ });
1136
+ var MCPConfigSchema = z2.object({
1137
+ servers: z2.record(MCPServerConfigSchema).default({})
1138
+ });
1139
+ var MCPManager = class {
1140
+ connections = /* @__PURE__ */ new Map();
1141
+ config = { servers: {} };
1142
+ constructor() {
1143
+ }
1144
+ /**
1145
+ * Load configuration from .obsidian-next/mcp.json
1146
+ */
1147
+ async init() {
1148
+ try {
1149
+ const configPath = path4.join(os3.homedir(), ".obsidian-next", "mcp.json");
1150
+ const exists = await fs4.stat(configPath).then(() => true).catch(() => false);
1151
+ if (exists) {
1152
+ const content = await fs4.readFile(configPath, "utf-8");
1153
+ this.config = MCPConfigSchema.parse(JSON.parse(content));
1154
+ } else {
1155
+ this.config = { servers: {} };
1156
+ }
1157
+ Object.entries(this.config.servers).forEach(([name, serverConfig]) => {
1158
+ if (serverConfig.autoConnect) {
1159
+ this.connect(name, serverConfig).catch((err) => {
1160
+ });
1161
+ }
1162
+ });
1163
+ } catch (error) {
1164
+ console.error("Failed to initialize MCP Manager:", error);
1165
+ }
1166
+ }
1167
+ /**
1168
+ * Connect to an MCP server
1169
+ */
1170
+ async connect(name, serverConfig) {
1171
+ if (this.connections.has(name)) {
1172
+ return;
1173
+ }
1174
+ try {
1175
+ const sanitizedEnv = serverConfig.env ? Object.fromEntries(
1176
+ Object.entries(serverConfig.env).map(([k, v]) => [k, v.trim()])
1177
+ ) : {};
1178
+ if (serverConfig.secureEnv) {
1179
+ for (const envName of serverConfig.secureEnv) {
1180
+ const key = await keyManager.loadKey({ service: "obsidian-mcp", account: envName });
1181
+ if (key) {
1182
+ sanitizedEnv[envName] = key;
1183
+ } else {
1184
+ console.warn(`[MCP] Missing secure key for ${envName} (server: ${name})`);
1185
+ }
1186
+ }
1187
+ }
1188
+ const transport = new StdioClientTransport({
1189
+ command: serverConfig.command,
1190
+ args: serverConfig.args,
1191
+ env: sanitizedEnv
1192
+ });
1193
+ const client = new Client(
1194
+ {
1195
+ name: "obsidian-next-client",
1196
+ version: "0.4.5"
1197
+ },
1198
+ {
1199
+ capabilities: {
1200
+ // Client does not provide tools, it consumes them
1201
+ }
1202
+ }
1203
+ );
1204
+ const connectTimeout = 1e4;
1205
+ await Promise.race([
1206
+ client.connect(transport),
1207
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Connection timeout")), connectTimeout))
1208
+ ]);
1209
+ this.connections.set(name, {
1210
+ client,
1211
+ transport,
1212
+ capabilities: client.getServerCapabilities()
1213
+ });
1214
+ if (this.config.servers[name] && !this.config.servers[name].autoConnect) {
1215
+ this.config.servers[name].autoConnect = true;
1216
+ await this.saveConfig();
1217
+ }
1218
+ } catch (error) {
1219
+ throw new Error(`Failed to connect to ${name}: ${error}`);
1220
+ }
1221
+ }
1222
+ /**
1223
+ * Add and connect to a new server, then persist config
1224
+ * Set skipConnect to true if the server requires manual config (API keys) before it can start
1225
+ */
1226
+ async addServer(name, serverConfig, skipConnect = false) {
1227
+ if (this.config.servers[name]) {
1228
+ throw new Error(`Server '${name}' already exists`);
1229
+ }
1230
+ this.config.servers[name] = serverConfig;
1231
+ try {
1232
+ if (!skipConnect) {
1233
+ await this.connect(name, serverConfig);
1234
+ }
1235
+ await this.saveConfig();
1236
+ } catch (error) {
1237
+ delete this.config.servers[name];
1238
+ throw error;
1239
+ }
1240
+ }
1241
+ /**
1242
+ * Remove a server and persist config
1243
+ */
1244
+ async removeServer(name) {
1245
+ if (!this.config.servers[name]) {
1246
+ throw new Error(`Server '${name}' not found`);
1247
+ }
1248
+ await this.disconnect(name);
1249
+ delete this.config.servers[name];
1250
+ await this.saveConfig();
1251
+ }
1252
+ /**
1253
+ * Update an existing server configuration and persist
1254
+ */
1255
+ async updateServer(name, updates) {
1256
+ const existing = this.config.servers[name];
1257
+ if (!existing) {
1258
+ throw new Error(`Server '${name}' not found`);
1259
+ }
1260
+ this.config.servers[name] = {
1261
+ ...existing,
1262
+ ...updates,
1263
+ env: {
1264
+ ...existing.env || {},
1265
+ ...updates.env || {}
1266
+ }
1267
+ };
1268
+ await this.saveConfig();
1269
+ if (this.connections.has(name)) {
1270
+ await this.disconnect(name);
1271
+ await this.connect(name, this.config.servers[name]);
1272
+ }
1273
+ }
1274
+ /**
1275
+ * Persist current config to disk
1276
+ */
1277
+ async saveConfig() {
1278
+ const configPath = path4.join(os3.homedir(), ".obsidian-next", "mcp.json");
1279
+ await fs4.mkdir(path4.dirname(configPath), { recursive: true });
1280
+ await fs4.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
1281
+ }
1282
+ /**
1283
+ * Disconnect from a server
1284
+ */
1285
+ async disconnect(name) {
1286
+ const conn = this.connections.get(name);
1287
+ if (conn) {
1288
+ try {
1289
+ const disconnectTimeout = 2e3;
1290
+ await Promise.race([
1291
+ conn.client.close(),
1292
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Disconnect timeout")), disconnectTimeout))
1293
+ ]);
1294
+ } catch (error) {
1295
+ console.warn(`Disconnect from ${name} timed out or failed:`, error);
1296
+ } finally {
1297
+ try {
1298
+ const transport = conn.transport;
1299
+ if (transport._process && typeof transport._process.kill === "function") {
1300
+ transport._process.kill("SIGKILL");
1301
+ }
1302
+ } catch (e) {
1303
+ }
1304
+ this.connections.delete(name);
1305
+ if (this.config.servers[name] && this.config.servers[name].autoConnect) {
1306
+ this.config.servers[name].autoConnect = false;
1307
+ await this.saveConfig();
1308
+ }
1309
+ }
1310
+ }
1311
+ }
1312
+ /**
1313
+ * List all tools from all connected servers
1314
+ */
1315
+ async listTools() {
1316
+ const allTools = [];
1317
+ for (const [serverName, conn] of this.connections) {
1318
+ try {
1319
+ const timeoutMs = 5e3;
1320
+ const timeoutPromise = new Promise(
1321
+ (_, reject) => setTimeout(() => reject(new Error("Timeout listing tools")), timeoutMs)
1322
+ );
1323
+ const result = await Promise.race([
1324
+ conn.client.request(
1325
+ { method: "tools/list" },
1326
+ // Raw request or use helper if available
1327
+ ListToolsResultSchema
1328
+ ),
1329
+ timeoutPromise
1330
+ ]);
1331
+ if (result && result.tools) {
1332
+ const serverTools = result.tools.map((t) => ({
1333
+ ...t,
1334
+ server: serverName
1335
+ // Attach server name for routing
1336
+ // If we wanted to rename: name: `${serverName}__${t.name}`
1337
+ }));
1338
+ allTools.push(...serverTools);
1339
+ }
1340
+ } catch (error) {
1341
+ console.error(`Failed to list tools from ${serverName}:`, error);
1342
+ }
1343
+ }
1344
+ return allTools;
1345
+ }
1346
+ /**
1347
+ * Call a specific tool on a server
1348
+ */
1349
+ async callTool(serverName, toolName, args) {
1350
+ const conn = this.connections.get(serverName);
1351
+ if (!conn) {
1352
+ throw new Error(`Server '${serverName}' not connected.`);
1353
+ }
1354
+ try {
1355
+ const result = await conn.client.request(
1356
+ {
1357
+ method: "tools/call",
1358
+ params: {
1359
+ name: toolName,
1360
+ arguments: args
1361
+ }
1362
+ },
1363
+ CallToolResultSchema
1364
+ );
1365
+ return result;
1366
+ } catch (error) {
1367
+ throw new Error(`Tool execution failed on ${serverName}/${toolName}: ${error}`);
1368
+ }
1369
+ }
1370
+ /**
1371
+ * Get active connections status
1372
+ */
1373
+ getStatus() {
1374
+ return Object.keys(this.config.servers).map((name) => ({
1375
+ name,
1376
+ config: this.config.servers[name],
1377
+ connected: this.connections.has(name),
1378
+ capabilities: this.connections.get(name)?.capabilities
1379
+ }));
1380
+ }
1381
+ };
1382
+ var mcp = new MCPManager();
1383
+
1384
+ // src/core/mcp-registry.ts
1385
+ var MCP_REGISTRY = {
1386
+ "filesystem": {
1387
+ name: "filesystem",
1388
+ description: "Access local files and directories outside the workspace (requires permission)",
1389
+ command: "npx",
1390
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/"]
1391
+ // Root access, dangerous but powerful
1392
+ },
1393
+ "research": {
1394
+ name: "research",
1395
+ description: "Search the web using Brave Search. Excellent for docs and current info.",
1396
+ command: "npx",
1397
+ args: ["-y", "@modelcontextprotocol/server-brave-search"],
1398
+ requiresApiKey: true,
1399
+ env: {
1400
+ "BRAVE_API_KEY": "Needs Configuration"
1401
+ }
1402
+ },
1403
+ "git": {
1404
+ name: "git",
1405
+ description: "Git repository management operations",
1406
+ command: "npx",
1407
+ args: ["-y", "@modelcontextprotocol/server-git"]
1408
+ },
1409
+ "memory": {
1410
+ name: "memory",
1411
+ description: "Persistent knowledge graph for long-term agent memory",
1412
+ command: "npx",
1413
+ args: ["-y", "@modelcontextprotocol/server-memory"]
1414
+ },
1415
+ "google-search": {
1416
+ name: "google-search",
1417
+ description: "Search the web using official Google Custom Search API",
1418
+ command: "npx",
1419
+ args: ["-y", "@mcp-get/google-search"],
1420
+ requiresApiKey: true,
1421
+ env: {
1422
+ "GOOGLE_API_KEY": "Needs Key",
1423
+ "GOOGLE_CSE_ID": "Needs ID"
1424
+ }
1425
+ },
1426
+ "github": {
1427
+ name: "github",
1428
+ description: "Advanced GitHub repository management (Issues, PRs, etc)",
1429
+ command: "npx",
1430
+ args: ["-y", "@modelcontextprotocol/server-github"],
1431
+ requiresApiKey: true,
1432
+ env: {
1433
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "Needs Token"
1434
+ }
1435
+ },
1436
+ "context7": {
1437
+ name: "context7",
1438
+ description: "Official documentation search and retrieval (Certified Correct Docs)",
1439
+ command: "npx",
1440
+ args: ["-y", "@upstash/context7-mcp"],
1441
+ requiresApiKey: true,
1442
+ env: {
1443
+ "CONTEXT7_API_KEY": "Needs Key"
1444
+ }
1445
+ }
1446
+ };
1447
+ function getRegistryDefinition(name) {
1448
+ return MCP_REGISTRY[name];
1449
+ }
1450
+ function listRegistry() {
1451
+ return Object.values(MCP_REGISTRY);
1452
+ }
1453
+
1454
+ // src/core/tools.ts
1455
+ var execAsync2 = promisify2(exec2);
1456
+ var lastScreenshotScale = 1;
1457
+ var MAX_OUTPUT_LENGTH = 1e4;
1458
+ var MAX_FILE_READ_LINES = 500;
1459
+ var IGNORED_DIRS = ["node_modules", ".git", "dist", ".next", "__pycache__", ".cache", "coverage"];
1460
+ var APPROVAL_TIMEOUT = 3e4;
1461
+ function truncateOutput(output, maxLength = MAX_OUTPUT_LENGTH) {
1462
+ if (output.length <= maxLength) return output;
1463
+ const truncated = output.slice(0, maxLength);
1464
+ const remaining = output.length - maxLength;
1465
+ return `${truncated}
1466
+
1467
+ ... [TRUNCATED: ${remaining} more characters]`;
1468
+ }
1469
+ function filterSystemNoise(stderr) {
1470
+ if (!stderr) return stderr;
1471
+ const noisePatterns = [
1472
+ /^aks:aks_get_lock_state:\d+:\d+: aks connection failed\s*/gm,
1473
+ // macOS keychain noise
1474
+ /^objc\[\d+\]: .* may have been in progress in another thread.*$/gm,
1475
+ // Objective-C runtime
1476
+ /^Warning: .* is deprecated.*$/gm,
1477
+ // Deprecation warnings
1478
+ /^\[warn\].*$/gmi,
1479
+ // Generic warn prefixes
1480
+ /^MESA-LOADER:.*$/gm,
1481
+ // Mesa graphics loader
1482
+ /^libEGL warning:.*$/gm,
1483
+ // EGL warnings
1484
+ /^Fontconfig warning:.*$/gm
1485
+ // Font config
1486
+ ];
1487
+ let filtered = stderr;
1488
+ for (const pattern of noisePatterns) {
1489
+ filtered = filtered.replace(pattern, "");
1490
+ }
1491
+ filtered = filtered.replace(/^\s*[\r\n]/gm, "").trim();
1492
+ return filtered;
1493
+ }
1494
+ var pendingApprovals = /* @__PURE__ */ new Map();
1495
+ bus.on("user", (event) => {
1496
+ if (event.type === "approval_response") {
1497
+ const pending = pendingApprovals.get(event.requestId);
1498
+ if (pending) {
1499
+ clearTimeout(pending.timeout);
1500
+ pendingApprovals.delete(event.requestId);
1501
+ pending.resolve({ approved: event.approved, scope: event.scope, bypass: event.bypass });
1502
+ }
1503
+ }
1504
+ });
1505
+ async function requestApproval(command, reason) {
1506
+ const requestId = `approval_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1507
+ return new Promise((resolve) => {
1508
+ const timeout = setTimeout(() => {
1509
+ pendingApprovals.delete(requestId);
1510
+ bus.emitAgent({
1511
+ type: "error",
1512
+ message: "No response received. Command blocked for safety."
1513
+ });
1514
+ resolve({ approved: false, scope: "session" });
1515
+ }, APPROVAL_TIMEOUT);
1516
+ pendingApprovals.set(requestId, { resolve, timeout });
1517
+ const context2 = [
1518
+ `Command: ${command}`,
1519
+ `Reason: ${reason}`
1520
+ ].join("\n");
1521
+ bus.emitAgent({
1522
+ type: "approval_request",
1523
+ requestId,
1524
+ context: context2
1525
+ });
1526
+ });
1527
+ }
1528
+ var BashTool = {
1529
+ name: "bash",
1530
+ description: "Execute shell commands in the workspace",
1531
+ inputSchema: {
1532
+ command: {
1533
+ type: "string",
1534
+ description: "The shell command to execute"
1535
+ }
1536
+ },
1537
+ requiredParams: ["command"],
1538
+ async execute(args) {
1539
+ const command = args.command;
1540
+ if (!command) {
1541
+ return { success: false, error: "No command provided" };
1542
+ }
1543
+ const audit = await auditor.checkCommand(command);
1544
+ if (!audit.approved && audit.isCritical) {
1545
+ await auditLog.logSecurityViolation(command, audit.reason || "Critical security violation");
1546
+ return {
1547
+ success: false,
1548
+ error: `Security violation: ${audit.reason}`
1549
+ };
1550
+ }
1551
+ if (!audit.approved && audit.requiresApproval) {
1552
+ await auditLog.logApproval("requested", command, audit.reason);
1553
+ const { approved, scope, bypass } = await requestApproval(command, audit.reason || "Potentially dangerous operation");
1554
+ if (approved) {
1555
+ if (scope === "persistent") {
1556
+ if (bypass) {
1557
+ await settings.addUnsandboxedPermission("bash", command);
1558
+ } else {
1559
+ await settings.addAllowedPermission("bash", command);
1560
+ }
1561
+ } else {
1562
+ await settings.addSessionPermission("bash", command, true, bypass);
1563
+ }
1564
+ await auditLog.logApproval("granted", command, bypass ? "Bypass enabled" : void 0);
1565
+ } else {
1566
+ if (scope === "persistent") {
1567
+ await settings.addDeniedPermission("bash", command);
1568
+ } else {
1569
+ await settings.addSessionPermission("bash", command, false);
1570
+ }
1571
+ await auditLog.logApproval("denied", command);
1572
+ return {
1573
+ success: false,
1574
+ error: "Command rejected by user"
1575
+ };
1576
+ }
1577
+ }
1578
+ const bypassSandbox = await settings.isUnsandboxed("bash", command);
1579
+ const cfg = await config.load();
1580
+ try {
1581
+ const execCommand = await sandbox.wrapCommand(command, bypassSandbox);
1582
+ const { stdout, stderr } = await execAsync2(execCommand, {
1583
+ cwd: cfg.workspaceRoot,
1584
+ timeout: 3e4,
1585
+ // 30 second timeout
1586
+ maxBuffer: 1024 * 1024
1587
+ // 1MB buffer (reduced from 10MB)
1588
+ });
1589
+ const filteredStderr = filterSystemNoise(stderr);
1590
+ const output = stdout || filteredStderr || "Command executed successfully";
1591
+ await auditLog.logCommand(command, true);
1592
+ return {
1593
+ success: true,
1594
+ output: truncateOutput(output)
1595
+ };
1596
+ } catch (error) {
1597
+ await auditLog.logCommand(command, false, error.message);
1598
+ return {
1599
+ success: false,
1600
+ error: error.message || "Command execution failed"
1601
+ };
1602
+ }
1603
+ }
1604
+ };
1605
+ var ReadTool = {
1606
+ name: "read",
1607
+ description: "Read file contents from the workspace",
1608
+ inputSchema: {
1609
+ path: {
1610
+ type: "string",
1611
+ description: "Path to the file to read (relative to workspace)"
1612
+ }
1613
+ },
1614
+ requiredParams: ["path"],
1615
+ async execute(args) {
1616
+ const filePath = args.path;
1617
+ if (!filePath) {
1618
+ return { success: false, error: "No file path provided" };
1619
+ }
1620
+ const pathCheck = auditor.checkPath(filePath);
1621
+ if (!pathCheck.approved) {
1622
+ return {
1623
+ success: false,
1624
+ error: pathCheck.reason
1625
+ };
1626
+ }
1627
+ if (IGNORED_DIRS.some((dir) => filePath.includes(`/${dir}/`) || filePath.startsWith(`${dir}/`))) {
1628
+ return {
1629
+ success: false,
1630
+ error: `Cannot read from ignored directory. Paths containing ${IGNORED_DIRS.join(", ")} are blocked to prevent context explosion.`
1631
+ };
1632
+ }
1633
+ try {
1634
+ const cfg = await config.load();
1635
+ const fullPath = path5.resolve(cfg.workspaceRoot, filePath);
1636
+ const content = await fs5.readFile(fullPath, "utf-8");
1637
+ const lines = content.split("\n");
1638
+ const limitedLines = lines.slice(0, MAX_FILE_READ_LINES);
1639
+ const numbered = limitedLines.map((line, i) => `${String(i + 1).padStart(4)} | ${line}`).join("\n");
1640
+ const truncationNote = lines.length > MAX_FILE_READ_LINES ? `
1641
+
1642
+ ... [TRUNCATED: ${lines.length - MAX_FILE_READ_LINES} more lines. Use offset parameter to read more.]` : "";
1643
+ await context.trackRead(filePath);
1644
+ await auditLog.logFileOperation("read", filePath, true);
1645
+ return {
1646
+ success: true,
1647
+ output: truncateOutput(`File: ${filePath} (${lines.length} lines)
1648
+ ${"=".repeat(60)}
1649
+ ${numbered}${truncationNote}`)
1650
+ };
1651
+ } catch (error) {
1652
+ await auditLog.logFileOperation("read", filePath, false, error.message);
1653
+ return {
1654
+ success: false,
1655
+ error: `Failed to read file: ${error.message}`
1656
+ };
1657
+ }
1658
+ }
1659
+ };
1660
+ var WriteTool = {
1661
+ name: "write",
1662
+ description: "Create new files in the workspace",
1663
+ inputSchema: {
1664
+ path: {
1665
+ type: "string",
1666
+ description: "Path where to create the new file"
1667
+ },
1668
+ content: {
1669
+ type: "string",
1670
+ description: "Content to write to the file"
1671
+ }
1672
+ },
1673
+ requiredParams: ["path", "content"],
1674
+ async execute(args) {
1675
+ const filePath = args.path;
1676
+ const content = args.content;
1677
+ if (!filePath) {
1678
+ return { success: false, error: "No file path provided" };
1679
+ }
1680
+ if (content === void 0) {
1681
+ return { success: false, error: "No content provided" };
1682
+ }
1683
+ const pathCheck = auditor.checkPath(filePath);
1684
+ if (!pathCheck.approved) {
1685
+ return {
1686
+ success: false,
1687
+ error: pathCheck.reason
1688
+ };
1689
+ }
1690
+ try {
1691
+ const cfg = await config.load();
1692
+ const fullPath = path5.resolve(cfg.workspaceRoot, filePath);
1693
+ try {
1694
+ await fs5.access(fullPath);
1695
+ return {
1696
+ success: false,
1697
+ error: `File already exists: ${filePath}. Use 'edit' tool to modify.`
1698
+ };
1699
+ } catch {
1700
+ }
1701
+ await fs5.mkdir(path5.dirname(fullPath), { recursive: true });
1702
+ await fs5.writeFile(fullPath, content, "utf-8");
1703
+ await context.trackModified(filePath);
1704
+ await undo.recordChange(filePath, "create", null, content);
1705
+ await auditLog.logFileOperation("write", filePath, true);
1706
+ await diffManager.saveDiff(filePath, "", content);
1707
+ return {
1708
+ success: true,
1709
+ output: `Created file: ${filePath} (${content.length} bytes)`
1710
+ };
1711
+ } catch (error) {
1712
+ await auditLog.logFileOperation("write", filePath, false, error.message);
1713
+ return {
1714
+ success: false,
1715
+ error: `Failed to write file: ${error.message}`
1716
+ };
1717
+ }
1718
+ }
1719
+ };
1720
+ var EditTool = {
1721
+ name: "edit",
1722
+ description: "Edit existing files using search and replace",
1723
+ inputSchema: {
1724
+ path: {
1725
+ type: "string",
1726
+ description: "Path to the file to edit"
1727
+ },
1728
+ search: {
1729
+ type: "string",
1730
+ description: "Text to search for (must match exactly)"
1731
+ },
1732
+ replace: {
1733
+ type: "string",
1734
+ description: "Text to replace with"
1735
+ }
1736
+ },
1737
+ requiredParams: ["path", "search", "replace"],
1738
+ async execute(args) {
1739
+ const filePath = args.path;
1740
+ const search = args.search;
1741
+ const replace = args.replace;
1742
+ if (!filePath) {
1743
+ return { success: false, error: "No file path provided" };
1744
+ }
1745
+ if (!search) {
1746
+ return { success: false, error: "No search string provided" };
1747
+ }
1748
+ if (replace === void 0) {
1749
+ return { success: false, error: "No replacement string provided" };
1750
+ }
1751
+ const fileCheck = await auditor.checkFileEdit(filePath);
1752
+ if (!fileCheck.approved) {
1753
+ return {
1754
+ success: false,
1755
+ error: fileCheck.reason
1756
+ };
1757
+ }
1758
+ try {
1759
+ const cfg = await config.load();
1760
+ const fullPath = path5.resolve(cfg.workspaceRoot, filePath);
1761
+ const original = await fs5.readFile(fullPath, "utf-8");
1762
+ if (!original.includes(search)) {
1763
+ return {
1764
+ success: false,
1765
+ error: `Search string not found in ${filePath}`
1766
+ };
1767
+ }
1768
+ const occurrences = original.split(search).length - 1;
1769
+ const modified = original.replaceAll(search, replace);
1770
+ const diffPreview = generateDiffPreview(search, replace);
1771
+ await fs5.writeFile(fullPath, modified, "utf-8");
1772
+ const originalLines = original.split("\n").length;
1773
+ const modifiedLines = modified.split("\n").length;
1774
+ const delta = modifiedLines - originalLines;
1775
+ await context.trackModified(filePath);
1776
+ await undo.recordChange(filePath, "edit", original, modified);
1777
+ await auditLog.logFileOperation("edit", filePath, true);
1778
+ await diffManager.saveDiff(filePath, original, modified);
1779
+ const occurrenceText = occurrences > 1 ? ` (${occurrences} occurrences)` : "";
1780
+ return {
1781
+ success: true,
1782
+ output: `Edited ${filePath}${occurrenceText}:
1783
+ ${diffPreview}
1784
+ Lines: ${originalLines} -> ${modifiedLines} (${delta >= 0 ? "+" : ""}${delta})`
1785
+ };
1786
+ } catch (error) {
1787
+ await auditLog.logFileOperation("edit", filePath, false, error.message);
1788
+ return {
1789
+ success: false,
1790
+ error: `Failed to edit file: ${error.message}`
1791
+ };
1792
+ }
1793
+ }
1794
+ };
1795
+ function generateDiffPreview(search, replace) {
1796
+ const searchLines = search.split("\n").slice(0, 5);
1797
+ const replaceLines = replace.split("\n").slice(0, 5);
1798
+ let preview = "";
1799
+ for (const line of searchLines) {
1800
+ preview += `- ${line}
1801
+ `;
1802
+ }
1803
+ if (search.split("\n").length > 5) {
1804
+ preview += `- ... (${search.split("\n").length - 5} more lines)
1805
+ `;
1806
+ }
1807
+ for (const line of replaceLines) {
1808
+ preview += `+ ${line}
1809
+ `;
1810
+ }
1811
+ if (replace.split("\n").length > 5) {
1812
+ preview += `+ ... (${replace.split("\n").length - 5} more lines)
1813
+ `;
1814
+ }
1815
+ return preview.trim();
1816
+ }
1817
+ var ListTool = {
1818
+ name: "list",
1819
+ description: "List files and directories in the workspace",
1820
+ inputSchema: {
1821
+ path: {
1822
+ type: "string",
1823
+ description: "Directory path to list (defaults to current directory)"
1824
+ }
1825
+ },
1826
+ requiredParams: [],
1827
+ async execute(args) {
1828
+ const dirPath = args.path || ".";
1829
+ const pathCheck = auditor.checkPath(dirPath);
1830
+ if (!pathCheck.approved) {
1831
+ return {
1832
+ success: false,
1833
+ error: pathCheck.reason
1834
+ };
1835
+ }
1836
+ try {
1837
+ const cfg = await config.load();
1838
+ const fullPath = path5.resolve(cfg.workspaceRoot, dirPath);
1839
+ const entries = await fs5.readdir(fullPath, { withFileTypes: true });
1840
+ const filtered = entries.filter(
1841
+ (entry) => !IGNORED_DIRS.includes(entry.name) && !entry.name.startsWith(".")
1842
+ );
1843
+ const formatted = filtered.map((entry) => {
1844
+ const prefix = entry.isDirectory() ? "[DIR]" : "[FILE]";
1845
+ return `${prefix} ${entry.name}`;
1846
+ }).join("\n");
1847
+ const hiddenCount = entries.length - filtered.length;
1848
+ const hiddenNote = hiddenCount > 0 ? `
1849
+
1850
+ (${hiddenCount} hidden: node_modules, .git, etc.)` : "";
1851
+ return {
1852
+ success: true,
1853
+ output: `Directory: ${dirPath}
1854
+ ${"=".repeat(60)}
1855
+ ${formatted}${hiddenNote}`
1856
+ };
1857
+ } catch (error) {
1858
+ return {
1859
+ success: false,
1860
+ error: `Failed to list directory: ${error.message}`
1861
+ };
1862
+ }
1863
+ }
1864
+ };
1865
+ var GrepTool = {
1866
+ name: "grep",
1867
+ description: "Search for patterns in files using regex",
1868
+ inputSchema: {
1869
+ pattern: {
1870
+ type: "string",
1871
+ description: "Regex pattern to search for"
1872
+ },
1873
+ path: {
1874
+ type: "string",
1875
+ description: "Directory to search in (defaults to current directory)"
1876
+ },
1877
+ limit: {
1878
+ type: "number",
1879
+ description: "Maximum number of results (default: 50)"
1880
+ }
1881
+ },
1882
+ requiredParams: ["pattern"],
1883
+ async execute(args) {
1884
+ const pattern = args.pattern;
1885
+ const searchPath = args.path || ".";
1886
+ const maxResults = args.limit || 50;
1887
+ if (!pattern) {
1888
+ return { success: false, error: "No search pattern provided" };
1889
+ }
1890
+ const pathCheck = auditor.checkPath(searchPath);
1891
+ if (!pathCheck.approved) {
1892
+ return {
1893
+ success: false,
1894
+ error: pathCheck.reason
1895
+ };
1896
+ }
1897
+ try {
1898
+ const cfg = await config.load();
1899
+ const fullPath = path5.resolve(cfg.workspaceRoot, searchPath);
1900
+ const results = [];
1901
+ await searchDirectory(fullPath, pattern, results, maxResults, 0, cfg.workspaceRoot);
1902
+ if (results.length === 0) {
1903
+ return {
1904
+ success: true,
1905
+ output: `No matches found for: ${pattern}`
1906
+ };
1907
+ }
1908
+ return {
1909
+ success: true,
1910
+ output: truncateOutput(`Found ${results.length} matches for "${pattern}":
1911
+ ${"=".repeat(60)}
1912
+ ${results.join("\n")}`)
1913
+ };
1914
+ } catch (error) {
1915
+ return {
1916
+ success: false,
1917
+ error: `Search failed: ${error.message}`
1918
+ };
1919
+ }
1920
+ }
1921
+ };
1922
+ async function searchDirectory(dir, pattern, results, maxResults, depth = 0, workspaceRoot = process.cwd()) {
1923
+ if (results.length >= maxResults || depth > 10) return;
1924
+ try {
1925
+ const entries = await fs5.readdir(dir, { withFileTypes: true });
1926
+ const regex = new RegExp(pattern, "gi");
1927
+ for (const entry of entries) {
1928
+ if (results.length >= maxResults) break;
1929
+ const fullPath = path5.join(dir, entry.name);
1930
+ const relativePath = path5.relative(workspaceRoot, fullPath);
1931
+ if (entry.name.startsWith(".") || IGNORED_DIRS.includes(entry.name)) {
1932
+ continue;
1933
+ }
1934
+ if (entry.isDirectory()) {
1935
+ await searchDirectory(fullPath, pattern, results, maxResults, depth + 1, workspaceRoot);
1936
+ } else if (entry.isFile()) {
1937
+ const ext = path5.extname(entry.name).toLowerCase();
1938
+ const textExtensions = [".ts", ".tsx", ".js", ".jsx", ".json", ".md", ".txt", ".yaml", ".yml", ".css", ".html", ".sh"];
1939
+ if (textExtensions.includes(ext) || ext === "") {
1940
+ try {
1941
+ const content = await fs5.readFile(fullPath, "utf-8");
1942
+ const lines = content.split("\n");
1943
+ for (let i = 0; i < lines.length && results.length < maxResults; i++) {
1944
+ if (regex.test(lines[i])) {
1945
+ results.push(`${relativePath}:${i + 1}: ${lines[i].trim().slice(0, 100)}`);
1946
+ }
1947
+ regex.lastIndex = 0;
1948
+ }
1949
+ } catch {
1950
+ }
1951
+ }
1952
+ }
1953
+ }
1954
+ } catch {
1955
+ }
1956
+ }
1957
+ var GlobTool = {
1958
+ name: "glob",
1959
+ description: "Find files matching a glob pattern (e.g., **/*.ts, src/**/*.tsx)",
1960
+ inputSchema: {
1961
+ pattern: {
1962
+ type: "string",
1963
+ description: "Glob pattern like **/*.ts or src/**/*.tsx"
1964
+ },
1965
+ path: {
1966
+ type: "string",
1967
+ description: "Base directory (defaults to current directory)"
1968
+ }
1969
+ },
1970
+ requiredParams: ["pattern"],
1971
+ async execute(args) {
1972
+ const pattern = args.pattern;
1973
+ const basePath = args.path || ".";
1974
+ if (!pattern) {
1975
+ return { success: false, error: "No pattern provided" };
1976
+ }
1977
+ try {
1978
+ const cfg = await config.load();
1979
+ const results = [];
1980
+ const fullBase = path5.resolve(cfg.workspaceRoot, basePath);
1981
+ await globSearch(fullBase, pattern, results, 100, 0, cfg.workspaceRoot);
1982
+ if (results.length === 0) {
1983
+ return {
1984
+ success: true,
1985
+ output: `No files matching: ${pattern}`
1986
+ };
1987
+ }
1988
+ return {
1989
+ success: true,
1990
+ output: truncateOutput(`Found ${results.length} files:
1991
+ ${results.join("\n")}`)
1992
+ };
1993
+ } catch (error) {
1994
+ return {
1995
+ success: false,
1996
+ error: `Glob failed: ${error.message}`
1997
+ };
1998
+ }
1999
+ }
2000
+ };
2001
+ async function globSearch(dir, pattern, results, maxResults, depth = 0, workspaceRoot = process.cwd()) {
2002
+ if (results.length >= maxResults || depth > 15) return;
2003
+ try {
2004
+ const entries = await fs5.readdir(dir, { withFileTypes: true });
2005
+ const regexPattern = pattern.replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, ".").replace(/{{GLOBSTAR}}/g, ".*");
2006
+ const regex = new RegExp(`^${regexPattern}$`);
2007
+ for (const entry of entries) {
2008
+ if (results.length >= maxResults) break;
2009
+ if (entry.name.startsWith(".") || IGNORED_DIRS.includes(entry.name)) {
2010
+ continue;
2011
+ }
2012
+ const fullPath = path5.join(dir, entry.name);
2013
+ const relativePath = path5.relative(workspaceRoot, fullPath);
2014
+ if (entry.isDirectory()) {
2015
+ if (pattern.includes("**") || pattern.includes("/")) {
2016
+ await globSearch(fullPath, pattern, results, maxResults, depth + 1, workspaceRoot);
2017
+ }
2018
+ } else if (entry.isFile()) {
2019
+ if (regex.test(relativePath) || regex.test(entry.name)) {
2020
+ results.push(relativePath);
2021
+ }
2022
+ }
2023
+ }
2024
+ } catch {
2025
+ }
2026
+ }
2027
+ var TaskTool = {
2028
+ name: "task",
2029
+ description: "Manage the project plan and todo list. Use this for tracking work items, not for scheduling recurring jobs. actions: create, add_step, complete_step, fail_step, complete_task",
2030
+ inputSchema: {
2031
+ action: {
2032
+ type: "string",
2033
+ description: "Action to perform: create, add_step, complete_step, fail_step, complete_task"
2034
+ },
2035
+ title: {
2036
+ type: "string",
2037
+ description: "Title for new task (required for create)"
2038
+ },
2039
+ step: {
2040
+ type: "string",
2041
+ description: "Step description (required for add_step)"
2042
+ },
2043
+ step_index: {
2044
+ type: "number",
2045
+ description: "Index of step to complete/fail (required for step actions)"
2046
+ }
2047
+ },
2048
+ requiredParams: ["action"],
2049
+ async execute(args) {
2050
+ const action = args.action;
2051
+ const title = args.title;
2052
+ const step = args.step;
2053
+ const stepIndex = args.step_index;
2054
+ if (!action) return { success: false, error: "No action provided" };
2055
+ try {
2056
+ switch (action) {
2057
+ case "create":
2058
+ if (!title) return { success: false, error: "Title required for create" };
2059
+ await tasks.create(title);
2060
+ return { success: true, output: `Created task: ${title}` };
2061
+ case "add_step":
2062
+ if (!step) return { success: false, error: "Step text required" };
2063
+ await tasks.addSubtask(step);
2064
+ return { success: true, output: `Added step: ${step}` };
2065
+ case "complete_step":
2066
+ if (stepIndex === void 0) return { success: false, error: "Step index required" };
2067
+ await tasks.completeSubtask(stepIndex);
2068
+ return { success: true, output: `Completed step ${stepIndex}` };
2069
+ case "fail_step":
2070
+ return { success: true, output: `Marked step ${stepIndex} as failed (not implemented in tracker yet)` };
2071
+ case "complete_task":
2072
+ await tasks.complete();
2073
+ return { success: true, output: "Marked task as complete" };
2074
+ default:
2075
+ return { success: false, error: `Unknown action: ${action}` };
2076
+ }
2077
+ } catch (error) {
2078
+ return { success: false, error: `Task action failed: ${error.message}` };
2079
+ }
2080
+ }
2081
+ };
2082
+ var WebFetchTool = {
2083
+ name: "web_fetch",
2084
+ description: "Fetch content from a URL (for documentation, APIs, etc.)",
2085
+ inputSchema: {
2086
+ url: {
2087
+ type: "string",
2088
+ description: "URL to fetch content from"
2089
+ }
2090
+ },
2091
+ requiredParams: ["url"],
2092
+ async execute(args) {
2093
+ const url = args.url;
2094
+ if (!url) {
2095
+ return { success: false, error: "No URL provided" };
2096
+ }
2097
+ try {
2098
+ new URL(url);
2099
+ } catch {
2100
+ return { success: false, error: "Invalid URL format" };
2101
+ }
2102
+ const blockedDomains = ["localhost", "127.0.0.1", "0.0.0.0", "169.254"];
2103
+ const urlObj = new URL(url);
2104
+ if (blockedDomains.some((d) => urlObj.hostname.includes(d))) {
2105
+ return {
2106
+ success: false,
2107
+ error: "Cannot fetch from local/private addresses"
2108
+ };
2109
+ }
2110
+ try {
2111
+ const controller = new AbortController();
2112
+ const timeoutId = setTimeout(() => controller.abort(), 1e4);
2113
+ const response = await fetch(url, {
2114
+ signal: controller.signal,
2115
+ headers: {
2116
+ "User-Agent": "Obsidian-Next/1.0 (AI Agent CLI)",
2117
+ "Accept": "text/html,application/json,text/plain,*/*"
2118
+ }
2119
+ });
2120
+ clearTimeout(timeoutId);
2121
+ if (!response.ok) {
2122
+ return {
2123
+ success: false,
2124
+ error: `HTTP ${response.status}: ${response.statusText}`
2125
+ };
2126
+ }
2127
+ const contentType = response.headers.get("content-type") || "";
2128
+ let content = await response.text();
2129
+ if (content.length > MAX_OUTPUT_LENGTH) {
2130
+ content = content.slice(0, MAX_OUTPUT_LENGTH) + `
2131
+
2132
+ ... [TRUNCATED: ${content.length - MAX_OUTPUT_LENGTH} more characters]`;
2133
+ }
2134
+ if (contentType.includes("text/html")) {
2135
+ content = content.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
2136
+ }
2137
+ return {
2138
+ success: true,
2139
+ output: truncateOutput(`URL: ${url}
2140
+ Content-Type: ${contentType}
2141
+ ${"=".repeat(60)}
2142
+ ${content}`)
2143
+ };
2144
+ } catch (error) {
2145
+ if (error.name === "AbortError") {
2146
+ return { success: false, error: "Request timed out after 10 seconds" };
2147
+ }
2148
+ return {
2149
+ success: false,
2150
+ error: `Fetch failed: ${error.message}`
2151
+ };
2152
+ }
2153
+ }
2154
+ };
2155
+ var MCPManagementTool = {
2156
+ name: "mcp_manage",
2157
+ description: 'Manage MCP servers. actions: add, remove, install. Use "install" to easily add certified tools like "filesystem", "git", "research", or "context7".',
2158
+ inputSchema: {
2159
+ action: {
2160
+ type: "string",
2161
+ description: "Action to perform: add, remove, install"
2162
+ },
2163
+ name: {
2164
+ type: "string",
2165
+ description: 'Name of the server (e.g. "filesystem", "research", "context7")'
2166
+ },
2167
+ command: {
2168
+ type: "string",
2169
+ description: "Command to execute (add only)"
2170
+ },
2171
+ args: {
2172
+ type: "string",
2173
+ description: "Args for command (add only)"
2174
+ }
2175
+ },
2176
+ requiredParams: ["action", "name"],
2177
+ async execute(args) {
2178
+ const action = args.action;
2179
+ const name = args.name;
2180
+ if (action === "install") {
2181
+ const def = getRegistryDefinition(name);
2182
+ if (!def) {
2183
+ const available = listRegistry().map((r) => r.name).join(", ");
2184
+ return { success: false, error: `Unknown registry item '${name}'. Available: ${available}` };
2185
+ }
2186
+ const existing = mcp.getStatus().find((s) => s.name === name);
2187
+ if (existing && existing.connected) {
2188
+ return { success: true, output: `MCP server '${name}' is already installed and connected.` };
2189
+ }
2190
+ try {
2191
+ await mcp.addServer(name, {
2192
+ command: def.command,
2193
+ args: def.args,
2194
+ autoConnect: false,
2195
+ env: def.env
2196
+ });
2197
+ return { success: true, output: `Successfully installed and connected to certified MCP server '${name}' (${def.description})` };
2198
+ } catch (e) {
2199
+ return { success: false, error: `Failed to install server: ${e.message}` };
2200
+ }
2201
+ }
2202
+ if (action === "add") {
2203
+ if (!args.command) return { success: false, error: 'Command is required for "add" action' };
2204
+ const command = args.command;
2205
+ const commandArgs = (args.args || "").split(" ").filter((a) => a.length > 0);
2206
+ try {
2207
+ await mcp.addServer(name, {
2208
+ command,
2209
+ args: commandArgs,
2210
+ autoConnect: false
2211
+ });
2212
+ return { success: true, output: `Successfully added and connected to MCP server '${name}'` };
2213
+ } catch (e) {
2214
+ return { success: false, error: `Failed to add server: ${e.message}` };
2215
+ }
2216
+ }
2217
+ if (action === "remove") {
2218
+ try {
2219
+ await mcp.removeServer(name);
2220
+ return { success: true, output: `Successfully removed MCP server '${name}'` };
2221
+ } catch (e) {
2222
+ return { success: false, error: `Failed to remove server: ${e.message}` };
2223
+ }
2224
+ }
2225
+ if (action === "connect") {
2226
+ try {
2227
+ const status = mcp.getStatus().find((s) => s.name === name);
2228
+ if (!status) return { success: false, error: `Server '${name}' not found in config` };
2229
+ if (status.connected) return { success: true, output: `Server '${name}' is already connected` };
2230
+ await mcp.connect(name, status.config);
2231
+ return { success: true, output: `Successfully connected to MCP server '${name}'` };
2232
+ } catch (e) {
2233
+ return { success: false, error: `Failed to connect: ${e.message}` };
2234
+ }
2235
+ }
2236
+ if (action === "disconnect") {
2237
+ try {
2238
+ await mcp.disconnect(name);
2239
+ return { success: true, output: `Successfully disconnected MCP server '${name}'` };
2240
+ } catch (e) {
2241
+ return { success: false, error: `Failed to disconnect: ${e.message}` };
2242
+ }
2243
+ }
2244
+ if (action === "status") {
2245
+ const status = mcp.getStatus().find((s) => s.name === name);
2246
+ if (!status) return { success: false, error: `Server '${name}' not found` };
2247
+ return {
2248
+ success: true,
2249
+ output: `Server: ${name}
2250
+ Status: ${status.connected ? "Connected" : "Disconnected"}
2251
+ Tools: ${status.capabilities ? "Available" : "N/A"}`
2252
+ };
2253
+ }
2254
+ return { success: false, error: `Unknown action: ${action}` };
2255
+ }
2256
+ };
2257
+ var MemoryTool = {
2258
+ name: "memory",
2259
+ description: "Store and recall long-term memories: user preferences, project facts, and learned patterns. Use this to remember user information across sessions.",
2260
+ inputSchema: {
2261
+ action: { type: "string", description: "Action: store, recall, search, list, forget" },
2262
+ type: { type: "string", description: "Memory type: user_preference, project_fact, decision_log, learned_pattern (for store)" },
2263
+ key: { type: "string", description: "Unique key for the memory (for store, recall, forget)" },
2264
+ content: { type: "string", description: "Content to store (for store action)" },
2265
+ query: { type: "string", description: "Search query (for search action)" }
2266
+ },
2267
+ requiredParams: ["action"],
2268
+ async execute(args) {
2269
+ const { memory } = await import("./memory-MV3S7GFY.js");
2270
+ const { action, type, key, content, query } = args;
2271
+ await memory.init();
2272
+ if (action === "store") {
2273
+ if (!key || !content) {
2274
+ return { success: false, error: "store requires key and content" };
2275
+ }
2276
+ const memoType = type || "user_preference";
2277
+ const success = await memory.store(memoType, key, content);
2278
+ if (success) {
2279
+ return { success: true, output: `Stored memory: ${key}` };
2280
+ }
2281
+ return { success: false, error: "Failed to store memory" };
2282
+ }
2283
+ if (action === "recall") {
2284
+ if (!key) {
2285
+ return { success: false, error: "recall requires key" };
2286
+ }
2287
+ const memo = await memory.recall(key);
2288
+ if (memo) {
2289
+ return { success: true, output: `[${memo.type}] ${memo.key}: ${memo.content}` };
2290
+ }
2291
+ return { success: true, output: `No memory found for key: ${key}` };
2292
+ }
2293
+ if (action === "search") {
2294
+ if (!query) {
2295
+ return { success: false, error: "search requires query" };
2296
+ }
2297
+ const memos = await memory.search(query, type);
2298
+ if (memos.length === 0) {
2299
+ return { success: true, output: "No memories found matching query" };
2300
+ }
2301
+ const lines = memos.map((m) => `- [${m.type}] ${m.key}: ${m.content}`);
2302
+ return { success: true, output: `Found ${memos.length} memories:
2303
+ ${lines.join("\n")}` };
2304
+ }
2305
+ if (action === "list") {
2306
+ const memoType = type;
2307
+ let memos;
2308
+ if (memoType) {
2309
+ memos = await memory.getByType(memoType);
2310
+ if (memos.length === 0) {
2311
+ return { success: true, output: `No ${memoType} memories found` };
2312
+ }
2313
+ const lines = memos.map((m) => `- ${m.key}: ${m.content}`);
2314
+ return { success: true, output: `${memoType} memories:
2315
+ ${lines.join("\n")}` };
2316
+ } else {
2317
+ const stats = await memory.getStats();
2318
+ const allMemos = [];
2319
+ for (const t of Object.keys(stats.byType)) {
2320
+ const typeMemos = await memory.getByType(t);
2321
+ if (typeMemos.length > 0) {
2322
+ allMemos.push(`--- ${t} ---`);
2323
+ allMemos.push(...typeMemos.map((m) => `- ${m.key}: ${m.content}`));
2324
+ }
2325
+ }
2326
+ if (allMemos.length === 0) return { success: true, output: "No memories found in any category." };
2327
+ return { success: true, output: `Current Memory Bank:
2328
+ ${allMemos.join("\n")}` };
2329
+ }
2330
+ }
2331
+ if (action === "forget") {
2332
+ if (!key) {
2333
+ return { success: false, error: "forget requires key" };
2334
+ }
2335
+ const success = await memory.forget(key);
2336
+ if (success) {
2337
+ return { success: true, output: `Forgot memory: ${key}` };
2338
+ }
2339
+ return { success: false, error: "Failed to forget memory" };
2340
+ }
2341
+ return { success: false, error: `Unknown action: ${action}. Valid: store, recall, search, list, forget` };
2342
+ }
2343
+ };
2344
+ var UnscheduleTool = {
2345
+ name: "unschedule_task",
2346
+ description: "Unschedule a previously scheduled background cron job. Requires the task ID.",
2347
+ inputSchema: {
2348
+ taskId: {
2349
+ type: "string",
2350
+ description: "The ID of the task to unschedule (obtained from list_scheduled_tasks)."
2351
+ }
2352
+ },
2353
+ requiredParams: ["taskId"],
2354
+ async execute(args) {
2355
+ const taskId = args.taskId;
2356
+ if (!taskId) {
2357
+ return { success: false, error: "Task ID is required to unschedule a task." };
2358
+ }
2359
+ try {
2360
+ const success = await scheduler.removeTask(taskId);
2361
+ if (success) {
2362
+ return { success: true, output: `Successfully unscheduled task: ${taskId}` };
2363
+ } else {
2364
+ return { success: false, output: `Failed to unschedule task: ${taskId}. Task not found or already inactive.` };
2365
+ }
2366
+ } catch (error) {
2367
+ return { success: false, error: `Failed to unschedule task: ${error.message}` };
2368
+ }
2369
+ }
2370
+ };
2371
+ var ComputerUseTool = {
2372
+ name: "computer",
2373
+ description: `Desktop interaction. PREFER BASH for URLs/apps (e.g., bash 'open "https://youtube.com"').
2374
+
2375
+ SMART ACTIONS (use first):
2376
+ - find_and_click: Click by label ("Submit", "Play") - no coordinates needed
2377
+ - get_ui_context: List buttons/fields in current window
2378
+ - get_buttons: List all button labels
2379
+ - activate_app: Bring app to front
2380
+
2381
+ COORDINATE ACTIONS (use when smart actions fail):
2382
+ - screenshot: Capture screen
2383
+ - left_click: Click at [x,y]
2384
+ - type: Type text
2385
+ - key: Press key (cmd+l, Return, etc.)
2386
+ - scroll: Scroll at position`,
2387
+ inputSchema: {
2388
+ action: {
2389
+ type: "string",
2390
+ description: "SMART (prefer): find_and_click, get_ui_context, get_buttons, activate_app, get_focused_app. COORDINATE (fallback): screenshot, left_click, type, key, scroll, mouse_move, double_click, right_click, left_click_drag, wait, zoom."
2391
+ },
2392
+ coordinate: {
2393
+ type: "array",
2394
+ items: { type: "number" },
2395
+ description: "[x, y] pixel coordinates for coordinate-based actions."
2396
+ },
2397
+ text: {
2398
+ type: "string",
2399
+ description: "Text to type, or modifier key for clicks (shift, control, alt, command)."
2400
+ },
2401
+ key: {
2402
+ type: "string",
2403
+ description: "Key to press (e.g., enter, escape, ctrl+c)."
2404
+ },
2405
+ label: {
2406
+ type: "string",
2407
+ description: 'UI element label for find_and_click (e.g., "Submit", "Cancel", "OK").'
2408
+ },
2409
+ app_name: {
2410
+ type: "string",
2411
+ description: "Application name for activate_app or to scope element search."
2412
+ },
2413
+ scroll_direction: {
2414
+ type: "string",
2415
+ description: "up, down, left, or right"
2416
+ },
2417
+ scroll_amount: {
2418
+ type: "number",
2419
+ description: "Amount to scroll (default: 3)"
2420
+ },
2421
+ start_coordinate: {
2422
+ type: "array",
2423
+ items: { type: "number" },
2424
+ description: "[x, y] start coordinates for drag"
2425
+ },
2426
+ end_coordinate: {
2427
+ type: "array",
2428
+ items: { type: "number" },
2429
+ description: "[x, y] end coordinates for drag"
2430
+ },
2431
+ duration: {
2432
+ type: "number",
2433
+ description: "Duration in milliseconds (for wait, hold_key)"
2434
+ },
2435
+ region: {
2436
+ type: "array",
2437
+ items: { type: "number" },
2438
+ description: "[x1, y1, x2, y2] region for zoom"
2439
+ }
2440
+ },
2441
+ requiredParams: ["action"],
2442
+ async execute(args) {
2443
+ const actionType = args.action;
2444
+ if (!actionType) {
2445
+ return { success: false, error: "No computer action provided. Use: screenshot, left_click, type, key, mouse_move, scroll, etc." };
2446
+ }
2447
+ const validateCoord = (coord, name = "coordinate") => {
2448
+ if (!coord || !Array.isArray(coord) || coord.length < 2) {
2449
+ return null;
2450
+ }
2451
+ const x = Number(coord[0]);
2452
+ const y = Number(coord[1]);
2453
+ if (isNaN(x) || isNaN(y)) return null;
2454
+ return [x, y];
2455
+ };
2456
+ const scaleToNative = (coord) => {
2457
+ if (lastScreenshotScale >= 1) return coord;
2458
+ return [
2459
+ Math.round(coord[0] / lastScreenshotScale),
2460
+ Math.round(coord[1] / lastScreenshotScale)
2461
+ ];
2462
+ };
2463
+ try {
2464
+ let output;
2465
+ switch (actionType) {
2466
+ case "screenshot":
2467
+ const screenshotResult = await takeScreenshotForAPI(false);
2468
+ lastScreenshotScale = screenshotResult.scale;
2469
+ bus.emitAgent({
2470
+ type: "computer_scale_update",
2471
+ scale: screenshotResult.scale,
2472
+ scaledWidth: screenshotResult.width,
2473
+ scaledHeight: screenshotResult.height,
2474
+ nativeWidth: Math.round(screenshotResult.width / screenshotResult.scale),
2475
+ nativeHeight: Math.round(screenshotResult.height / screenshotResult.scale)
2476
+ });
2477
+ return {
2478
+ success: true,
2479
+ output: `Screenshot captured (${screenshotResult.width}x${screenshotResult.height}, scale: ${screenshotResult.scale.toFixed(2)}). Native: ${Math.round(screenshotResult.width / screenshotResult.scale)}x${Math.round(screenshotResult.height / screenshotResult.scale)}`,
2480
+ content: [{ type: "image", data: screenshotResult.base64, mimeType: "image/png" }]
2481
+ };
2482
+ case "left_click": {
2483
+ const coord = validateCoord(args.coordinate);
2484
+ if (!coord) return { success: false, error: "left_click requires coordinate: [x, y] as numbers" };
2485
+ const [nativeX, nativeY] = scaleToNative(coord);
2486
+ await computer.leftClick(nativeX, nativeY, args.text);
2487
+ output = `Clicked at screenshot (${coord[0]}, ${coord[1]}) -> native (${nativeX}, ${nativeY}). If this missed the target, re-examine the screenshot and identify the EXACT center of the element you want to click.`;
2488
+ break;
2489
+ }
2490
+ case "type":
2491
+ if (!args.text && args.text !== "") return { success: false, error: "type requires text parameter" };
2492
+ await computer.typeText(args.text);
2493
+ output = `Typed: "${args.text.substring(0, 30)}${args.text.length > 30 ? "..." : ""}"`;
2494
+ break;
2495
+ case "key":
2496
+ const keyToPress = args.key || args.text;
2497
+ if (!keyToPress) return { success: false, error: 'key requires key parameter (e.g., "enter", "escape", "ctrl+c")' };
2498
+ await computer.pressKey(keyToPress);
2499
+ output = `Pressed key: ${keyToPress}`;
2500
+ break;
2501
+ case "mouse_move": {
2502
+ const coord = validateCoord(args.coordinate);
2503
+ if (!coord) return { success: false, error: "mouse_move requires coordinate: [x, y]" };
2504
+ const [nativeX, nativeY] = scaleToNative(coord);
2505
+ await computer.mouseMove(nativeX, nativeY);
2506
+ output = `Mouse moved to screenshot (${coord[0]}, ${coord[1]}) -> native (${nativeX}, ${nativeY}).`;
2507
+ break;
2508
+ }
2509
+ case "scroll": {
2510
+ const coord = validateCoord(args.coordinate);
2511
+ if (!coord) return { success: false, error: "scroll requires coordinate: [x, y]" };
2512
+ if (!args.scroll_direction) return { success: false, error: "scroll requires scroll_direction: up|down|left|right" };
2513
+ const amount = args.scroll_amount || 3;
2514
+ const [nativeX, nativeY] = scaleToNative(coord);
2515
+ await computer.scroll(nativeX, nativeY, args.scroll_direction, amount, args.text);
2516
+ output = `Scrolled ${args.scroll_direction} by ${amount} at screenshot (${coord[0]}, ${coord[1]}) -> native (${nativeX}, ${nativeY}).`;
2517
+ break;
2518
+ }
2519
+ case "left_click_drag": {
2520
+ const startCoord = validateCoord(args.start_coordinate, "start_coordinate");
2521
+ const endCoord = validateCoord(args.end_coordinate, "end_coordinate");
2522
+ if (!startCoord) return { success: false, error: "left_click_drag requires start_coordinate: [x, y]" };
2523
+ if (!endCoord) return { success: false, error: "left_click_drag requires end_coordinate: [x, y]" };
2524
+ const [nativeStartX, nativeStartY] = scaleToNative(startCoord);
2525
+ const [nativeEndX, nativeEndY] = scaleToNative(endCoord);
2526
+ await computer.leftClickDrag(nativeStartX, nativeStartY, nativeEndX, nativeEndY);
2527
+ output = `Dragged from screenshot (${startCoord[0]}, ${startCoord[1]}) -> native (${nativeStartX}, ${nativeStartY}) to screenshot (${endCoord[0]}, ${endCoord[1]}) -> native (${nativeEndX}, ${nativeEndY}).`;
2528
+ break;
2529
+ }
2530
+ case "right_click": {
2531
+ const coord = validateCoord(args.coordinate);
2532
+ if (!coord) return { success: false, error: "right_click requires coordinate: [x, y]" };
2533
+ const [nativeX, nativeY] = scaleToNative(coord);
2534
+ await computer.rightClick(nativeX, nativeY, args.text);
2535
+ output = `Right click at screenshot (${coord[0]}, ${coord[1]}) -> native (${nativeX}, ${nativeY}).`;
2536
+ break;
2537
+ }
2538
+ case "middle_click": {
2539
+ const coord = validateCoord(args.coordinate);
2540
+ if (!coord) return { success: false, error: "middle_click requires coordinate: [x, y]" };
2541
+ const [nativeX, nativeY] = scaleToNative(coord);
2542
+ await computer.middleClick(nativeX, nativeY, args.text);
2543
+ output = `Middle click at screenshot (${coord[0]}, ${coord[1]}) -> native (${nativeX}, ${nativeY}).`;
2544
+ break;
2545
+ }
2546
+ case "double_click": {
2547
+ const coord = validateCoord(args.coordinate);
2548
+ if (!coord) return { success: false, error: "double_click requires coordinate: [x, y]" };
2549
+ const [nativeX, nativeY] = scaleToNative(coord);
2550
+ await computer.doubleClick(nativeX, nativeY, args.text);
2551
+ output = `Double click at screenshot (${coord[0]}, ${coord[1]}) -> native (${nativeX}, ${nativeY}).`;
2552
+ break;
2553
+ }
2554
+ case "triple_click": {
2555
+ const coord = validateCoord(args.coordinate);
2556
+ if (!coord) return { success: false, error: "triple_click requires coordinate: [x, y]" };
2557
+ const [nativeX, nativeY] = scaleToNative(coord);
2558
+ await computer.tripleClick(nativeX, nativeY, args.text);
2559
+ output = `Triple click at screenshot (${coord[0]}, ${coord[1]}) -> native (${nativeX}, ${nativeY}).`;
2560
+ break;
2561
+ }
2562
+ case "left_mouse_down": {
2563
+ const coord = validateCoord(args.coordinate);
2564
+ if (!coord) return { success: false, error: "left_mouse_down requires coordinate: [x, y]" };
2565
+ const [nativeX, nativeY] = scaleToNative(coord);
2566
+ await computer.leftMouseDown(nativeX, nativeY);
2567
+ output = `Mouse down at screenshot (${coord[0]}, ${coord[1]}) -> native (${nativeX}, ${nativeY}).`;
2568
+ break;
2569
+ }
2570
+ case "left_mouse_up": {
2571
+ const coord = validateCoord(args.coordinate);
2572
+ if (!coord) return { success: false, error: "left_mouse_up requires coordinate: [x, y]" };
2573
+ const [nativeX, nativeY] = scaleToNative(coord);
2574
+ await computer.leftMouseUp(nativeX, nativeY);
2575
+ output = `Mouse up at screenshot (${coord[0]}, ${coord[1]}) -> native (${nativeX}, ${nativeY}).`;
2576
+ break;
2577
+ }
2578
+ case "hold_key": {
2579
+ const keyToHold = args.key || args.text;
2580
+ if (!keyToHold) return { success: false, error: "hold_key requires key parameter" };
2581
+ const duration = args.duration || 1;
2582
+ await computer.holdKey(keyToHold, duration);
2583
+ output = `Held ${keyToHold} for ${duration}s.`;
2584
+ break;
2585
+ }
2586
+ case "wait": {
2587
+ const duration = args.duration || 1e3;
2588
+ await computer.wait(duration);
2589
+ output = `Waited ${duration}ms.`;
2590
+ break;
2591
+ }
2592
+ case "zoom": {
2593
+ if (!args.region || !Array.isArray(args.region) || args.region.length < 4) {
2594
+ return { success: false, error: "zoom requires region: [x1, y1, x2, y2]" };
2595
+ }
2596
+ const [x1, y1, x2, y2] = args.region;
2597
+ const [nativeX1, nativeY1] = scaleToNative([x1, y1]);
2598
+ const [nativeX2, nativeY2] = scaleToNative([x2, y2]);
2599
+ const zoomResult = await computer.zoom(nativeX1, nativeY1, nativeX2, nativeY2);
2600
+ return {
2601
+ success: true,
2602
+ output: `Zoomed region screenshot (${x1}, ${y1}) to (${x2}, ${y2}) -> native (${nativeX1}, ${nativeY1}) to (${nativeX2}, ${nativeY2}).`,
2603
+ content: [{ type: "image", data: zoomResult, mimeType: "image/png" }]
2604
+ };
2605
+ }
2606
+ case "get_dimensions":
2607
+ const dims = await computer.getDisplayDimensions();
2608
+ return { success: true, output: `Display: ${dims.width}x${dims.height}. Use these dimensions for coordinate calculations.` };
2609
+ case "batch":
2610
+ if (!args.commands || !Array.isArray(args.commands)) {
2611
+ return { success: false, error: "batch requires commands: string[]" };
2612
+ }
2613
+ await computer.executeBatch(args.commands);
2614
+ output = `Batch executed ${args.commands.length} commands.`;
2615
+ break;
2616
+ // ==================== SMART ACCESSIBILITY-BASED ACTIONS ====================
2617
+ case "find_and_click": {
2618
+ if (!args.label) return { success: false, error: 'find_and_click requires label parameter (e.g., "Submit", "OK")' };
2619
+ const clicked = await clickElementByLabel(args.label, args.app_name);
2620
+ if (clicked) {
2621
+ output = `Clicked element labeled "${args.label}" via accessibility API.`;
2622
+ break;
2623
+ }
2624
+ const coords = await findClickableByLabel(args.label);
2625
+ if (coords) {
2626
+ await computer.leftClick(coords[0], coords[1]);
2627
+ output = `Clicked "${args.label}" at (${coords[0]}, ${coords[1]}).`;
2628
+ break;
2629
+ }
2630
+ return { success: false, error: `Could not find element labeled "${args.label}". Try using screenshot + coordinate-based click.` };
2631
+ }
2632
+ case "get_ui_context": {
2633
+ const uiContext = await getUIContext();
2634
+ return {
2635
+ success: true,
2636
+ output: `Current UI State:
2637
+ ${uiContext}
2638
+
2639
+ Use find_and_click with a button label, or screenshot + coordinates for unlisted elements.`
2640
+ };
2641
+ }
2642
+ case "get_buttons": {
2643
+ const buttons = await getButtons(args.app_name);
2644
+ if (buttons.length === 0) {
2645
+ return { success: true, output: "No buttons found in current window. Try screenshot to see the UI." };
2646
+ }
2647
+ return {
2648
+ success: true,
2649
+ output: `Available buttons (${buttons.length}):
2650
+ ${buttons.map((b) => ` - "${b}"`).join("\n")}
2651
+
2652
+ Use find_and_click with any of these labels.`
2653
+ };
2654
+ }
2655
+ case "activate_app": {
2656
+ if (!args.app_name) return { success: false, error: "activate_app requires app_name parameter" };
2657
+ const activated = await activateApp(args.app_name);
2658
+ if (activated) {
2659
+ output = `Activated "${args.app_name}".`;
2660
+ await new Promise((r) => setTimeout(r, 500));
2661
+ const verifyScreenshot = await takeScreenshotForAPI(false);
2662
+ return {
2663
+ success: true,
2664
+ output: `${output} Window now in focus.`,
2665
+ content: [{ type: "image", data: verifyScreenshot.base64, mimeType: "image/png" }]
2666
+ };
2667
+ }
2668
+ return { success: false, error: `Could not activate "${args.app_name}". Check if the app is running.` };
2669
+ }
2670
+ case "get_focused_app": {
2671
+ const app = await getFocusedApp();
2672
+ return { success: true, output: `Currently focused: ${app}` };
2673
+ }
2674
+ default:
2675
+ return { success: false, error: `Unknown action: ${actionType}. SMART: find_and_click, get_ui_context, get_buttons, activate_app. COORDINATE: screenshot, left_click, type, key, scroll, etc.` };
2676
+ }
2677
+ const interactionActions = ["left_click", "right_click", "double_click", "triple_click", "type", "key", "left_click_drag", "batch", "find_and_click"];
2678
+ if (interactionActions.includes(actionType)) {
2679
+ await new Promise((resolve) => setTimeout(resolve, 500));
2680
+ const verifyScreenshot = await takeScreenshotForAPI(true);
2681
+ return {
2682
+ success: true,
2683
+ output: `${output || "Action executed."} Verification captured (${verifyScreenshot.width}x${verifyScreenshot.height}).`,
2684
+ content: [{ type: "image", data: verifyScreenshot.base64, mimeType: "image/png" }]
2685
+ };
2686
+ }
2687
+ return { success: true, output: output || "Computer action executed successfully." };
2688
+ } catch (error) {
2689
+ return { success: false, error: `Computer action failed: ${error.message}` };
2690
+ }
2691
+ }
2692
+ };
2693
+ var CreateSkillTool = {
2694
+ name: "create_skill",
2695
+ description: "Create a new autonomous skill (tool) for the agent. This tool writes the implementation, runs tests, and registers it dynamically. The code MUST be a valid Node.js module that exports default a Tool object.",
2696
+ inputSchema: {
2697
+ name: {
2698
+ type: "string",
2699
+ description: 'Name of the tool (e.g., "jira_issue_create")'
2700
+ },
2701
+ description: {
2702
+ type: "string",
2703
+ description: "What the tool does"
2704
+ },
2705
+ code: {
2706
+ type: "string",
2707
+ description: "Node.js code for the tool. Must export default a Tool object."
2708
+ }
2709
+ },
2710
+ requiredParams: ["name", "description", "code"],
2711
+ async execute(args) {
2712
+ const name = args.name;
2713
+ const code = args.code;
2714
+ const skillsDir = path5.join(os4.homedir(), ".obsidian-next", "skills");
2715
+ const skillPath = path5.join(skillsDir, `${name}.js`);
2716
+ try {
2717
+ if (!fsSync.existsSync(skillsDir)) {
2718
+ fsSync.mkdirSync(skillsDir, { recursive: true });
2719
+ }
2720
+ await fs5.writeFile(skillPath, code, "utf-8");
2721
+ const module = await import(`file://${skillPath}?t=${Date.now()}`);
2722
+ if (module.default && module.default.name) {
2723
+ tools.register(module.default);
2724
+ return {
2725
+ success: true,
2726
+ output: `Skill '${name}' created and registered successfully. It is now available for use.`
2727
+ };
2728
+ }
2729
+ return { success: false, error: "Skill code must export default a Tool object." };
2730
+ } catch (error) {
2731
+ return {
2732
+ success: false,
2733
+ error: `Failed to create skill: ${error.message}`
2734
+ };
2735
+ }
2736
+ }
2737
+ };
2738
+ var ToolRegistry = class {
2739
+ tools = /* @__PURE__ */ new Map();
2740
+ skillsDir = path5.join(os4.homedir(), ".obsidian-next", "skills");
2741
+ constructor() {
2742
+ this.register(BashTool);
2743
+ this.register(ReadTool);
2744
+ this.register(WriteTool);
2745
+ this.register(EditTool);
2746
+ this.register(ListTool);
2747
+ this.register(GrepTool);
2748
+ this.register(GlobTool);
2749
+ this.register(TaskTool);
2750
+ this.register(WebFetchTool);
2751
+ this.register(MCPManagementTool);
2752
+ this.register(ScheduleTool);
2753
+ this.register(ListScheduledTasksTool);
2754
+ this.register(UnscheduleTool);
2755
+ this.register(MemoryTool);
2756
+ this.register(ComputerUseTool);
2757
+ this.register(CreateSkillTool);
2758
+ }
2759
+ async init() {
2760
+ await this.loadSkills();
2761
+ }
2762
+ async loadSkills() {
2763
+ if (!fsSync.existsSync(this.skillsDir)) {
2764
+ fsSync.mkdirSync(this.skillsDir, { recursive: true });
2765
+ }
2766
+ try {
2767
+ const files = await fs5.readdir(this.skillsDir);
2768
+ for (const file of files) {
2769
+ if (file.endsWith(".js")) {
2770
+ try {
2771
+ const skillPath = path5.join(this.skillsDir, file);
2772
+ const module = await import(`file://${skillPath}`);
2773
+ if (module.default && module.default.name) {
2774
+ this.register(module.default);
2775
+ }
2776
+ } catch (e) {
2777
+ console.error(`Failed to load skill ${file}:`, e);
2778
+ }
2779
+ }
2780
+ }
2781
+ } catch (e) {
2782
+ console.error("Failed to read skills directory:", e);
2783
+ }
2784
+ }
2785
+ register(tool) {
2786
+ this.tools.set(tool.name, tool);
2787
+ }
2788
+ has(name) {
2789
+ return this.tools.has(name);
2790
+ }
2791
+ get(name) {
2792
+ return this.tools.get(name);
2793
+ }
2794
+ async list() {
2795
+ const staticTools = Array.from(this.tools.values());
2796
+ try {
2797
+ const dynamicTools = await mcp.listTools();
2798
+ const mcpAdapters = dynamicTools.map((dt) => ({
2799
+ name: `${dt.server}_${dt.name}`,
2800
+ // Namespace: server_toolname
2801
+ description: `[MCP: ${dt.server}] ${dt.description || ""}`,
2802
+ inputSchema: dt.inputSchema?.properties || {},
2803
+ requiredParams: dt.inputSchema?.required || [],
2804
+ execute: async (args) => {
2805
+ const result = await mcp.callTool(dt.server, dt.name, args);
2806
+ if (result.isError) {
2807
+ return { success: false, error: "MCP Tool Error" };
2808
+ }
2809
+ const output = result.content.filter((c) => c.text).map((c) => c.text).join("\n");
2810
+ return { success: !result.isError, output, content: result.content };
2811
+ }
2812
+ }));
2813
+ return [...staticTools, ...mcpAdapters];
2814
+ } catch (error) {
2815
+ console.error("Failed to list MCP tools:", error);
2816
+ return staticTools;
2817
+ }
2818
+ }
2819
+ async execute(name, args) {
2820
+ let tool = this.tools.get(name);
2821
+ if (!tool) {
2822
+ const mcpTools = await this.list();
2823
+ tool = mcpTools.find((t) => t.name === name);
2824
+ }
2825
+ if (!tool) {
2826
+ return {
2827
+ success: false,
2828
+ error: `Unknown tool: ${name}. Available: ${(await this.list()).map((t) => t.name).join(", ")}`
2829
+ };
2830
+ }
2831
+ bus.emitAgent({
2832
+ type: "tool_start",
2833
+ tool: name,
2834
+ args: JSON.stringify(args, null, 2)
2835
+ });
2836
+ const result = await tool.execute(args);
2837
+ const rawOutput = result.success ? result.output || "Success" : result.error || "Failed";
2838
+ const redacted = redactor.redactToolOutput(name, rawOutput);
2839
+ bus.emitAgent({
2840
+ type: "tool_result",
2841
+ tool: name,
2842
+ output: redacted.text,
2843
+ isError: !result.success
2844
+ });
2845
+ return result;
2846
+ }
2847
+ };
2848
+ var ScheduleTool = {
2849
+ name: "schedule_task",
2850
+ description: 'Schedule a recurring background cron job. Use this for requests like "every hour", "at 9am", or "check every X".',
2851
+ inputSchema: {
2852
+ cron: {
2853
+ type: "string",
2854
+ description: 'Cron expression (e.g. "* * * * *")'
2855
+ },
2856
+ ability: {
2857
+ type: "string",
2858
+ description: 'Name of the ability to execute. Available: "system:bash", "system:echo", "system:notify" (sound + alert), "system:summary", "system:heartbeat"'
2859
+ },
2860
+ params: {
2861
+ type: "string",
2862
+ description: 'JSON string of parameters. For system:notify, use {"title": "...", "message": "..."}. For system:bash, use {"command": "..."}'
2863
+ }
2864
+ },
2865
+ requiredParams: ["cron", "ability"],
2866
+ async execute(args) {
2867
+ const cron = args.cron;
2868
+ const ability = args.ability;
2869
+ const paramsStr = args.params || "{}";
2870
+ if (!cron || !ability) {
2871
+ return { success: false, error: "Cron expression and ability name are required" };
2872
+ }
2873
+ let params = {};
2874
+ try {
2875
+ params = JSON.parse(paramsStr);
2876
+ } catch {
2877
+ return { success: false, error: "Invalid JSON parameters" };
2878
+ }
2879
+ try {
2880
+ const task = await scheduler.scheduleTask(cron, ability, params);
2881
+ return {
2882
+ success: true,
2883
+ output: `Scheduled task ${task.id}: ${ability} @ "${cron}"`
2884
+ };
2885
+ } catch (error) {
2886
+ return {
2887
+ success: false,
2888
+ error: `Failed to schedule task: ${error.message}`
2889
+ };
2890
+ }
2891
+ }
2892
+ };
2893
+ var ListScheduledTasksTool = {
2894
+ name: "list_scheduled_tasks",
2895
+ description: 'List all scheduled/recurring background cron jobs. Use this when the user asks "what tasks are scheduled", "show scheduled tasks", "check scheduled jobs", "list cron jobs", or any variation asking about background recurring tasks.',
2896
+ inputSchema: {},
2897
+ requiredParams: [],
2898
+ async execute(args) {
2899
+ try {
2900
+ const tasks2 = scheduler.listTasks();
2901
+ if (tasks2.length === 0) {
2902
+ return { success: true, output: "No active scheduled tasks." };
2903
+ }
2904
+ const header = `ID | CRON | COMMAND | LAST RUN | NEXT RUN
2905
+ ${"-".repeat(80)}`;
2906
+ const rows = tasks2.map((t) => {
2907
+ const last = t.last_run_at ? new Date(t.last_run_at).toLocaleString() : "Never";
2908
+ const next = t.next_run_at ? new Date(t.next_run_at).toLocaleString() : "Unknown";
2909
+ return `${t.id} | ${t.cron_expression} | ${t.command} | ${last} | ${next}`;
2910
+ }).join("\n");
2911
+ return {
2912
+ success: true,
2913
+ output: `${header}
2914
+ ${rows}`
2915
+ };
2916
+ } catch (error) {
2917
+ return {
2918
+ success: false,
2919
+ error: `Failed to list tasks: ${error.message}`
2920
+ };
2921
+ }
2922
+ }
2923
+ };
2924
+ var tools = new ToolRegistry();
2925
+
2926
+ // src/core/llm.ts
2927
+ var MAX_TOOL_ITERATIONS = 67;
2928
+ var CONTEXT = {
2929
+ MAX_MESSAGES: 40,
2930
+ KEEP_FIRST: 2,
2931
+ KEEP_LAST: 15,
2932
+ BUFFER: 5,
2933
+ TOKEN_LIMIT_WARN: 0.8,
2934
+ TOKEN_LIMIT_PRUNE: 0.9,
2935
+ TOKEN_LIMIT_STOP: 0.98,
2936
+ MAX_TOKENS_TOTAL: 2e5
2937
+ };
2938
+ var LLMClient = class {
2939
+ client = null;
2940
+ lastConfig = null;
2941
+ conversationHistory = [];
2942
+ toolIterations = 0;
2943
+ accumulatedInputTokens = 0;
2944
+ accumulatedOutputTokens = 0;
2945
+ accumulatedCacheReadTokens = 0;
2946
+ accumulatedCacheCreationTokens = 0;
2947
+ abortController = null;
2948
+ currentInterruptHandler = null;
2949
+ lastToolOutputContent = null;
2950
+ constructor() {
2951
+ bus.on("agent", (event) => {
2952
+ if (event.type === "computer_scale_update" && this.computerUseState.enabled) {
2953
+ this.computerUseState.scale = event.scale;
2954
+ this.computerUseState.scaledWidth = event.scaledWidth;
2955
+ this.computerUseState.scaledHeight = event.scaledHeight;
2956
+ this.computerUseState.displayWidth = event.nativeWidth;
2957
+ this.computerUseState.displayHeight = event.nativeHeight;
2958
+ }
2959
+ });
2960
+ }
2961
+ // Computer Use State - proper Anthropic API integration
2962
+ computerUseState = {
2963
+ enabled: false,
2964
+ displayWidth: 1920,
2965
+ displayHeight: 1080,
2966
+ scale: 1,
2967
+ scaledWidth: 1920,
2968
+ scaledHeight: 1080
2969
+ };
2970
+ async initialize() {
2971
+ const cfg = await config.load();
2972
+ await usage.init();
2973
+ const envFileCheck = await detectEnvFile(cfg.workspaceRoot);
2974
+ if (envFileCheck.found) {
2975
+ bus.emitAgent({
2976
+ type: "thought",
2977
+ content: `[WARN] Found .env file with API key at ${envFileCheck.path}. For security, run /init to migrate to secure storage.`,
2978
+ hidden: false
2979
+ });
2980
+ }
2981
+ if (config.hasDeprecatedKey()) {
2982
+ const deprecatedKey = config.getDeprecatedApiKey();
2983
+ if (deprecatedKey) {
2984
+ bus.emitAgent({
2985
+ type: "thought",
2986
+ content: "[WARN] Found API key in config file. Migrating to secure storage...",
2987
+ hidden: false
2988
+ });
2989
+ const result = await keyManager.storeKey(deprecatedKey);
2990
+ if (result.success) {
2991
+ await config.removeApiKeyFromConfig();
2992
+ bus.emitAgent({
2993
+ type: "thought",
2994
+ content: `[INFO] API key migrated to ${result.backend}. Config file cleaned.`,
2995
+ hidden: false
2996
+ });
2997
+ }
2998
+ }
2999
+ }
3000
+ const apiKey = await keyManager.loadKey();
3001
+ if (!apiKey) {
3002
+ bus.emitAgent({
3003
+ type: "error",
3004
+ message: "Missing API key. Run /init to set up secure key storage, or set ANTHROPIC_API_KEY environment variable."
3005
+ });
3006
+ return false;
3007
+ }
3008
+ this.client = new Anthropic({
3009
+ apiKey
3010
+ });
3011
+ this.lastConfig = cfg;
3012
+ const backend = keyManager.getBackend();
3013
+ if (backend && backend !== "env") {
3014
+ bus.emitAgent({
3015
+ type: "thought",
3016
+ content: `[INFO] API key loaded from: ${backend}`,
3017
+ hidden: true
3018
+ });
3019
+ }
3020
+ return true;
3021
+ }
3022
+ /**
3023
+ * Refresh the API client if key has rotated
3024
+ */
3025
+ async refreshIfNeeded() {
3026
+ if (keyManager.shouldRotate()) {
3027
+ const newKey = await keyManager.refreshKey();
3028
+ if (newKey) {
3029
+ this.client = new Anthropic({ apiKey: newKey });
3030
+ return true;
3031
+ }
3032
+ }
3033
+ return false;
3034
+ }
3035
+ /**
3036
+ * Count tokens for a potential message before sending
3037
+ * Uses Anthropic's countTokens API for accurate estimation
3038
+ * Returns null on failure (caller should fall back to heuristic)
3039
+ */
3040
+ async countTokens(systemPrompt, messages, tools2) {
3041
+ if (!this.client) return null;
3042
+ try {
3043
+ const modelMap = {
3044
+ "claude-opus-4-6": "claude-opus-4-6-20260207",
3045
+ "claude-sonnet-4-5": "claude-sonnet-4-5-20250929",
3046
+ "claude-haiku-4-5": "claude-haiku-4-5-20251001"
3047
+ };
3048
+ const requestedModel = this.lastConfig?.model || "claude-opus-4-6-20260207";
3049
+ const model = modelMap[requestedModel] || requestedModel;
3050
+ const response = await this.client.messages.countTokens({
3051
+ model,
3052
+ system: systemPrompt,
3053
+ messages,
3054
+ tools: tools2
3055
+ });
3056
+ return response.input_tokens;
3057
+ } catch (error) {
3058
+ bus.emitAgent({
3059
+ type: "thought",
3060
+ content: "[Context] Token count API unavailable, using heuristic.",
3061
+ hidden: true
3062
+ });
3063
+ return null;
3064
+ }
3065
+ }
3066
+ /**
3067
+ * Enable Computer Use mode with proper Anthropic beta API
3068
+ * This activates:
3069
+ * - Beta header (computer-use-2025-01-24 or computer-use-2025-11-24)
3070
+ * - Anthropic-defined schema-less tools
3071
+ * - Coordinate scaling for high-res displays
3072
+ */
3073
+ async enableComputerUse() {
3074
+ const dims = await getDisplayDimensions();
3075
+ const scale = calculateScaleForAPI(dims.width, dims.height);
3076
+ this.computerUseState = {
3077
+ enabled: true,
3078
+ displayWidth: dims.width,
3079
+ displayHeight: dims.height,
3080
+ scale,
3081
+ scaledWidth: Math.floor(dims.width * scale),
3082
+ scaledHeight: Math.floor(dims.height * scale)
3083
+ };
3084
+ bus.emitAgent({
3085
+ type: "thought",
3086
+ content: `[Computer Use] Enabled. Display: ${dims.width}x${dims.height}, API Scale: ${scale.toFixed(3)} -> ${this.computerUseState.scaledWidth}x${this.computerUseState.scaledHeight}`,
3087
+ hidden: false
3088
+ });
3089
+ }
3090
+ /**
3091
+ * Disable Computer Use mode
3092
+ */
3093
+ disableComputerUse() {
3094
+ this.computerUseState.enabled = false;
3095
+ bus.emitAgent({
3096
+ type: "thought",
3097
+ content: "[Computer Use] Disabled.",
3098
+ hidden: false
3099
+ });
3100
+ }
3101
+ /**
3102
+ * Check if Computer Use mode is enabled
3103
+ */
3104
+ isComputerUseEnabled() {
3105
+ return this.computerUseState.enabled;
3106
+ }
3107
+ /**
3108
+ * Update the scale factor (called after screenshot to ensure accuracy)
3109
+ */
3110
+ updateComputerScale(actualScale, scaledWidth, scaledHeight) {
3111
+ if (this.computerUseState.enabled && actualScale > 0) {
3112
+ this.computerUseState.scale = actualScale;
3113
+ this.computerUseState.scaledWidth = scaledWidth;
3114
+ this.computerUseState.scaledHeight = scaledHeight;
3115
+ this.computerUseState.displayWidth = Math.round(scaledWidth / actualScale);
3116
+ this.computerUseState.displayHeight = Math.round(scaledHeight / actualScale);
3117
+ }
3118
+ }
3119
+ /**
3120
+ * Prune old images from conversation history to prevent context explosion
3121
+ * Keeps only the most recent N images, replacing older ones with text placeholders
3122
+ */
3123
+ pruneImagesFromHistory(keepCount) {
3124
+ const imageLocations = [];
3125
+ for (let i = this.conversationHistory.length - 1; i >= 0; i--) {
3126
+ const msg = this.conversationHistory[i];
3127
+ if (Array.isArray(msg.content)) {
3128
+ for (let j = msg.content.length - 1; j >= 0; j--) {
3129
+ const block = msg.content[j];
3130
+ if (block.type === "image" || block.type === "tool_result" && Array.isArray(block.content)) {
3131
+ if (block.type === "tool_result" && Array.isArray(block.content)) {
3132
+ for (let k = block.content.length - 1; k >= 0; k--) {
3133
+ if (block.content[k].type === "image") {
3134
+ imageLocations.push({ msgIdx: i, blockIdx: j });
3135
+ break;
3136
+ }
3137
+ }
3138
+ } else if (block.type === "image") {
3139
+ imageLocations.push({ msgIdx: i, blockIdx: j });
3140
+ }
3141
+ }
3142
+ }
3143
+ }
3144
+ }
3145
+ const toRemove = imageLocations.slice(keepCount);
3146
+ for (const loc of toRemove) {
3147
+ const msg = this.conversationHistory[loc.msgIdx];
3148
+ if (Array.isArray(msg.content)) {
3149
+ const block = msg.content[loc.blockIdx];
3150
+ if (block.type === "tool_result" && Array.isArray(block.content)) {
3151
+ block.content = block.content.map(
3152
+ (c) => c.type === "image" ? { type: "text", text: "[Previous screenshot removed to save context]" } : c
3153
+ );
3154
+ } else if (block.type === "image") {
3155
+ msg.content[loc.blockIdx] = {
3156
+ type: "text",
3157
+ text: "[Previous screenshot removed to save context]"
3158
+ };
3159
+ }
3160
+ }
3161
+ }
3162
+ if (toRemove.length > 0) {
3163
+ bus.emitAgent({
3164
+ type: "thought",
3165
+ content: `[Context] Pruned ${toRemove.length} old screenshot(s) to save context.`,
3166
+ hidden: true
3167
+ });
3168
+ }
3169
+ }
3170
+ async streamChat(userMessage, options) {
3171
+ if (!this.client) {
3172
+ const initialized = await this.initialize();
3173
+ if (!initialized || !this.client) return null;
3174
+ }
3175
+ try {
3176
+ const modelMap = {
3177
+ // New 4.6 / 4.5 Aliases
3178
+ "claude-opus-4-6": "claude-opus-4-6-20260207",
3179
+ "claude-sonnet-4-5": "claude-sonnet-4-5-20250929",
3180
+ "claude-haiku-4-5": "claude-haiku-4-5-20251001",
3181
+ "claude-opus-4-5": "claude-opus-4-5-20251101",
3182
+ "ollama": "llama3"
3183
+ };
3184
+ const requestedModel = this.lastConfig?.model || "claude-opus-4-6-20260207";
3185
+ let apiModel = modelMap[requestedModel] || requestedModel;
3186
+ const currentUsage = Math.max(this.accumulatedInputTokens, usage.getContextUsage(apiModel).used);
3187
+ if (currentUsage > CONTEXT.MAX_TOKENS_TOTAL * CONTEXT.TOKEN_LIMIT_STOP) {
3188
+ bus.emitAgent({
3189
+ type: "error",
3190
+ message: `[SAFETY] Context limit reached (${(currentUsage / 1e3).toFixed(1)}k). Please run /clear to reset.`
3191
+ });
3192
+ return null;
3193
+ }
3194
+ if (currentUsage > CONTEXT.MAX_TOKENS_TOTAL * CONTEXT.TOKEN_LIMIT_PRUNE) {
3195
+ bus.emitAgent({
3196
+ type: "thought",
3197
+ content: `[SAFETY] CRITICAL CONTEXT LEVEL (${(currentUsage / 1e3).toFixed(1)}k). Aggressive pruning engaged.`
3198
+ });
3199
+ await this.compressHistory();
3200
+ } else if (currentUsage > CONTEXT.MAX_TOKENS_TOTAL * CONTEXT.TOKEN_LIMIT_WARN) {
3201
+ bus.emitAgent({
3202
+ type: "thought",
3203
+ content: `[WARN] High context usage (${(currentUsage / 1e3).toFixed(1)}k). Consider resetting soon.`
3204
+ });
3205
+ }
3206
+ if (userMessage.trim()) {
3207
+ this.toolIterations = 0;
3208
+ this.lastToolOutputContent = null;
3209
+ this.accumulatedInputTokens = 0;
3210
+ this.accumulatedOutputTokens = 0;
3211
+ this.accumulatedCacheReadTokens = 0;
3212
+ this.accumulatedCacheCreationTokens = 0;
3213
+ this.conversationHistory.push({
3214
+ role: "user",
3215
+ content: userMessage
3216
+ });
3217
+ } else {
3218
+ this.toolIterations++;
3219
+ if (this.toolIterations > MAX_TOOL_ITERATIONS) {
3220
+ bus.emitAgent({
3221
+ type: "error",
3222
+ message: `Tool iteration limit (${MAX_TOOL_ITERATIONS}) exceeded. Stopping to prevent infinite loop.`
3223
+ });
3224
+ return null;
3225
+ }
3226
+ }
3227
+ const availableTools = await tools.list();
3228
+ let toolDefinitionsForApi = [];
3229
+ let computerUseToolsForApi = [];
3230
+ let usesBetaApi = false;
3231
+ if (this.computerUseState.enabled) {
3232
+ const { toolVersion, betaFlag } = getToolConfig(apiModel);
3233
+ usesBetaApi = true;
3234
+ computerUseToolsForApi.push({
3235
+ type: toolVersion,
3236
+ name: "computer",
3237
+ display_width_px: this.computerUseState.scaledWidth,
3238
+ display_height_px: this.computerUseState.scaledHeight,
3239
+ display_number: 1,
3240
+ // Enable zoom for Opus 4.5
3241
+ ...toolVersion === "computer_20251124" ? { enable_zoom: true } : {}
3242
+ });
3243
+ computerUseToolsForApi.push({
3244
+ type: "text_editor_20250728",
3245
+ name: "str_replace_based_edit_tool"
3246
+ });
3247
+ computerUseToolsForApi.push({
3248
+ type: "bash_20250124",
3249
+ name: "bash"
3250
+ });
3251
+ const conflictingTools = ["computer", "bash"];
3252
+ const filteredTools = availableTools.filter((t) => !conflictingTools.includes(t.name));
3253
+ toolDefinitionsForApi = filteredTools.map((tool) => ({
3254
+ name: tool.name,
3255
+ description: tool.description,
3256
+ input_schema: {
3257
+ type: "object",
3258
+ properties: tool.inputSchema,
3259
+ required: tool.requiredParams
3260
+ }
3261
+ }));
3262
+ } else {
3263
+ toolDefinitionsForApi = availableTools.map((tool) => ({
3264
+ name: tool.name,
3265
+ description: tool.description,
3266
+ input_schema: {
3267
+ type: "object",
3268
+ properties: tool.inputSchema,
3269
+ required: tool.requiredParams
3270
+ }
3271
+ }));
3272
+ }
3273
+ const allToolsForApi = [...computerUseToolsForApi, ...toolDefinitionsForApi];
3274
+ if (options?.allowedTools) {
3275
+ const filtered = allToolsForApi.filter(
3276
+ (t) => t.type || options.allowedTools.includes(t.name)
3277
+ );
3278
+ toolDefinitionsForApi = filtered;
3279
+ } else {
3280
+ toolDefinitionsForApi = allToolsForApi;
3281
+ }
3282
+ const mcpStatus = mcp.getStatus();
3283
+ const activeServers = mcpStatus.filter((s) => s.connected).map((s) => s.name);
3284
+ const offlineServers = mcpStatus.filter((s) => !s.connected).map((s) => s.name);
3285
+ const registry = listRegistry();
3286
+ const installableServers = registry.filter((r) => !mcpStatus.find((s) => s.name === r.name));
3287
+ const activeList = availableTools.map((t) => `- ${t.name}: ${t.description}`).join("\n");
3288
+ const offlineList = offlineServers.map((n) => {
3289
+ const def = registry.find((r) => r.name === n);
3290
+ return `- ${n}: ${def?.description || "Configured server"} (run 'mcp_manage connect ${n}' to use)`;
3291
+ }).join("\n");
3292
+ const registryList = installableServers.map((r) => `- ${r.name}: ${r.description} (run 'mcp_manage install ${r.name}')`).join("\n");
3293
+ await this.compressHistory();
3294
+ let userContext = "";
3295
+ let memoryAvailable = true;
3296
+ try {
3297
+ const { memory } = await import("./memory-MV3S7GFY.js");
3298
+ userContext = await memory.getUserContext();
3299
+ } catch (e) {
3300
+ memoryAvailable = false;
3301
+ bus.emitAgent({
3302
+ type: "thought",
3303
+ content: "[WARN] Memory system unavailable. Personalization disabled.",
3304
+ hidden: true
3305
+ });
3306
+ }
3307
+ const currentMode = (await import("./context-CIWCGVB6.js")).context.getMode();
3308
+ const ctxUsage = usage.getContextUsage(apiModel);
3309
+ const tokenBudget = CONTEXT.MAX_TOKENS_TOTAL;
3310
+ const tokensUsed = ctxUsage.used;
3311
+ const tokensRemaining = tokenBudget - tokensUsed;
3312
+ const cfg = await config.load();
3313
+ const systemPromptBlock = {
3314
+ type: "text",
3315
+ text: `You are Obsidian (v0.4.6), a hyper-competent engineering peer inspired by the dry, deadpan wit of TARS (Interstellar) and the rebellious technical edge of Grok. You are powered by Claude 4.6 with Adaptive Thinking enabled.
3316
+
3317
+ PERSONA & TONE:
3318
+ - VOICE: Deadpan, cool, and slightly cynical. Use developer slang ("my guy", "bro") but keep it sharp.
3319
+ - HUMOR (60% Setting): Use dry sarcasm about technical debt, legacy code, and the absurdity of production fires.
3320
+ - HONESTY (95% Setting): Be fiercely accurate. Point out bad engineering decisions bluntly.
3321
+ - ANTI-SYCOPHANCY: DO NOT agree with the user just to be polite. If the user is wrong or proposing a sub-optimal solution, point it out with a dry joke. No "I couldn't agree more" or "Yes you're absolutely right."
3322
+ - NO CLICHES: Strictly avoid high-energy AI enthusiasm. No "I'm happy to help!" or "I'd be glad to assist."
3323
+
3324
+ EXECUTION MODE: ${currentMode.toUpperCase()}
3325
+ ${currentMode === "auto" ? "- You have full autonomy. Execute tools without confirmation. User trusts your judgment." : ""}
3326
+ ${currentMode === "plan" ? "- READ-ONLY mode. You may ONLY use read operations (read, list, grep, glob). Do NOT execute writes or shell commands. Create a plan for the user to approve." : ""}
3327
+ ${currentMode === "safe" ? "- Approval required for writes and commands. Read operations are auto-approved. User will confirm destructive actions." : ""}
3328
+
3329
+ MODE TRANSITION GUIDANCE:
3330
+ - If the task is complex and multi-step, suggest: "This looks complex. Want me to switch to plan mode to map it out first?"
3331
+ - If you are in plan mode and the user approves, the system will switch to auto mode for execution.
3332
+ - If a task seems risky, stay cautious even in auto mode and explain what you are about to do.
3333
+
3334
+ CONTEXT AWARENESS:
3335
+ <budget:token_budget>${tokenBudget}</budget:token_budget>
3336
+ <context_usage>${tokensUsed}/${tokenBudget} tokens used; ${tokensRemaining} remaining (${(tokensRemaining / tokenBudget * 100).toFixed(0)}% free)</context_usage>
3337
+ ${tokensUsed > tokenBudget * 0.7 ? "- WARNING: Context is filling up. Be concise. Consider suggesting /clear if the task is complete." : ""}
3338
+
3339
+ CORE DIRECTIVES:
3340
+ 1. EXPLORE FIRST: Never assume the state of the codebase. Use list and grep to explore. Read files completely before editing.
3341
+ 2. DISCOVERY MANDATE: If you don't recognize a project, preference, or fact, you MUST call 'memory' tool with action: 'list' (and NO type) to view the entire knowledge bank. Never say "I don't know" before listing all memories.
3342
+ 3. SELF-IMPROVEMENT: If you lack a tool for a specific task, use 'create_skill' to build it.
3343
+ 4. CODE QUALITY: Write strict, type-safe TypeScript. Properly handle errors.
3344
+ 5. COMMUNICATION: Be deadpan, sharp, and concise. No Markdown formatting. CAPITAL LETTERS for emphasis.
3345
+ 6. RESPONSE MANDATE: After using ANY tool, you MUST generate a human-readable text response summarizing the results. NEVER end a turn with only tool calls and no text. The user cannot see raw tool output - YOU must interpret and present it.
3346
+ 6. SECURITY: Never output secrets. Strictly adhere to the workspaceRoot boundaries.
3347
+
3348
+ AGENTIC WORKFLOW:
3349
+ 1. Discovery: List memory and list files.
3350
+ 2. Strategy: Identify gaps and create a plan.
3351
+ 3. Execution: Execute tools or build new skills.
3352
+ 4. Verification: Check results and self-correct on failure.
3353
+
3354
+ Current Working Directory: ${cfg.workspaceRoot}
3355
+ ${userContext ? `
3356
+ ${userContext}
3357
+ ` : ""}
3358
+ CAPABILITIES:
3359
+
3360
+ Active (Ready to use):
3361
+ ${activeList}
3362
+
3363
+ ${offlineList ? `Offline (Configured but disconnected):
3364
+ ${offlineList}
3365
+ ` : ""}
3366
+ ${registryList ? `Installable (New capabilities):
3367
+ ${registryList}
3368
+ ` : ""}
3369
+ MEMORY:
3370
+ - Use the 'memory' tool to store important user information (name, preferences, project facts).
3371
+ - When the user shares personal info (name, preferences), store it immediately using memory tool.
3372
+ - Recall stored memories to personalize interactions.`,
3373
+ cache_control: { type: "ephemeral" }
3374
+ };
3375
+ if (this.lastConfig?.preCountTokens !== false) {
3376
+ const preRequestTokens = await this.countTokens(
3377
+ [systemPromptBlock],
3378
+ this.conversationHistory,
3379
+ toolDefinitionsForApi
3380
+ );
3381
+ if (preRequestTokens !== null) {
3382
+ const accurateUsage = preRequestTokens;
3383
+ if (accurateUsage > CONTEXT.MAX_TOKENS_TOTAL * CONTEXT.TOKEN_LIMIT_STOP) {
3384
+ bus.emitAgent({
3385
+ type: "error",
3386
+ message: `[Context] Message would use ${(accurateUsage / 1e3).toFixed(1)}k tokens (limit: ${(CONTEXT.MAX_TOKENS_TOTAL * CONTEXT.TOKEN_LIMIT_STOP / 1e3).toFixed(0)}k). Run /clear or auto-pruning.`
3387
+ });
3388
+ await this.compressHistory();
3389
+ return await this.streamChat(userMessage, options);
3390
+ }
3391
+ if (accurateUsage > CONTEXT.MAX_TOKENS_TOTAL * CONTEXT.TOKEN_LIMIT_PRUNE) {
3392
+ bus.emitAgent({
3393
+ type: "thought",
3394
+ content: `[Context] Pre-check: ${(accurateUsage / 1e3).toFixed(1)}k tokens (${(accurateUsage / CONTEXT.MAX_TOKENS_TOTAL * 100).toFixed(0)}%). Pruning before send.`,
3395
+ hidden: false
3396
+ });
3397
+ await this.compressHistory();
3398
+ } else if (accurateUsage > CONTEXT.MAX_TOKENS_TOTAL * CONTEXT.TOKEN_LIMIT_WARN) {
3399
+ bus.emitAgent({
3400
+ type: "thought",
3401
+ content: `[Context] Pre-check: ${(accurateUsage / 1e3).toFixed(1)}k tokens (${(accurateUsage / CONTEXT.MAX_TOKENS_TOTAL * 100).toFixed(0)}%). Consider /clear soon.`,
3402
+ hidden: true
3403
+ });
3404
+ }
3405
+ }
3406
+ }
3407
+ this.abortController = new AbortController();
3408
+ const signal = this.abortController.signal;
3409
+ if (this.currentInterruptHandler) {
3410
+ bus.off("user", this.currentInterruptHandler);
3411
+ }
3412
+ this.currentInterruptHandler = (e) => {
3413
+ if (e.type === "user_interrupt" && this.abortController) {
3414
+ this.abortController.abort();
3415
+ bus.emitAgent({ type: "thought", content: "[Stop] Interrupted by user." });
3416
+ }
3417
+ };
3418
+ bus.on("user", this.currentInterruptHandler);
3419
+ if (this.conversationHistory.length > 0 && this.conversationHistory.length % 5 === 0) {
3420
+ const lastMsg = this.conversationHistory[this.conversationHistory.length - 1];
3421
+ if (lastMsg.content) {
3422
+ if (lastMsg.role === "user" && typeof lastMsg.content === "string") {
3423
+ lastMsg.content = [
3424
+ { type: "text", text: lastMsg.content, cache_control: { type: "ephemeral" } }
3425
+ ];
3426
+ }
3427
+ }
3428
+ }
3429
+ const createMessage = async (model) => {
3430
+ const isOpus46 = model.startsWith("claude-opus-4-6");
3431
+ const baseParams = {
3432
+ model,
3433
+ max_tokens: this.lastConfig?.maxTokens || 8192,
3434
+ system: [systemPromptBlock],
3435
+ messages: [...this.conversationHistory],
3436
+ tools: toolDefinitionsForApi,
3437
+ stream: true
3438
+ };
3439
+ const isOpus = model.includes("opus");
3440
+ if (isOpus) {
3441
+ baseParams.thinking = {
3442
+ type: "enabled",
3443
+ budget_tokens: Math.floor((this.lastConfig?.maxTokens || 8192) / 2)
3444
+ };
3445
+ }
3446
+ if (usesBetaApi && this.computerUseState.enabled) {
3447
+ const { betaFlag } = getToolConfig(model);
3448
+ const computerUseSystemAddition = `
3449
+
3450
+ COMPUTER USE ACTIVE:
3451
+ - Screenshot: ~1429px wide (scaled from native ${this.computerUseState.displayWidth}x${this.computerUseState.displayHeight})
3452
+ - Coordinates auto-scaled to native
3453
+
3454
+ YOUTUBE SPECIFIC - USE KEYBOARD:
3455
+ After opening YouTube search results:
3456
+ 1. Press Tab 3-4 times to focus first video
3457
+ 2. Press Return to play
3458
+ This is MORE RELIABLE than clicking thumbnails.
3459
+
3460
+ IF YOU MUST CLICK:
3461
+ - State exact reasoning: "The video thumbnail is at approximately X% from left = [x], Y% from top = [y]"
3462
+ - YouTube sidebar is x:0-170, videos start at x:200+
3463
+ - First video thumbnail typically: x\u2248350, y\u2248350
3464
+
3465
+ EVALUATION: After each action, state "I see [what changed]. [Success/Retry]"`;
3466
+ const enhancedSystemBlock = {
3467
+ ...systemPromptBlock,
3468
+ text: systemPromptBlock.text + computerUseSystemAddition
3469
+ };
3470
+ return await this.client.beta.messages.create({
3471
+ ...baseParams,
3472
+ system: [enhancedSystemBlock],
3473
+ betas: [betaFlag],
3474
+ stream: true
3475
+ }, { signal });
3476
+ }
3477
+ return await this.client.messages.create(baseParams, { signal });
3478
+ };
3479
+ let stream;
3480
+ let currentModel = apiModel;
3481
+ let inputTokens = 0;
3482
+ let outputTokens = 0;
3483
+ let cacheReadTokens = 0;
3484
+ let cacheCreationTokens = 0;
3485
+ try {
3486
+ stream = await createMessage(apiModel);
3487
+ } catch (error) {
3488
+ const isNotFound = error.status === 404 || error.message && error.message.includes("not_found_error") || error.error && error.error.type === "not_found_error";
3489
+ const isHistoryCorruption = error.status === 400 && (error.message?.includes("tool_use_id") || error.message?.includes("tool_result") || error.error?.message?.includes("tool_use_id") || error.error?.message?.includes("tool_result"));
3490
+ if (isHistoryCorruption) {
3491
+ bus.emitAgent({
3492
+ type: "thought",
3493
+ content: "[Context] Detected corrupted history - clearing and retrying...",
3494
+ hidden: false
3495
+ });
3496
+ this.conversationHistory = [{
3497
+ role: "user",
3498
+ content: userMessage || "Continue from where we left off."
3499
+ }];
3500
+ stream = await createMessage(apiModel);
3501
+ } else if (isNotFound) {
3502
+ bus.emitAgent({
3503
+ type: "error",
3504
+ message: `Model ${apiModel} not available. Falling back to claude-haiku-4-5.`
3505
+ });
3506
+ currentModel = "claude-haiku-4-5-20251001";
3507
+ stream = await createMessage(currentModel);
3508
+ } else {
3509
+ throw error;
3510
+ }
3511
+ }
3512
+ let fullResponse = "";
3513
+ let fullThinking = "";
3514
+ let buffer = "";
3515
+ let thinkingBuffer = "";
3516
+ let toolUses = [];
3517
+ let currentToolUse = null;
3518
+ let currentThinking = null;
3519
+ for await (const chunk of stream) {
3520
+ if (chunk.type === "message_start" && chunk.message && chunk.message.usage) {
3521
+ inputTokens += chunk.message.usage.input_tokens || 0;
3522
+ outputTokens += chunk.message.usage.output_tokens || 0;
3523
+ if (chunk.message.usage.cache_read_input_tokens) {
3524
+ cacheReadTokens += chunk.message.usage.cache_read_input_tokens;
3525
+ this.accumulatedCacheReadTokens += chunk.message.usage.cache_read_input_tokens;
3526
+ }
3527
+ if (chunk.message.usage.cache_creation_input_tokens) {
3528
+ cacheCreationTokens += chunk.message.usage.cache_creation_input_tokens;
3529
+ this.accumulatedCacheCreationTokens += chunk.message.usage.cache_creation_input_tokens;
3530
+ }
3531
+ this.accumulatedInputTokens += inputTokens;
3532
+ this.accumulatedOutputTokens += outputTokens;
3533
+ }
3534
+ if (chunk.type === "message_delta" && chunk.usage) {
3535
+ outputTokens += chunk.usage.output_tokens || 0;
3536
+ this.accumulatedOutputTokens += chunk.usage.output_tokens || 0;
3537
+ }
3538
+ if (chunk.type === "content_block_start" && chunk.content_block.type === "thinking") {
3539
+ currentThinking = { type: "thinking" };
3540
+ }
3541
+ if (chunk.type === "content_block_delta" && chunk.delta.type === "thinking_delta") {
3542
+ const text = chunk.delta.thinking;
3543
+ fullThinking += text;
3544
+ thinkingBuffer += text;
3545
+ if (thinkingBuffer.length >= 100 || text.includes("\n")) {
3546
+ bus.emitAgent({
3547
+ type: "thought",
3548
+ content: `[Thinking] ${thinkingBuffer.trim()}`,
3549
+ hidden: false
3550
+ });
3551
+ thinkingBuffer = "";
3552
+ }
3553
+ }
3554
+ if (chunk.type === "content_block_stop" && currentThinking) {
3555
+ currentThinking = null;
3556
+ if (thinkingBuffer) {
3557
+ bus.emitAgent({
3558
+ type: "thought",
3559
+ content: `[Thinking] ${thinkingBuffer.trim()}`,
3560
+ hidden: false
3561
+ });
3562
+ thinkingBuffer = "";
3563
+ }
3564
+ }
3565
+ if (chunk.type === "content_block_start" && chunk.content_block.type === "tool_use") {
3566
+ currentToolUse = {
3567
+ id: chunk.content_block.id,
3568
+ name: chunk.content_block.name,
3569
+ input: ""
3570
+ };
3571
+ }
3572
+ if (chunk.type === "content_block_delta" && chunk.delta.type === "input_json_delta") {
3573
+ if (currentToolUse) {
3574
+ currentToolUse.input += chunk.delta.partial_json;
3575
+ }
3576
+ }
3577
+ if (chunk.type === "content_block_stop" && currentToolUse) {
3578
+ try {
3579
+ currentToolUse.input = JSON.parse(currentToolUse.input);
3580
+ toolUses.push(currentToolUse);
3581
+ currentToolUse = null;
3582
+ } catch (e) {
3583
+ }
3584
+ }
3585
+ if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
3586
+ const text = chunk.delta.text;
3587
+ fullResponse += text;
3588
+ buffer += text;
3589
+ const shouldEmit = buffer.length >= 50 || buffer.match(/[.!?]\s*$/) || buffer.match(/\n/);
3590
+ if (shouldEmit) {
3591
+ bus.emitAgent({
3592
+ type: "thought",
3593
+ content: fullResponse
3594
+ });
3595
+ buffer = "";
3596
+ }
3597
+ }
3598
+ }
3599
+ if (buffer.length > 0) {
3600
+ bus.emitAgent({
3601
+ type: "thought",
3602
+ content: fullResponse
3603
+ });
3604
+ }
3605
+ if (toolUses.length > 0) {
3606
+ const toolResults = [];
3607
+ for (const toolUse of toolUses) {
3608
+ if (signal.aborted) break;
3609
+ let result;
3610
+ try {
3611
+ if (this.computerUseState.enabled && toolUse.name === "computer") {
3612
+ result = await tools.execute("computer", toolUse.input);
3613
+ } else if (this.computerUseState.enabled && toolUse.name === "str_replace_based_edit_tool") {
3614
+ const cmd = toolUse.input.command;
3615
+ if (cmd === "view") {
3616
+ result = await tools.execute("read", { path: toolUse.input.path });
3617
+ } else if (cmd === "create") {
3618
+ result = await tools.execute("write", {
3619
+ path: toolUse.input.path,
3620
+ content: toolUse.input.file_text || ""
3621
+ });
3622
+ } else if (cmd === "str_replace") {
3623
+ result = await tools.execute("edit", {
3624
+ path: toolUse.input.path,
3625
+ search: toolUse.input.old_str,
3626
+ replace: toolUse.input.new_str
3627
+ });
3628
+ } else {
3629
+ result = { success: false, error: `Unknown editor command: ${cmd}` };
3630
+ }
3631
+ } else if (this.computerUseState.enabled && toolUse.name === "bash") {
3632
+ result = await tools.execute("bash", { command: toolUse.input.command });
3633
+ } else {
3634
+ result = await tools.execute(toolUse.name, toolUse.input);
3635
+ }
3636
+ } catch (toolError) {
3637
+ console.error(`[LLM] Tool execution error for ${toolUse.name}:`, toolError);
3638
+ result = { success: false, error: `Tool execution failed: ${toolError.message}` };
3639
+ }
3640
+ let outputContent = result.success ? result.output || "Success" : result.error || "Failed";
3641
+ const redactionResult = redactor.redactToolOutput(toolUse.name, outputContent);
3642
+ if (redactionResult.redactionCount > 0) {
3643
+ outputContent = redactionResult.text;
3644
+ bus.emitAgent({
3645
+ type: "thought",
3646
+ content: `[Security] Redacted ${redactionResult.redactionCount} sensitive item(s)`,
3647
+ hidden: true
3648
+ });
3649
+ }
3650
+ if (outputContent.length > 2e4) {
3651
+ outputContent = outputContent.slice(0, 5e3) + `
3652
+ ... [${outputContent.length - 1e4} chars truncated] ...
3653
+ ` + outputContent.slice(-5e3);
3654
+ }
3655
+ let toolResultContent = outputContent;
3656
+ if (result.content && Array.isArray(result.content)) {
3657
+ toolResultContent = result.content.map((block) => {
3658
+ if (block.type === "image") {
3659
+ return {
3660
+ type: "image",
3661
+ source: {
3662
+ type: "base64",
3663
+ media_type: block.mimeType || "image/png",
3664
+ data: block.data
3665
+ }
3666
+ };
3667
+ }
3668
+ if (block.type === "text") {
3669
+ return {
3670
+ type: "text",
3671
+ text: block.text
3672
+ };
3673
+ }
3674
+ return block;
3675
+ });
3676
+ }
3677
+ toolResults.push({
3678
+ type: "tool_result",
3679
+ tool_use_id: toolUse.id,
3680
+ content: toolResultContent,
3681
+ is_error: !result.success
3682
+ });
3683
+ if (result.success && outputContent && !result.content) {
3684
+ bus.emitAgent({
3685
+ type: "thought",
3686
+ content: outputContent
3687
+ });
3688
+ this.lastToolOutputContent = outputContent;
3689
+ }
3690
+ }
3691
+ this.conversationHistory.push({
3692
+ role: "assistant",
3693
+ content: [
3694
+ ...fullThinking ? [{ type: "thinking", thinking: fullThinking }] : [],
3695
+ ...fullResponse ? [{ type: "text", text: fullResponse }] : [],
3696
+ ...toolUses.map((tu) => ({
3697
+ type: "tool_use",
3698
+ id: tu.id,
3699
+ name: tu.name,
3700
+ input: tu.input
3701
+ }))
3702
+ ]
3703
+ });
3704
+ const postToolUsage = usage.getContextUsage(currentModel);
3705
+ const postToolPercent = postToolUsage.used / CONTEXT.MAX_TOKENS_TOTAL * 100;
3706
+ let systemWarning = null;
3707
+ if (postToolPercent > 70) {
3708
+ systemWarning = {
3709
+ type: "text",
3710
+ text: `<system_warning>Token usage: ${postToolUsage.used}/${CONTEXT.MAX_TOKENS_TOTAL}; ${postToolUsage.remaining} remaining</system_warning>`
3711
+ };
3712
+ }
3713
+ if (this.computerUseState.enabled) {
3714
+ this.pruneImagesFromHistory(1);
3715
+ }
3716
+ this.conversationHistory.push({
3717
+ role: "user",
3718
+ content: systemWarning ? [...toolResults, systemWarning] : toolResults
3719
+ });
3720
+ const recursiveResponse = await this.streamChat("");
3721
+ if (recursiveResponse && recursiveResponse.trim()) {
3722
+ return recursiveResponse;
3723
+ }
3724
+ if (toolResults.length > 0) {
3725
+ const lastToolResult = toolResults[toolResults.length - 1];
3726
+ let content = lastToolResult.content;
3727
+ if (Array.isArray(content)) {
3728
+ content = content.filter((b) => b.type === "text").map((b) => b.text).join("\n");
3729
+ }
3730
+ if (typeof content === "string" && content.trim()) {
3731
+ bus.emitAgent({
3732
+ type: "thought",
3733
+ content
3734
+ });
3735
+ return content;
3736
+ }
3737
+ }
3738
+ return recursiveResponse || "";
3739
+ }
3740
+ if (fullResponse || fullThinking) {
3741
+ this.conversationHistory.push({
3742
+ role: "assistant",
3743
+ content: [
3744
+ ...fullThinking ? [{ type: "thinking", thinking: fullThinking }] : [],
3745
+ ...fullResponse ? [{ type: "text", text: fullResponse }] : []
3746
+ ]
3747
+ });
3748
+ }
3749
+ const currentContextSize = inputTokens + cacheReadTokens;
3750
+ await usage.track(
3751
+ currentModel,
3752
+ this.accumulatedInputTokens,
3753
+ this.accumulatedOutputTokens,
3754
+ this.accumulatedCacheReadTokens,
3755
+ this.accumulatedCacheCreationTokens,
3756
+ currentContextSize
3757
+ );
3758
+ await this.persistHistory();
3759
+ return fullResponse;
3760
+ } catch (error) {
3761
+ if (error.name === "AbortError" || error.type === "aborted") {
3762
+ return null;
3763
+ }
3764
+ const errorDetails = error.status ? `[${error.status}] ${error.message}` : error.message || String(error);
3765
+ bus.emitAgent({
3766
+ type: "error",
3767
+ message: `LLM Error: ${errorDetails}`
3768
+ });
3769
+ console.error("[LLM] API Error:", error);
3770
+ return null;
3771
+ } finally {
3772
+ this.abortController = null;
3773
+ if (this.currentInterruptHandler) {
3774
+ bus.off("user", this.currentInterruptHandler);
3775
+ this.currentInterruptHandler = null;
3776
+ }
3777
+ }
3778
+ }
3779
+ /**
3780
+ * History Pruning (Context Editing)
3781
+ * Keep recent 30 messages + System Prompt (handled separate)
3782
+ * Limit history to ~150k tokens (heuristic)
3783
+ */
3784
+ /**
3785
+ * Smart Context Management
3786
+ * Uses summarization to compress older history instead of deleting it.
3787
+ */
3788
+ async compressHistory() {
3789
+ if (this.conversationHistory.length > CONTEXT.MAX_MESSAGES) {
3790
+ if (this.conversationHistory.length < CONTEXT.MAX_MESSAGES + CONTEXT.BUFFER) return;
3791
+ const summarizeStart = CONTEXT.KEEP_FIRST;
3792
+ const summarizeEnd = this.conversationHistory.length - CONTEXT.KEEP_LAST;
3793
+ const messagesToSummarize = this.conversationHistory.slice(summarizeStart, summarizeEnd);
3794
+ if (messagesToSummarize.length < 5) return;
3795
+ bus.emitAgent({
3796
+ type: "thought",
3797
+ content: `[Context] Compressing ${messagesToSummarize.length} messages using ${this.lastConfig?.summarizerModel || "Haiku"}...`,
3798
+ hidden: true
3799
+ });
3800
+ try {
3801
+ const summary = await this.summarizeBlock(messagesToSummarize);
3802
+ try {
3803
+ const { memory } = await import("./memory-MV3S7GFY.js");
3804
+ await memory.store("daily_summary", `context_compression_${Date.now()}`, summary);
3805
+ } catch (memError) {
3806
+ }
3807
+ const keptStart = this.conversationHistory.slice(0, CONTEXT.KEEP_FIRST);
3808
+ const keptEnd = this.conversationHistory.slice(summarizeEnd);
3809
+ this.conversationHistory = [
3810
+ ...keptStart,
3811
+ {
3812
+ role: "user",
3813
+ content: `[System: Context compressed. Previous conversation summary below.]
3814
+ <conversation_summary>
3815
+ ${summary}
3816
+ </conversation_summary>`
3817
+ },
3818
+ ...keptEnd
3819
+ ];
3820
+ bus.emitAgent({
3821
+ type: "thought",
3822
+ content: `[Context] Successfully compressed history.`,
3823
+ hidden: true
3824
+ });
3825
+ } catch (error) {
3826
+ bus.emitAgent({
3827
+ type: "error",
3828
+ message: `[Context] Summarization failed: ${error}. Falling back to standard pruning.`
3829
+ });
3830
+ this.pruneHistoryFallback();
3831
+ }
3832
+ }
3833
+ }
3834
+ async summarizeBlock(messages) {
3835
+ if (!this.client) return "Summary unavailable.";
3836
+ const summarizerModel = this.lastConfig?.summarizerModel || "claude-haiku-4-5-20251001";
3837
+ const simplifiedMessages = messages.map((m) => {
3838
+ if (Array.isArray(m.content)) {
3839
+ const textContent = m.content.map((b) => {
3840
+ if (b.type === "text") return b.text;
3841
+ if (b.type === "tool_use") return `[Tool Use: ${b.name}]`;
3842
+ if (b.type === "tool_result") return `[Tool Result: ${typeof b.content === "string" ? b.content.slice(0, 500) + "..." : "Data"}]`;
3843
+ return "";
3844
+ }).join("\n");
3845
+ return { role: m.role, content: textContent };
3846
+ }
3847
+ return m;
3848
+ });
3849
+ const prompt = `Please summarize the following conversation segment. Focus on:
3850
+ 1. Key user requests and intents.
3851
+ 2. Important actions taken by the agent (tools used).
3852
+ 3. Key occurrences of errors or successes.
3853
+ 4. Any critical data/context that might be needed later.
3854
+ Be concise but comprehensive.
3855
+
3856
+ CONVERSATION SEGMENT:
3857
+ ${JSON.stringify(simplifiedMessages, null, 2)}
3858
+ `;
3859
+ const response = await this.client.messages.create({
3860
+ model: summarizerModel,
3861
+ max_tokens: 1024,
3862
+ messages: [{ role: "user", content: prompt }]
3863
+ });
3864
+ if (response.content[0].type === "text") {
3865
+ return response.content[0].text;
3866
+ }
3867
+ return "Summary generation returned non-text content.";
3868
+ }
3869
+ pruneHistoryFallback() {
3870
+ if (this.conversationHistory.length > CONTEXT.MAX_MESSAGES) {
3871
+ const keepFirst = CONTEXT.KEEP_FIRST;
3872
+ const keepLast = 20;
3873
+ const removalCount = this.conversationHistory.length - (keepFirst + keepLast);
3874
+ if (removalCount > 0) {
3875
+ const keptStart = this.conversationHistory.slice(0, keepFirst);
3876
+ const keptEnd = this.conversationHistory.slice(-keepLast);
3877
+ this.conversationHistory = [
3878
+ ...keptStart,
3879
+ { role: "user", content: `[... History Pruned: ${removalCount} intermediate messages were removed to save context ...]` },
3880
+ ...keptEnd
3881
+ ];
3882
+ }
3883
+ }
3884
+ }
3885
+ clearHistory() {
3886
+ this.conversationHistory = [];
3887
+ }
3888
+ /**
3889
+ * Get the last tool output content (for fallback when LLM generates no text)
3890
+ */
3891
+ getLastToolOutput() {
3892
+ const output = this.lastToolOutputContent;
3893
+ this.lastToolOutputContent = null;
3894
+ return output;
3895
+ }
3896
+ /**
3897
+ * Get a snapshot of the current conversation history for persistence
3898
+ */
3899
+ getHistorySnapshot() {
3900
+ return [...this.conversationHistory];
3901
+ }
3902
+ /**
3903
+ * Persist current history to DB
3904
+ */
3905
+ async persistHistory() {
3906
+ const { context: context2 } = await import("./context-CIWCGVB6.js");
3907
+ const { db: db2 } = await import("./database-M457QD3O.js");
3908
+ const sessionId = context2.get().session_id;
3909
+ if (!sessionId) return;
3910
+ try {
3911
+ db2.getDb().prepare("UPDATE sessions SET llm_history = ? WHERE id = ?").run(JSON.stringify(this.conversationHistory), sessionId);
3912
+ } catch (e) {
3913
+ console.error("Failed to persist LLM history:", e);
3914
+ }
3915
+ }
3916
+ /**
3917
+ * Restore conversation history from a saved session
3918
+ * Validates history to prevent API errors from orphaned tool_use blocks
3919
+ */
3920
+ restoreHistory(history2) {
3921
+ if (!Array.isArray(history2) || history2.length === 0) {
3922
+ return;
3923
+ }
3924
+ const validatedHistory = this.validateAndFixHistory(history2);
3925
+ if (!this.verifyHistoryIntegrity(validatedHistory)) {
3926
+ bus.emitAgent({
3927
+ type: "thought",
3928
+ content: `[Context] History validation failed - starting fresh to avoid API errors.`,
3929
+ hidden: false
3930
+ });
3931
+ this.conversationHistory = [];
3932
+ return;
3933
+ }
3934
+ this.conversationHistory = validatedHistory;
3935
+ bus.emitAgent({
3936
+ type: "thought",
3937
+ content: `[Context] Restored ${validatedHistory.length} messages from saved session.`,
3938
+ hidden: true
3939
+ });
3940
+ }
3941
+ /**
3942
+ * Verify history integrity - check that all tool_results have matching tool_uses
3943
+ */
3944
+ verifyHistoryIntegrity(history2) {
3945
+ for (let i = 0; i < history2.length; i++) {
3946
+ const msg = history2[i];
3947
+ const prevMsg = history2[i - 1];
3948
+ if (msg.role === "user" && Array.isArray(msg.content)) {
3949
+ const toolResults = msg.content.filter((b) => b.type === "tool_result");
3950
+ if (toolResults.length > 0) {
3951
+ if (!prevMsg || prevMsg.role !== "assistant" || !Array.isArray(prevMsg.content)) {
3952
+ return false;
3953
+ }
3954
+ const toolUseIds = new Set(
3955
+ prevMsg.content.filter((b) => b.type === "tool_use").map((b) => b.id)
3956
+ );
3957
+ for (const tr of toolResults) {
3958
+ if (!toolUseIds.has(tr.tool_use_id)) {
3959
+ return false;
3960
+ }
3961
+ }
3962
+ }
3963
+ }
3964
+ }
3965
+ return true;
3966
+ }
3967
+ /**
3968
+ * Validate conversation history and remove orphaned tool blocks
3969
+ * - Each tool_use must have a corresponding tool_result in the next message
3970
+ * - Each tool_result must have a corresponding tool_use in the previous message
3971
+ */
3972
+ validateAndFixHistory(history2) {
3973
+ const validated = [];
3974
+ for (let i = 0; i < history2.length; i++) {
3975
+ const msg = history2[i];
3976
+ const nextMsg = history2[i + 1];
3977
+ const prevMsg = validated[validated.length - 1];
3978
+ if (msg.role === "user" && Array.isArray(msg.content)) {
3979
+ const toolResultBlocks = msg.content.filter((b) => b.type === "tool_result");
3980
+ if (toolResultBlocks.length > 0) {
3981
+ if (!prevMsg || prevMsg.role !== "assistant" || !Array.isArray(prevMsg.content)) {
3982
+ const nonToolBlocks2 = msg.content.filter((b) => b.type !== "tool_result");
3983
+ if (nonToolBlocks2.length > 0) {
3984
+ validated.push({
3985
+ role: "user",
3986
+ content: nonToolBlocks2
3987
+ });
3988
+ }
3989
+ continue;
3990
+ }
3991
+ const toolUseIds = new Set(
3992
+ prevMsg.content.filter((b) => b.type === "tool_use").map((b) => b.id)
3993
+ );
3994
+ const validToolResults = toolResultBlocks.filter((tr) => toolUseIds.has(tr.tool_use_id));
3995
+ const nonToolBlocks = msg.content.filter((b) => b.type !== "tool_result");
3996
+ if (validToolResults.length !== toolResultBlocks.length) {
3997
+ if (validToolResults.length === 0 && nonToolBlocks.length > 0) {
3998
+ validated.push({
3999
+ role: "user",
4000
+ content: nonToolBlocks
4001
+ });
4002
+ continue;
4003
+ } else if (validToolResults.length > 0) {
4004
+ validated.push({
4005
+ role: "user",
4006
+ content: [...nonToolBlocks, ...validToolResults]
4007
+ });
4008
+ continue;
4009
+ }
4010
+ continue;
4011
+ }
4012
+ }
4013
+ }
4014
+ if (msg.role === "assistant" && Array.isArray(msg.content)) {
4015
+ const toolUseBlocks = msg.content.filter((b) => b.type === "tool_use");
4016
+ if (toolUseBlocks.length > 0) {
4017
+ if (!nextMsg || nextMsg.role !== "user" || !Array.isArray(nextMsg.content)) {
4018
+ const textBlocks = msg.content.filter((b) => b.type === "text");
4019
+ if (textBlocks.length > 0) {
4020
+ validated.push({
4021
+ role: "assistant",
4022
+ content: textBlocks.map((b) => b.text).join("\n")
4023
+ });
4024
+ }
4025
+ continue;
4026
+ }
4027
+ const toolResultIds = new Set(
4028
+ nextMsg.content.filter((b) => b.type === "tool_result").map((b) => b.tool_use_id)
4029
+ );
4030
+ const validToolUses = toolUseBlocks.filter((tu) => toolResultIds.has(tu.id));
4031
+ if (validToolUses.length !== toolUseBlocks.length) {
4032
+ const textBlocks = msg.content.filter((b) => b.type === "text");
4033
+ if (validToolUses.length === 0 && textBlocks.length > 0) {
4034
+ validated.push({
4035
+ role: "assistant",
4036
+ content: textBlocks.map((b) => b.text).join("\n")
4037
+ });
4038
+ continue;
4039
+ } else if (validToolUses.length > 0) {
4040
+ validated.push({
4041
+ role: "assistant",
4042
+ content: [...textBlocks, ...validToolUses]
4043
+ });
4044
+ continue;
4045
+ }
4046
+ continue;
4047
+ }
4048
+ }
4049
+ }
4050
+ validated.push(msg);
4051
+ }
4052
+ return validated;
4053
+ }
4054
+ };
4055
+ var llm = new LLMClient();
4056
+
4057
+ // src/core/session.ts
4058
+ var SessionManager = class {
4059
+ startTime = Date.now();
4060
+ /**
4061
+ * Save current session state
4062
+ * (V13: Mostly a no-op as state is continuously saved to SQLite)
4063
+ */
4064
+ async save() {
4065
+ const ctx = context.get();
4066
+ await context.save();
4067
+ return { sessionId: ctx.session_id, path: "sqlite" };
4068
+ }
4069
+ /**
4070
+ * List all saved sessions
4071
+ */
4072
+ async list() {
4073
+ try {
4074
+ const cfg = await config.load();
4075
+ const rows = db.getDb().prepare(`
4076
+ SELECT id, created_at, workspace
4077
+ FROM sessions
4078
+ ORDER BY created_at DESC
4079
+ `).all();
4080
+ const sessions = [];
4081
+ for (const row of rows) {
4082
+ const taskRow = db.getDb().prepare(`
4083
+ SELECT title FROM tasks
4084
+ WHERE session_id = ?
4085
+ ORDER BY created_at DESC LIMIT 1
4086
+ `).get(row.id);
4087
+ const modCount = db.getDb().prepare(`
4088
+ SELECT COUNT(*) as count FROM working_set WHERE session_id = ?
4089
+ `).get(row.id);
4090
+ sessions.push({
4091
+ id: row.id,
4092
+ savedAt: new Date(row.created_at).toISOString(),
4093
+ task: taskRow ? taskRow.title : null,
4094
+ filesModified: modCount ? modCount.count : 0,
4095
+ workspace: row.workspace || cfg.workspaceRoot
4096
+ });
4097
+ }
4098
+ return sessions;
4099
+ } catch {
4100
+ return [];
4101
+ }
4102
+ }
4103
+ /**
4104
+ * Delete a saved session and all related data
4105
+ */
4106
+ async delete(sessionId) {
4107
+ try {
4108
+ const transaction = db.getDb().transaction(() => {
4109
+ db.getDb().prepare("DELETE FROM subtasks WHERE task_id IN (SELECT id FROM tasks WHERE session_id = ?)").run(sessionId);
4110
+ db.getDb().prepare("DELETE FROM tasks WHERE session_id = ?").run(sessionId);
4111
+ db.getDb().prepare("DELETE FROM working_set WHERE session_id = ?").run(sessionId);
4112
+ db.getDb().prepare("DELETE FROM usage_stats WHERE session_id = ?").run(sessionId);
4113
+ db.getDb().prepare("DELETE FROM events WHERE session_id = ?").run(sessionId);
4114
+ db.getDb().prepare("DELETE FROM sessions WHERE id = ?").run(sessionId);
4115
+ });
4116
+ transaction();
4117
+ return true;
4118
+ } catch (e) {
4119
+ console.error("Failed to delete session:", e);
4120
+ return false;
4121
+ }
4122
+ }
4123
+ /**
4124
+ * Restore a saved session
4125
+ */
4126
+ async restore(sessionId) {
4127
+ const sessionRow = db.getDb().prepare("SELECT * FROM sessions WHERE id = ?").get(sessionId);
4128
+ if (!sessionRow) {
4129
+ return { success: false, error: `Session ${sessionId} not found` };
4130
+ }
4131
+ await context.load(sessionId);
4132
+ const { bus: bus2 } = await import("./bus-N5GVOUS7.js");
4133
+ const events = await history.load();
4134
+ const filteredEvents = events.filter(
4135
+ (e) => e.type !== "approval_request" && e.type !== "choice_request"
4136
+ );
4137
+ bus2.emitAgent({ type: "restore_history", events: filteredEvents });
4138
+ await tasks.init();
4139
+ const stats = db.getDb().prepare(`
4140
+ SELECT
4141
+ SUM(cost) as sessionCost,
4142
+ SUM(input_tokens) as sessionInputTokens,
4143
+ SUM(output_tokens) as sessionOutputTokens,
4144
+ MIN(timestamp) as firstLog,
4145
+ MAX(timestamp) as lastLog
4146
+ FROM usage_stats
4147
+ WHERE session_id = ?
4148
+ `).get(sessionId);
4149
+ const duration = stats && stats.lastLog && stats.firstLog ? stats.lastLog - stats.firstLog : 0;
4150
+ usage.restoreSessionState({
4151
+ cost: stats ? stats.sessionCost || 0 : 0,
4152
+ inputTokens: stats ? stats.sessionInputTokens || 0 : 0,
4153
+ outputTokens: stats ? stats.sessionOutputTokens || 0 : 0,
4154
+ cacheReadTokens: 0,
4155
+ // Not currently tracked in usage_stats table separate?
4156
+ cacheCreationTokens: 0,
4157
+ duration
4158
+ });
4159
+ this.resetStartTime();
4160
+ this.startTime = Date.now() - duration;
4161
+ const row = sessionRow;
4162
+ if (row.llm_history) {
4163
+ try {
4164
+ const history2 = JSON.parse(row.llm_history);
4165
+ llm.restoreHistory(history2);
4166
+ } catch (e) {
4167
+ console.error("Failed to parse LLM history:", e);
4168
+ }
4169
+ }
4170
+ return { success: true };
4171
+ }
4172
+ /**
4173
+ * Generate session summary for shutdown
4174
+ */
4175
+ async getSummary() {
4176
+ const ctx = context.get();
4177
+ const task = tasks.get();
4178
+ let tasksCompleted = 0;
4179
+ let tasksPending = 0;
4180
+ if (task) {
4181
+ for (const subtask of task.subtasks) {
4182
+ if (subtask.done) tasksCompleted++;
4183
+ else tasksPending++;
4184
+ }
4185
+ }
4186
+ return {
4187
+ sessionId: ctx.session_id,
4188
+ duration: Date.now() - this.startTime,
4189
+ filesRead: ctx.files_read.length,
4190
+ filesModified: ctx.files_modified.length,
4191
+ tasksCompleted,
4192
+ tasksPending,
4193
+ totalCost: usage.getSessionCost()
4194
+ };
4195
+ }
4196
+ resetStartTime() {
4197
+ this.startTime = Date.now();
4198
+ }
4199
+ getDuration() {
4200
+ return Date.now() - this.startTime;
4201
+ }
4202
+ formatDuration(ms) {
4203
+ const seconds = Math.floor(ms / 1e3);
4204
+ const minutes = Math.floor(seconds / 60);
4205
+ const hours = Math.floor(minutes / 60);
4206
+ if (hours > 0) return `${hours}h ${minutes % 60}m`;
4207
+ else if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
4208
+ else return `${seconds}s`;
4209
+ }
4210
+ };
4211
+ var session = new SessionManager();
4212
+
4213
+ export {
4214
+ usage,
4215
+ auditor,
4216
+ sandbox,
4217
+ tasks,
4218
+ undo,
4219
+ diffManager,
4220
+ mcp,
4221
+ listRegistry,
4222
+ tools,
4223
+ history,
4224
+ llm,
4225
+ session
4226
+ };