@bahulam/code 2.6.0

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 (273) hide show
  1. package/README.md +80 -0
  2. package/package.json +49 -0
  3. package/pulse/app/activity/page.tsx +190 -0
  4. package/pulse/app/api/activity/route.ts +138 -0
  5. package/pulse/app/api/benchmark/route.ts +113 -0
  6. package/pulse/app/api/benchmarks/route.ts +195 -0
  7. package/pulse/app/api/costs/route.ts +88 -0
  8. package/pulse/app/api/export/route.ts +77 -0
  9. package/pulse/app/api/history/route.ts +11 -0
  10. package/pulse/app/api/import/route.ts +31 -0
  11. package/pulse/app/api/memory/route.ts +50 -0
  12. package/pulse/app/api/plans/route.ts +9 -0
  13. package/pulse/app/api/projects/[slug]/route.ts +96 -0
  14. package/pulse/app/api/projects/route.ts +121 -0
  15. package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
  16. package/pulse/app/api/sessions/[id]/route.ts +31 -0
  17. package/pulse/app/api/sessions/route.ts +112 -0
  18. package/pulse/app/api/settings/route.ts +14 -0
  19. package/pulse/app/api/stats/route.ts +143 -0
  20. package/pulse/app/api/todos/route.ts +9 -0
  21. package/pulse/app/api/tools/route.ts +160 -0
  22. package/pulse/app/benchmarks/page.tsx +224 -0
  23. package/pulse/app/costs/page.tsx +179 -0
  24. package/pulse/app/export/page.tsx +465 -0
  25. package/pulse/app/favicon.ico +0 -0
  26. package/pulse/app/globals.css +263 -0
  27. package/pulse/app/help/page.tsx +143 -0
  28. package/pulse/app/history/page.tsx +157 -0
  29. package/pulse/app/layout.tsx +46 -0
  30. package/pulse/app/memory/page.tsx +365 -0
  31. package/pulse/app/overview-client.tsx +393 -0
  32. package/pulse/app/page.tsx +14 -0
  33. package/pulse/app/plans/page.tsx +308 -0
  34. package/pulse/app/projects/[slug]/page.tsx +390 -0
  35. package/pulse/app/projects/page.tsx +110 -0
  36. package/pulse/app/sessions/[id]/page.tsx +243 -0
  37. package/pulse/app/sessions/page.tsx +39 -0
  38. package/pulse/app/settings/page.tsx +188 -0
  39. package/pulse/app/todos/page.tsx +211 -0
  40. package/pulse/app/tools/page.tsx +249 -0
  41. package/pulse/cli.js +164 -0
  42. package/pulse/components/activity/day-of-week-chart.tsx +35 -0
  43. package/pulse/components/activity/streak-card.tsx +36 -0
  44. package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
  45. package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
  46. package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
  47. package/pulse/components/costs/model-token-table.tsx +60 -0
  48. package/pulse/components/global-search.tsx +193 -0
  49. package/pulse/components/keyboard-nav-provider.tsx +23 -0
  50. package/pulse/components/layout/bottom-nav.tsx +53 -0
  51. package/pulse/components/layout/client-layout.tsx +31 -0
  52. package/pulse/components/layout/sidebar-context.tsx +50 -0
  53. package/pulse/components/layout/sidebar.tsx +183 -0
  54. package/pulse/components/layout/top-bar.tsx +121 -0
  55. package/pulse/components/overview/activity-heatmap.tsx +107 -0
  56. package/pulse/components/overview/conversation-table.tsx +148 -0
  57. package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
  58. package/pulse/components/overview/peak-hours-chart.tsx +87 -0
  59. package/pulse/components/overview/project-activity-donut.tsx +96 -0
  60. package/pulse/components/overview/stat-card.tsx +102 -0
  61. package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
  62. package/pulse/components/projects/project-card.tsx +175 -0
  63. package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
  64. package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
  65. package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
  66. package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
  67. package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
  68. package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
  69. package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
  70. package/pulse/components/sessions/session-badges.tsx +49 -0
  71. package/pulse/components/sessions/session-table.tsx +299 -0
  72. package/pulse/components/theme-provider.tsx +44 -0
  73. package/pulse/components/tools/feature-adoption-table.tsx +58 -0
  74. package/pulse/components/tools/mcp-server-panel.tsx +45 -0
  75. package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
  76. package/pulse/components/tools/version-history-table.tsx +32 -0
  77. package/pulse/components/ui/alert.tsx +66 -0
  78. package/pulse/components/ui/badge.tsx +48 -0
  79. package/pulse/components/ui/breadcrumb.tsx +109 -0
  80. package/pulse/components/ui/button.tsx +64 -0
  81. package/pulse/components/ui/calendar.tsx +220 -0
  82. package/pulse/components/ui/card.tsx +92 -0
  83. package/pulse/components/ui/command.tsx +158 -0
  84. package/pulse/components/ui/dialog.tsx +158 -0
  85. package/pulse/components/ui/input.tsx +21 -0
  86. package/pulse/components/ui/popover.tsx +89 -0
  87. package/pulse/components/ui/progress.tsx +31 -0
  88. package/pulse/components/ui/select.tsx +190 -0
  89. package/pulse/components/ui/separator.tsx +28 -0
  90. package/pulse/components/ui/sheet.tsx +143 -0
  91. package/pulse/components/ui/skeleton.tsx +13 -0
  92. package/pulse/components/ui/table.tsx +116 -0
  93. package/pulse/components/ui/tabs.tsx +91 -0
  94. package/pulse/components/ui/tooltip.tsx +57 -0
  95. package/pulse/components/use-global-keyboard-nav.ts +79 -0
  96. package/pulse/components.json +23 -0
  97. package/pulse/eslint.config.mjs +18 -0
  98. package/pulse/lib/bahulam-paths.ts +23 -0
  99. package/pulse/lib/claude-reader.ts +592 -0
  100. package/pulse/lib/decode.ts +129 -0
  101. package/pulse/lib/pricing.ts +102 -0
  102. package/pulse/lib/replay-parser.ts +165 -0
  103. package/pulse/lib/tool-categories.ts +127 -0
  104. package/pulse/lib/utils.ts +6 -0
  105. package/pulse/next-env.d.ts +6 -0
  106. package/pulse/next.config.ts +16 -0
  107. package/pulse/package.json +45 -0
  108. package/pulse/postcss.config.mjs +7 -0
  109. package/pulse/public/activity.png +0 -0
  110. package/pulse/public/cc-lens.png +0 -0
  111. package/pulse/public/command-k.png +0 -0
  112. package/pulse/public/costs.png +0 -0
  113. package/pulse/public/dashboard-dark.png +0 -0
  114. package/pulse/public/dashboard-white.png +0 -0
  115. package/pulse/public/export.png +0 -0
  116. package/pulse/public/file.svg +1 -0
  117. package/pulse/public/globe.svg +1 -0
  118. package/pulse/public/next.svg +1 -0
  119. package/pulse/public/projects.png +0 -0
  120. package/pulse/public/session-chat.png +0 -0
  121. package/pulse/public/todos.png +0 -0
  122. package/pulse/public/tools.png +0 -0
  123. package/pulse/public/vercel.svg +1 -0
  124. package/pulse/public/window.svg +1 -0
  125. package/pulse/tsconfig.json +34 -0
  126. package/pulse/types/claude.ts +294 -0
  127. package/src/agents/loader.mjs +94 -0
  128. package/src/agents/multi_workflow_loader.mjs +330 -0
  129. package/src/agents/parser.mjs +205 -0
  130. package/src/agents/scaffold.mjs +222 -0
  131. package/src/agents/teams.mjs +123 -0
  132. package/src/agents/workflow_loader.mjs +122 -0
  133. package/src/agents/workflow_scaffold.mjs +249 -0
  134. package/src/auth/oauth.mjs +220 -0
  135. package/src/auth/tarang-auth.mjs +306 -0
  136. package/src/commands/agent.mjs +220 -0
  137. package/src/commands/workflow.mjs +581 -0
  138. package/src/config/cli-args.mjs +200 -0
  139. package/src/config/env.mjs +263 -0
  140. package/src/config/hook-runner.mjs +100 -0
  141. package/src/config/memory-loader.mjs +32 -0
  142. package/src/config/settings-loader.mjs +45 -0
  143. package/src/config/settings.mjs +132 -0
  144. package/src/context/ast-parser.mjs +298 -0
  145. package/src/context/bm25.mjs +85 -0
  146. package/src/context/retriever.mjs +308 -0
  147. package/src/context/skeleton.mjs +134 -0
  148. package/src/context/symbol-indexer.mjs +375 -0
  149. package/src/core/agent-history.mjs +111 -0
  150. package/src/core/agent-loop.mjs +486 -0
  151. package/src/core/approval-log.mjs +104 -0
  152. package/src/core/approval.mjs +476 -0
  153. package/src/core/attachments.mjs +380 -0
  154. package/src/core/backend-url.mjs +55 -0
  155. package/src/core/cache-control.mjs +92 -0
  156. package/src/core/cache.mjs +105 -0
  157. package/src/core/callback-client.mjs +180 -0
  158. package/src/core/checkpoints.mjs +142 -0
  159. package/src/core/compact-history.mjs +127 -0
  160. package/src/core/context-envelope.mjs +54 -0
  161. package/src/core/context-manager.mjs +198 -0
  162. package/src/core/error-guidance.mjs +311 -0
  163. package/src/core/file-diff.mjs +217 -0
  164. package/src/core/headless.mjs +448 -0
  165. package/src/core/hooks-manager.mjs +87 -0
  166. package/src/core/jsonl-writer.mjs +449 -0
  167. package/src/core/local-agent.mjs +537 -0
  168. package/src/core/local-store.mjs +836 -0
  169. package/src/core/mode-selector.mjs +51 -0
  170. package/src/core/output-filter.mjs +177 -0
  171. package/src/core/paths.mjs +190 -0
  172. package/src/core/policy-resolver.mjs +156 -0
  173. package/src/core/pricing.mjs +336 -0
  174. package/src/core/project-artifacts.mjs +39 -0
  175. package/src/core/project-context-loader.mjs +139 -0
  176. package/src/core/providers.mjs +219 -0
  177. package/src/core/rate-limit-display.mjs +121 -0
  178. package/src/core/rate-limiter.mjs +119 -0
  179. package/src/core/resume-mode.mjs +192 -0
  180. package/src/core/risk-tier.mjs +337 -0
  181. package/src/core/safety.mjs +203 -0
  182. package/src/core/scheduler.mjs +173 -0
  183. package/src/core/session-manager.mjs +360 -0
  184. package/src/core/session.mjs +143 -0
  185. package/src/core/settings-sync.mjs +85 -0
  186. package/src/core/stagnation.mjs +57 -0
  187. package/src/core/stream-client.mjs +829 -0
  188. package/src/core/streaming.mjs +182 -0
  189. package/src/core/system-prompt.mjs +140 -0
  190. package/src/core/tasks.mjs +196 -0
  191. package/src/core/tool-executor.mjs +1950 -0
  192. package/src/core/trust.mjs +158 -0
  193. package/src/core/work-scope.mjs +248 -0
  194. package/src/hooks/engine.mjs +162 -0
  195. package/src/index.mjs +426 -0
  196. package/src/mcp/client.mjs +253 -0
  197. package/src/mcp/transport-shttp.mjs +130 -0
  198. package/src/mcp/transport-sse.mjs +131 -0
  199. package/src/mcp/transport-ws.mjs +134 -0
  200. package/src/onboarding/preflight.mjs +360 -0
  201. package/src/permissions/checker.mjs +57 -0
  202. package/src/permissions/command-classifier.mjs +652 -0
  203. package/src/permissions/injection-check.mjs +60 -0
  204. package/src/permissions/path-check.mjs +102 -0
  205. package/src/permissions/prompt.mjs +73 -0
  206. package/src/permissions/sandbox.mjs +112 -0
  207. package/src/plugins/loader.mjs +138 -0
  208. package/src/skills/installer.mjs +188 -0
  209. package/src/skills/loader.mjs +252 -0
  210. package/src/skills/runner.mjs +55 -0
  211. package/src/state/orbit.mjs +263 -0
  212. package/src/state/verbosity.mjs +99 -0
  213. package/src/telemetry/index.mjs +96 -0
  214. package/src/terminal/agents.mjs +177 -0
  215. package/src/terminal/analytics.mjs +292 -0
  216. package/src/terminal/ansi.mjs +695 -0
  217. package/src/terminal/init.mjs +145 -0
  218. package/src/terminal/main.mjs +269 -0
  219. package/src/terminal/repl-explore.mjs +35 -0
  220. package/src/terminal/repl-format.mjs +257 -0
  221. package/src/terminal/repl-render.mjs +561 -0
  222. package/src/terminal/repl-resume.mjs +625 -0
  223. package/src/terminal/repl-state.mjs +103 -0
  224. package/src/terminal/repl-utils.mjs +34 -0
  225. package/src/terminal/repl.mjs +3832 -0
  226. package/src/terminal/skills.mjs +54 -0
  227. package/src/terminal/tool-display.mjs +240 -0
  228. package/src/tools/agent.mjs +137 -0
  229. package/src/tools/ask-user.mjs +61 -0
  230. package/src/tools/bash.mjs +231 -0
  231. package/src/tools/cron-create.mjs +120 -0
  232. package/src/tools/cron-delete.mjs +49 -0
  233. package/src/tools/cron-list.mjs +37 -0
  234. package/src/tools/edit.mjs +82 -0
  235. package/src/tools/enter-worktree.mjs +69 -0
  236. package/src/tools/exit-worktree.mjs +57 -0
  237. package/src/tools/glob.mjs +117 -0
  238. package/src/tools/grep.mjs +129 -0
  239. package/src/tools/lint.mjs +71 -0
  240. package/src/tools/ls.mjs +58 -0
  241. package/src/tools/lsp.mjs +115 -0
  242. package/src/tools/multi-edit.mjs +94 -0
  243. package/src/tools/notebook-edit.mjs +96 -0
  244. package/src/tools/project-overview.mjs +641 -0
  245. package/src/tools/read-mcp-resource.mjs +57 -0
  246. package/src/tools/read.mjs +138 -0
  247. package/src/tools/registry.mjs +116 -0
  248. package/src/tools/remote-trigger.mjs +84 -0
  249. package/src/tools/send-message.mjs +64 -0
  250. package/src/tools/skill.mjs +52 -0
  251. package/src/tools/test-runner.mjs +49 -0
  252. package/src/tools/todo-write.mjs +68 -0
  253. package/src/tools/tool-search.mjs +77 -0
  254. package/src/tools/web-fetch.mjs +65 -0
  255. package/src/tools/web-search.mjs +89 -0
  256. package/src/tools/write.mjs +55 -0
  257. package/src/ui/approval.mjs +263 -0
  258. package/src/ui/banner.mjs +235 -0
  259. package/src/ui/commands.mjs +537 -0
  260. package/src/ui/formatter.mjs +409 -0
  261. package/src/ui/icons.mjs +164 -0
  262. package/src/ui/input-dock.mjs +444 -0
  263. package/src/ui/markdown.mjs +278 -0
  264. package/src/ui/mission-report.mjs +296 -0
  265. package/src/ui/palette.mjs +189 -0
  266. package/src/ui/slash-commands.mjs +245 -0
  267. package/src/ui/spinner.mjs +116 -0
  268. package/src/ui/sub-agent.mjs +152 -0
  269. package/src/ui/term.mjs +159 -0
  270. package/src/ui/text-layout.mjs +127 -0
  271. package/src/ui/tool-card.mjs +463 -0
  272. package/src/ui/tool-details.mjs +312 -0
  273. package/src/ui/transcript-block.mjs +21 -0
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Symbol Indexer — AST-based code search using tree-sitter.
3
+ *
4
+ * Parses source files into symbols (functions, classes, methods) with
5
+ * signatures and line numbers. Indexes symbols in BM25 for search.
6
+ *
7
+ * Memory efficient: stores symbol signatures (~50 chars) not file chunks
8
+ * (~2000 chars). One tree-sitter parse per file, O(n) on file size.
9
+ *
10
+ * Usage:
11
+ * const indexer = new SymbolIndexer();
12
+ * await indexer.init(); // load WASM grammars once
13
+ * indexer.indexFile('/path/to/file.py', content);
14
+ * const results = indexer.search('find_ordering_name');
15
+ */
16
+
17
+ import * as fs from 'node:fs';
18
+ import * as path from 'node:path';
19
+ import { BM25Index } from './bm25.mjs';
20
+
21
+ const GRAMMAR_DIR = new URL('./grammars/', import.meta.url).pathname;
22
+
23
+ const LANG_MAP = {
24
+ '.py': 'python',
25
+ '.js': 'javascript',
26
+ '.mjs': 'javascript',
27
+ '.jsx': 'javascript',
28
+ '.ts': 'typescript',
29
+ '.tsx': 'typescript',
30
+ };
31
+
32
+ /**
33
+ * @typedef {Object} Symbol
34
+ * @property {string} name
35
+ * @property {string} kind - 'function' | 'class' | 'method'
36
+ * @property {string} file - relative path
37
+ * @property {number} line
38
+ * @property {number} endLine
39
+ * @property {string} signature - e.g., "def find_ordering_name(self, name, opts)"
40
+ * @property {string} [parent] - parent class name if method
41
+ * @property {string} [docstring] - first line of docstring
42
+ */
43
+
44
+ export class SymbolIndexer {
45
+ constructor() {
46
+ this._Parser = null;
47
+ this._languages = {}; // ext → Language
48
+ this._symbols = []; // all extracted symbols
49
+ this._symbolMap = new Map(); // id → Symbol
50
+ this._bm25 = new BM25Index();
51
+ this._initialized = false;
52
+ }
53
+
54
+ /**
55
+ * Load tree-sitter WASM runtime + grammars. Call once per session.
56
+ * Lazy — only loads grammars for languages actually encountered.
57
+ */
58
+ async init() {
59
+ if (this._initialized) return;
60
+ try {
61
+ const TreeSitter = (await import('web-tree-sitter')).default;
62
+ await TreeSitter.init();
63
+ this._Parser = new TreeSitter();
64
+ this._TreeSitter = TreeSitter;
65
+ this._initialized = true;
66
+ } catch (e) {
67
+ // Fallback: tree-sitter not available, use regex parser
68
+ this._initialized = false;
69
+ }
70
+ }
71
+
72
+ async _getLanguage(ext) {
73
+ if (this._languages[ext]) return this._languages[ext];
74
+ const langName = LANG_MAP[ext];
75
+ if (!langName || !this._TreeSitter) return null;
76
+
77
+ // Try bundled WASM from tree-sitter-wasms package
78
+ const wasmPaths = [
79
+ path.join(GRAMMAR_DIR, `tree-sitter-${langName}.wasm`),
80
+ ];
81
+
82
+ // Also check node_modules
83
+ try {
84
+ const modPath = new URL(`../../node_modules/tree-sitter-wasms/out/tree-sitter-${langName}.wasm`, import.meta.url).pathname;
85
+ wasmPaths.push(modPath);
86
+ } catch { /* ignore */ }
87
+
88
+ for (const p of wasmPaths) {
89
+ try {
90
+ if (fs.existsSync(p)) {
91
+ const lang = await this._TreeSitter.Language.load(p);
92
+ this._languages[ext] = lang;
93
+ return lang;
94
+ }
95
+ } catch { /* try next */ }
96
+ }
97
+ return null;
98
+ }
99
+
100
+ /**
101
+ * Index a single file. Extracts symbols and adds to BM25.
102
+ * @param {string} relPath - relative path (used as ID)
103
+ * @param {string} content - file content
104
+ */
105
+ async indexFile(relPath, content) {
106
+ const ext = path.extname(relPath).toLowerCase();
107
+ let symbols;
108
+
109
+ const lang = await this._getLanguage(ext);
110
+ if (lang && this._Parser) {
111
+ this._Parser.setLanguage(lang);
112
+ const tree = this._Parser.parse(content);
113
+ symbols = this._extractSymbols(tree.rootNode, relPath, ext);
114
+ tree.delete();
115
+ } else {
116
+ symbols = this._regexExtract(relPath, content, ext);
117
+ }
118
+
119
+ for (const sym of symbols) {
120
+ const id = `${sym.file}:${sym.line}:${sym.name}`;
121
+ this._symbols.push(sym);
122
+ this._symbolMap.set(id, sym);
123
+
124
+ // BM25 document: name + signature + parent + docstring
125
+ const text = [
126
+ sym.name,
127
+ sym.signature || '',
128
+ sym.parent ? `${sym.parent}.${sym.name}` : '',
129
+ sym.docstring || '',
130
+ sym.file,
131
+ ].join(' ');
132
+ this._bm25.addDocument(id, text);
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Search for symbols matching a query.
138
+ * @param {string} query
139
+ * @param {number} [topK=10]
140
+ * @returns {Array<{symbol: Symbol, score: number}>}
141
+ */
142
+ search(query, topK = 10) {
143
+ const results = this._bm25.search(query, topK);
144
+ return results.map(r => ({
145
+ symbol: this._symbolMap.get(r.id),
146
+ score: r.score,
147
+ id: r.id,
148
+ })).filter(r => r.symbol);
149
+ }
150
+
151
+ /**
152
+ * Format search results for the agent.
153
+ */
154
+ formatResults(results) {
155
+ if (!results.length) return '';
156
+ return results.map(r => {
157
+ const s = r.symbol;
158
+ const parent = s.parent ? `${s.parent}.` : '';
159
+ const doc = s.docstring ? ` "${s.docstring}"` : '';
160
+ return `${s.file}:${s.line} ${parent}${s.signature || s.name}${doc}`;
161
+ }).join('\n');
162
+ }
163
+
164
+ get symbolCount() { return this._symbols.length; }
165
+
166
+ // ── Tree-sitter extraction ──
167
+
168
+ _extractSymbols(node, file, ext) {
169
+ const symbols = [];
170
+ const langName = LANG_MAP[ext];
171
+
172
+ if (langName === 'python') {
173
+ this._walkPython(node, file, symbols, null);
174
+ } else if (langName === 'javascript' || langName === 'typescript') {
175
+ this._walkJS(node, file, symbols, null);
176
+ }
177
+ return symbols;
178
+ }
179
+
180
+ _walkPython(node, file, symbols, parentClass) {
181
+ for (let i = 0; i < node.childCount; i++) {
182
+ const child = node.child(i);
183
+ const type = child.type;
184
+
185
+ if (type === 'class_definition') {
186
+ const nameNode = child.childForFieldName('name');
187
+ const name = nameNode?.text || '';
188
+ const bases = child.childForFieldName('superclasses')?.text || '';
189
+ symbols.push({
190
+ name, kind: 'class', file,
191
+ line: child.startPosition.row + 1,
192
+ endLine: child.endPosition.row + 1,
193
+ signature: `class ${name}${bases ? `(${bases})` : ''}`,
194
+ docstring: this._pyDocstring(child),
195
+ });
196
+ // Recurse into class body for methods
197
+ const body = child.childForFieldName('body');
198
+ if (body) this._walkPython(body, file, symbols, name);
199
+ }
200
+
201
+ else if (type === 'function_definition') {
202
+ const nameNode = child.childForFieldName('name');
203
+ const name = nameNode?.text || '';
204
+ const params = child.childForFieldName('parameters')?.text || '()';
205
+ const returnType = child.childForFieldName('return_type')?.text || '';
206
+ const sig = `def ${name}${params}${returnType ? ' -> ' + returnType : ''}`;
207
+ symbols.push({
208
+ name,
209
+ kind: parentClass ? 'method' : 'function',
210
+ file,
211
+ line: child.startPosition.row + 1,
212
+ endLine: child.endPosition.row + 1,
213
+ signature: sig,
214
+ parent: parentClass || undefined,
215
+ docstring: this._pyDocstring(child),
216
+ });
217
+ }
218
+
219
+ else if (type === 'decorated_definition') {
220
+ // Unwrap decorator to get the actual definition
221
+ for (let j = 0; j < child.childCount; j++) {
222
+ const inner = child.child(j);
223
+ if (inner.type === 'function_definition' || inner.type === 'class_definition') {
224
+ this._walkPython(child, file, symbols, parentClass);
225
+ break;
226
+ }
227
+ }
228
+ }
229
+
230
+ else {
231
+ // Recurse for module-level statements
232
+ if (!parentClass && child.childCount > 0) {
233
+ this._walkPython(child, file, symbols, parentClass);
234
+ }
235
+ }
236
+ }
237
+ }
238
+
239
+ _pyDocstring(defNode) {
240
+ const body = defNode.childForFieldName('body');
241
+ if (!body || body.childCount === 0) return '';
242
+ const first = body.child(0);
243
+ if (first?.type === 'expression_statement') {
244
+ const expr = first.child(0);
245
+ if (expr?.type === 'string' || expr?.type === 'concatenated_string') {
246
+ const raw = expr.text;
247
+ // Extract first line of docstring
248
+ const content = raw.replace(/^['"`]{1,3}/, '').replace(/['"`]{1,3}$/, '');
249
+ const firstLine = content.split('\n')[0].trim();
250
+ return firstLine.slice(0, 120);
251
+ }
252
+ }
253
+ return '';
254
+ }
255
+
256
+ _walkJS(node, file, symbols, parentClass) {
257
+ for (let i = 0; i < node.childCount; i++) {
258
+ const child = node.child(i);
259
+ const type = child.type;
260
+
261
+ if (type === 'class_declaration' || type === 'class') {
262
+ const nameNode = child.childForFieldName('name');
263
+ const name = nameNode?.text || '';
264
+ symbols.push({
265
+ name, kind: 'class', file,
266
+ line: child.startPosition.row + 1,
267
+ endLine: child.endPosition.row + 1,
268
+ signature: `class ${name}`,
269
+ });
270
+ const body = child.childForFieldName('body');
271
+ if (body) this._walkJS(body, file, symbols, name);
272
+ }
273
+
274
+ else if (type === 'function_declaration' || type === 'method_definition') {
275
+ const nameNode = child.childForFieldName('name');
276
+ const name = nameNode?.text || '';
277
+ const params = child.childForFieldName('parameters')?.text || '()';
278
+ symbols.push({
279
+ name,
280
+ kind: parentClass ? 'method' : 'function',
281
+ file,
282
+ line: child.startPosition.row + 1,
283
+ endLine: child.endPosition.row + 1,
284
+ signature: `${parentClass ? '' : 'function '}${name}${params}`,
285
+ parent: parentClass || undefined,
286
+ });
287
+ }
288
+
289
+ else if (type === 'export_statement' || type === 'lexical_declaration') {
290
+ this._walkJS(child, file, symbols, parentClass);
291
+ }
292
+
293
+ else if (child.childCount > 0 && !parentClass) {
294
+ this._walkJS(child, file, symbols, parentClass);
295
+ }
296
+ }
297
+ }
298
+
299
+ // ── Regex fallback (no tree-sitter) ──
300
+
301
+ _regexExtract(file, content, ext) {
302
+ const symbols = [];
303
+ const lines = content.split('\n');
304
+ let currentClass = null;
305
+
306
+ for (let i = 0; i < lines.length; i++) {
307
+ const line = lines[i];
308
+ const trimmed = line.trim();
309
+ const lineNum = i + 1;
310
+ const indent = line.length - line.trimStart().length;
311
+
312
+ // Python
313
+ if (ext === '.py') {
314
+ const classMatch = trimmed.match(/^class\s+(\w+)(?:\(([^)]*)\))?/);
315
+ if (classMatch) {
316
+ currentClass = classMatch[1];
317
+ symbols.push({
318
+ name: currentClass, kind: 'class', file, line: lineNum,
319
+ signature: `class ${currentClass}${classMatch[2] ? `(${classMatch[2]})` : ''}`,
320
+ });
321
+ continue;
322
+ }
323
+ const fnMatch = trimmed.match(/^(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)/);
324
+ if (fnMatch) {
325
+ const isMethod = indent >= 4 && currentClass;
326
+ symbols.push({
327
+ name: fnMatch[1],
328
+ kind: isMethod ? 'method' : 'function',
329
+ file, line: lineNum,
330
+ signature: `def ${fnMatch[1]}(${fnMatch[2]})`,
331
+ parent: isMethod ? currentClass : undefined,
332
+ });
333
+ continue;
334
+ }
335
+ if (indent === 0 && !trimmed.startsWith('#') && trimmed) {
336
+ currentClass = null;
337
+ }
338
+ }
339
+
340
+ // JS/TS
341
+ if (['.js', '.mjs', '.ts', '.tsx', '.jsx'].includes(ext)) {
342
+ const fnMatch = trimmed.match(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/);
343
+ if (fnMatch) {
344
+ symbols.push({ name: fnMatch[1], kind: 'function', file, line: lineNum, signature: `function ${fnMatch[1]}(${fnMatch[2]})` });
345
+ }
346
+ const classMatch = trimmed.match(/^(?:export\s+)?class\s+(\w+)/);
347
+ if (classMatch) {
348
+ symbols.push({ name: classMatch[1], kind: 'class', file, line: lineNum, signature: `class ${classMatch[1]}` });
349
+ }
350
+ }
351
+ }
352
+ return symbols;
353
+ }
354
+
355
+ // ── Serialization ──
356
+
357
+ toJSON() {
358
+ return {
359
+ symbols: this._symbols,
360
+ bm25: this._bm25.toJSON(),
361
+ };
362
+ }
363
+
364
+ static fromJSON(data) {
365
+ const indexer = new SymbolIndexer();
366
+ indexer._initialized = true; // don't need tree-sitter for search
367
+ indexer._symbols = data.symbols || [];
368
+ indexer._bm25 = BM25Index.fromJSON(data.bm25);
369
+ for (const sym of indexer._symbols) {
370
+ const id = `${sym.file}:${sym.line}:${sym.name}`;
371
+ indexer._symbolMap.set(id, sym);
372
+ }
373
+ return indexer;
374
+ }
375
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Structured live history for backend continuity.
3
+ *
4
+ * The terminal display history is intentionally human-friendly. This builder
5
+ * keeps the backend payload provider-shaped: assistant text/tool_use blocks,
6
+ * followed by user tool_result blocks, appended in the order they happened.
7
+ */
8
+
9
+ const DEFAULT_MAX_TOOL_RESULT_CHARS = 200_000;
10
+
11
+ function asString(value) {
12
+ if (value == null) return '';
13
+ if (typeof value === 'string') return value;
14
+ try {
15
+ return JSON.stringify(value);
16
+ } catch {
17
+ return String(value);
18
+ }
19
+ }
20
+
21
+ function maybeTruncate(text, maxChars) {
22
+ const value = asString(text);
23
+ if (!maxChars || value.length <= maxChars) return value;
24
+ return `${value.slice(0, maxChars)}\n\n[Bahulam Code truncated this tool result from ${value.length} to ${maxChars} characters for live session continuity.]`;
25
+ }
26
+
27
+ function mergeTextBlock(blocks, text) {
28
+ if (!text) return;
29
+ const last = blocks[blocks.length - 1];
30
+ if (last?.type === 'text') {
31
+ last.text += text;
32
+ } else {
33
+ blocks.push({ type: 'text', text });
34
+ }
35
+ }
36
+
37
+ export class AgentHistoryTurnBuilder {
38
+ constructor({ maxToolResultChars = DEFAULT_MAX_TOOL_RESULT_CHARS } = {}) {
39
+ this.maxToolResultChars = maxToolResultChars;
40
+ this.messages = [];
41
+ this.assistantBlocks = [];
42
+ this.toolUseIds = new Set();
43
+ this.toolResultIds = new Set();
44
+ }
45
+
46
+ addAssistantText(text) {
47
+ mergeTextBlock(this.assistantBlocks, asString(text));
48
+ }
49
+
50
+ addToolUse(data = {}) {
51
+ if (data.internal || data.sub_agent) return false;
52
+ const id = data.call_id || data.request_id || data.id;
53
+ const name = data.tool || data.name;
54
+ if (!id || !name) return false;
55
+ this.assistantBlocks.push({
56
+ type: 'tool_use',
57
+ id,
58
+ name,
59
+ input: data.args || data.input || {},
60
+ });
61
+ this.toolUseIds.add(id);
62
+ return true;
63
+ }
64
+
65
+ addToolResult(data = {}) {
66
+ if (data.internal || data.sub_agent) return false;
67
+ const id = data.call_id || data._callId || data.request_id || data.id || data.tool_use_id;
68
+ if (this.toolResultIds.has(id)) return false;
69
+ if (!id) return false;
70
+ if (!this.toolUseIds.has(id)) {
71
+ const name = data.tool || data.name;
72
+ if (!name) return false;
73
+ this.assistantBlocks.push({
74
+ type: 'tool_use',
75
+ id,
76
+ name,
77
+ input: data.args || data.input || {},
78
+ });
79
+ this.toolUseIds.add(id);
80
+ }
81
+
82
+ this.flushAssistant();
83
+ this.messages.push({
84
+ role: 'user',
85
+ content: [{
86
+ type: 'tool_result',
87
+ tool_use_id: id,
88
+ content: maybeTruncate(data.llm_content ?? data.output ?? data.result ?? data.message ?? '', this.maxToolResultChars),
89
+ ...(data.success === false || data.is_error ? { is_error: true } : {}),
90
+ }],
91
+ });
92
+ this.toolResultIds.add(id);
93
+ return true;
94
+ }
95
+
96
+ flushAssistant() {
97
+ const blocks = this.assistantBlocks.filter(block => {
98
+ if (block.type === 'text') return Boolean(block.text);
99
+ return true;
100
+ });
101
+ if (!blocks.length) return false;
102
+ this.messages.push({ role: 'assistant', content: blocks });
103
+ this.assistantBlocks = [];
104
+ return true;
105
+ }
106
+
107
+ finish() {
108
+ this.flushAssistant();
109
+ return this.messages;
110
+ }
111
+ }