lonny-agent 0.2.4 → 0.2.6

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 (102) hide show
  1. package/README.md +326 -326
  2. package/dist/agent/llm.d.ts +4 -0
  3. package/dist/agent/llm.d.ts.map +1 -1
  4. package/dist/agent/prompt-builder.d.ts.map +1 -1
  5. package/dist/agent/prompt-builder.js +13 -9
  6. package/dist/agent/prompt-builder.js.map +1 -1
  7. package/dist/agent/providers/google.d.ts.map +1 -1
  8. package/dist/agent/providers/google.js.map +1 -1
  9. package/dist/agent/providers/openai.d.ts.map +1 -1
  10. package/dist/agent/providers/openai.js +43 -3
  11. package/dist/agent/providers/openai.js.map +1 -1
  12. package/dist/agent/session.d.ts +4 -0
  13. package/dist/agent/session.d.ts.map +1 -1
  14. package/dist/agent/session.js +45 -7
  15. package/dist/agent/session.js.map +1 -1
  16. package/dist/config/index.d.ts.map +1 -1
  17. package/dist/config/index.js +7 -1
  18. package/dist/config/index.js.map +1 -1
  19. package/dist/tools/__tests__/bash.test.js +1 -1
  20. package/dist/tools/__tests__/bash.test.js.map +1 -1
  21. package/dist/tools/__tests__/edit.test.js +875 -371
  22. package/dist/tools/__tests__/edit.test.js.map +1 -1
  23. package/dist/tools/__tests__/grep.test.js +32 -0
  24. package/dist/tools/__tests__/grep.test.js.map +1 -1
  25. package/dist/tools/__tests__/sed.test.d.ts +2 -0
  26. package/dist/tools/__tests__/sed.test.d.ts.map +1 -0
  27. package/dist/tools/__tests__/sed.test.js +228 -0
  28. package/dist/tools/__tests__/sed.test.js.map +1 -0
  29. package/dist/tools/bash.d.ts.map +1 -1
  30. package/dist/tools/bash.js +83 -10
  31. package/dist/tools/bash.js.map +1 -1
  32. package/dist/tools/edit.d.ts +50 -9
  33. package/dist/tools/edit.d.ts.map +1 -1
  34. package/dist/tools/edit.js +328 -158
  35. package/dist/tools/edit.js.map +1 -1
  36. package/dist/tools/grep.d.ts.map +1 -1
  37. package/dist/tools/grep.js +54 -30
  38. package/dist/tools/grep.js.map +1 -1
  39. package/dist/tools/registry.d.ts +0 -6
  40. package/dist/tools/registry.d.ts.map +1 -1
  41. package/dist/tools/registry.js +2 -29
  42. package/dist/tools/registry.js.map +1 -1
  43. package/dist/tools/sed.d.ts +4 -0
  44. package/dist/tools/sed.d.ts.map +1 -0
  45. package/dist/tools/sed.js +121 -0
  46. package/dist/tools/sed.js.map +1 -0
  47. package/dist/web/index.d.ts.map +1 -1
  48. package/dist/web/index.js +26 -3
  49. package/dist/web/index.js.map +1 -1
  50. package/dist/web/public/app.js +5 -855
  51. package/dist/web/public/confirm.js +70 -0
  52. package/dist/web/public/index.html +121 -120
  53. package/dist/web/public/input.js +107 -0
  54. package/dist/web/public/messages.js +395 -0
  55. package/dist/web/public/sidebar.js +82 -0
  56. package/dist/web/public/state.js +72 -0
  57. package/dist/web/public/style.css +996 -949
  58. package/dist/web/public/utils.js +37 -0
  59. package/dist/web/public/websocket.js +267 -0
  60. package/dist/web/public/ws.js +19 -0
  61. package/dist/web/session-bridge.d.ts.map +1 -1
  62. package/dist/web/session-bridge.js +3 -7
  63. package/dist/web/session-bridge.js.map +1 -1
  64. package/package.json +55 -54
  65. package/scripts/copy-native.mjs +24 -0
  66. package/scripts/copy-web.mjs +15 -0
  67. package/src/agent/index.ts +25 -25
  68. package/src/agent/llm.ts +4 -0
  69. package/src/agent/prompt-builder.ts +13 -9
  70. package/src/agent/providers/anthropic.ts +179 -179
  71. package/src/agent/providers/google.ts +210 -211
  72. package/src/agent/providers/ollama.ts +186 -186
  73. package/src/agent/providers/openai.ts +61 -5
  74. package/src/agent/session.ts +59 -3
  75. package/src/config/index.ts +342 -335
  76. package/src/tools/__tests__/bash.test.ts +1 -1
  77. package/src/tools/__tests__/edit.test.ts +981 -385
  78. package/src/tools/__tests__/grep.test.ts +35 -0
  79. package/src/tools/bash.ts +97 -11
  80. package/src/tools/edit.ts +362 -169
  81. package/src/tools/git.ts +76 -76
  82. package/src/tools/grep.ts +57 -30
  83. package/src/tools/registry.ts +2 -31
  84. package/src/tui/index.ts +786 -786
  85. package/src/web/index.ts +25 -3
  86. package/src/web/public/app.js +5 -855
  87. package/src/web/public/confirm.js +70 -0
  88. package/src/web/public/index.html +121 -120
  89. package/src/web/public/input.js +107 -0
  90. package/src/web/public/messages.js +395 -0
  91. package/src/web/public/sidebar.js +82 -0
  92. package/src/web/public/state.js +72 -0
  93. package/src/web/public/style.css +996 -949
  94. package/src/web/public/utils.js +37 -0
  95. package/src/web/public/websocket.js +267 -0
  96. package/src/web/public/ws.js +19 -0
  97. package/src/web/session-bridge.ts +193 -194
  98. package/.claude/settings.local.json +0 -14
  99. package/.kilo/plans/1780064105789-mighty-pixel.md +0 -171
  100. package/.kilo/plans/1780293725888-quick-wizard.md +0 -62
  101. package/.lonny/plan-web-cwd-status.md +0 -38
  102. package/src/tools/exec.ts +0 -348
@@ -0,0 +1,37 @@
1
+ /* ── Utility Functions ── */
2
+
3
+ import { chatContainer } from './state.js'
4
+
5
+ export function getWsUrl() {
6
+ const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'
7
+ return `${proto}//${location.host}`
8
+ }
9
+
10
+ export function scrollToBottom() {
11
+ requestAnimationFrame(() => {
12
+ chatContainer.scrollTop = chatContainer.scrollHeight
13
+ })
14
+ }
15
+
16
+ export function escapeHtml(text) {
17
+ const div = document.createElement('div')
18
+ div.textContent = text
19
+ return div.innerHTML
20
+ }
21
+
22
+ export function formatTimestamp() {
23
+ return new Date().toLocaleTimeString()
24
+ }
25
+
26
+ export function formatTokenCount(n) {
27
+ if (n >= 1000000) return `${(n / 1000000).toFixed(n % 1000000 === 0 ? 0 : 1)}M`
28
+ if (n >= 1000) return `${(n / 1000).toFixed(n % 1000 === 0 ? 0 : 1)}K`
29
+ return String(n)
30
+ }
31
+
32
+ export function renderMarkdown(text) {
33
+ if (typeof marked !== 'undefined' && marked.parse) {
34
+ return marked.parse(text, { breaks: true, gfm: true })
35
+ }
36
+ return `<pre>${escapeHtml(text)}</pre>`
37
+ }
@@ -0,0 +1,267 @@
1
+ /* ── WebSocket Connection & Message Handler ── */
2
+
3
+ import { closeConfirmDialog, showConfirmDialog } from './confirm.js'
4
+ import {
5
+ addErrorMessage,
6
+ addSystemMessage,
7
+ addTokenStats,
8
+ addToolCall,
9
+ addToolResult,
10
+ appendChunk,
11
+ finalizeAssistantMessage,
12
+ hideThinking,
13
+ renderSessionHistory,
14
+ showThinking,
15
+ startAssistantMessage,
16
+ } from './messages.js'
17
+ import { updatePlansAndTodos } from './sidebar.js'
18
+ import {
19
+ balanceDisplay,
20
+ balanceSep,
21
+ connectionOverlay,
22
+ cwdDisplay,
23
+ MAX_RECONNECT_ATTEMPTS,
24
+ messagesEl,
25
+ modeDisplay,
26
+ modelDisplay,
27
+ pendingToolCalls,
28
+ RECONNECT_DELAY,
29
+ setStatus,
30
+ state,
31
+ tokenCache,
32
+ tokenCalls,
33
+ tokenIn,
34
+ tokenOut,
35
+ } from './state.js'
36
+ import { formatTokenCount, getWsUrl, scrollToBottom } from './utils.js'
37
+ import { setWs } from './ws.js'
38
+
39
+ function handleMessage(msg) {
40
+ switch (msg.type) {
41
+ case 'hello':
42
+ state.currentMode = msg.mode || 'code'
43
+ state.currentModel = msg.model || ''
44
+ state.currentProvider = msg.provider || ''
45
+ modeDisplay.textContent = state.currentMode
46
+ modelDisplay.textContent = `${state.currentProvider}/${state.currentModel}`
47
+ if (msg.totalIn !== undefined) {
48
+ tokenIn.textContent = formatTokenCount(msg.totalIn || 0)
49
+ tokenOut.textContent = formatTokenCount(msg.totalOut || 0)
50
+ tokenCalls.textContent = `(${msg.totalApi || 0})`
51
+ const cacheHit = msg.totalCacheHit ?? 0
52
+ const cacheMiss = msg.totalCacheMiss ?? 0
53
+ const cacheTotal = cacheHit + cacheMiss
54
+ if (cacheTotal > 0) {
55
+ const pct = Math.round((cacheHit / cacheTotal) * 100)
56
+ tokenCache.textContent = `| 缓存 ${pct}%`
57
+ tokenCache.classList.remove('hidden')
58
+ }
59
+ }
60
+ if (msg.webBalance) {
61
+ balanceDisplay.textContent = `余额:${msg.webBalance}`
62
+ balanceDisplay.style.display = ''
63
+ balanceSep.style.display = ''
64
+ } else if (msg.balance) {
65
+ balanceDisplay.textContent = `余额:${msg.balance}`
66
+ balanceDisplay.style.display = ''
67
+ balanceSep.style.display = ''
68
+ } else {
69
+ balanceDisplay.style.display = 'none'
70
+ balanceSep.style.display = 'none'
71
+ }
72
+ if (msg.cwd) {
73
+ const maxLen = 35
74
+ cwdDisplay.textContent = msg.cwd.length > maxLen ? `...${msg.cwd.slice(-maxLen)}` : msg.cwd
75
+ }
76
+ break
77
+
78
+ case 'chunk':
79
+ appendChunk(msg.text || '')
80
+ break
81
+
82
+ case 'thinking':
83
+ showThinking(msg.text || '')
84
+ break
85
+
86
+ case 'thinking_end':
87
+ hideThinking()
88
+ break
89
+
90
+ case 'tool_call':
91
+ addToolCall(msg.name, msg.input, msg.id)
92
+ break
93
+
94
+ case 'tool_result':
95
+ if (msg.success) {
96
+ addToolResult(msg.name, true, msg.output || '', msg.id)
97
+ } else {
98
+ addToolResult(msg.name, false, msg.error || 'Unknown error', msg.id)
99
+ }
100
+ break
101
+
102
+ case 'turn_start':
103
+ setStatus(true)
104
+ startAssistantMessage()
105
+ break
106
+
107
+ case 'turn_end':
108
+ hideThinking()
109
+ for (const [, el] of pendingToolCalls) el.classList.remove('executing')
110
+ pendingToolCalls.clear()
111
+ finalizeAssistantMessage()
112
+ break
113
+
114
+ case 'done':
115
+ setStatus(false)
116
+ hideThinking()
117
+ for (const [, el] of pendingToolCalls) el.classList.remove('executing')
118
+ pendingToolCalls.clear()
119
+ finalizeAssistantMessage()
120
+ if (msg.reason === 'error') {
121
+ addErrorMessage('An error occurred during processing.')
122
+ }
123
+ break
124
+
125
+ case 'mode_changed':
126
+ state.currentMode = msg.mode
127
+ modeDisplay.textContent = state.currentMode
128
+ addSystemMessage(`Switched to ${state.currentMode} mode`)
129
+ break
130
+
131
+ case 'model_changed':
132
+ state.currentModel = msg.model
133
+ modelDisplay.textContent = `${state.currentProvider}/${state.currentModel}`
134
+ addSystemMessage(`Model switched to ${state.currentModel}`)
135
+ break
136
+
137
+ case 'session_cleared':
138
+ messagesEl.innerHTML = ''
139
+ addSystemMessage('Session cleared. Starting fresh.')
140
+ break
141
+
142
+ case 'help':
143
+ if (Array.isArray(msg.commands)) {
144
+ addSystemMessage('Available commands:')
145
+ msg.commands.forEach(cmd => addSystemMessage(cmd))
146
+ }
147
+ break
148
+
149
+ case 'compaction':
150
+ addSystemMessage(`📦 Compressed context: ${msg.before} → ${msg.after} messages`)
151
+ break
152
+
153
+ case 'plan_written':
154
+ addSystemMessage(`📝 Plan written: ${msg.display || ''}`)
155
+ break
156
+
157
+ case 'session_history':
158
+ renderSessionHistory(msg.messages)
159
+ setTimeout(scrollToBottom, 50)
160
+ break
161
+
162
+ case 'plan_data':
163
+ state.plans = msg.plans || []
164
+ state.currentPlanName = msg.currentPlanName || ''
165
+ state.todos = msg.todos || []
166
+ updatePlansAndTodos()
167
+ break
168
+
169
+ case 'balance_update':
170
+ if (msg.webBalance) {
171
+ balanceDisplay.textContent = `余额:${msg.webBalance}`
172
+ balanceDisplay.style.display = ''
173
+ balanceSep.style.display = ''
174
+ } else if (msg.balance) {
175
+ balanceDisplay.textContent = `余额:${msg.balance}`
176
+ balanceDisplay.style.display = ''
177
+ balanceSep.style.display = ''
178
+ }
179
+ break
180
+
181
+ case 'token_stats':
182
+ addTokenStats(
183
+ msg.turnIn,
184
+ msg.turnOut,
185
+ msg.totalIn,
186
+ msg.totalOut,
187
+ msg.turnApi,
188
+ msg.totalApi,
189
+ msg.totalCacheHit,
190
+ msg.totalCacheMiss,
191
+ )
192
+ break
193
+
194
+ case 'tool_confirm_request': {
195
+ if (!msg.toolCalls || msg.toolCalls.length === 0) break
196
+ showConfirmDialog(msg.toolCalls)
197
+ break
198
+ }
199
+
200
+ case 'error':
201
+ closeConfirmDialog()
202
+ addErrorMessage(msg.message || 'Unknown error')
203
+ break
204
+
205
+ case 'pong':
206
+ break
207
+
208
+ default:
209
+ console.log('Unknown message type:', msg.type)
210
+ }
211
+ }
212
+
213
+ export function connect() {
214
+ connectionOverlay.classList.remove('hidden')
215
+
216
+ let ws
217
+ try {
218
+ ws = new WebSocket(getWsUrl())
219
+ setWs(ws)
220
+ } catch (err) {
221
+ connectionOverlay.classList.add('hidden')
222
+ addErrorMessage(`Connection failed: ${err.message}`)
223
+ return
224
+ }
225
+
226
+ ws.onopen = () => {
227
+ connectionOverlay.classList.add('hidden')
228
+ state.reconnectAttempts = 0
229
+ addSystemMessage('Connected to Lonny')
230
+ }
231
+
232
+ ws.onmessage = event => {
233
+ try {
234
+ const msg = JSON.parse(event.data)
235
+ handleMessage(msg)
236
+ } catch (err) {
237
+ console.error('Failed to parse message:', err)
238
+ }
239
+ }
240
+
241
+ ws.onclose = () => {
242
+ setStatus(false)
243
+ hideThinking()
244
+ for (const [, el] of pendingToolCalls) el.classList.remove('executing')
245
+ pendingToolCalls.clear()
246
+ if (state.streamingMsgEl) {
247
+ finalizeAssistantMessage()
248
+ }
249
+
250
+ if (state.reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
251
+ state.reconnectAttempts++
252
+ const delay = RECONNECT_DELAY * Math.min(state.reconnectAttempts, 5)
253
+ connectionOverlay.querySelector('p').textContent =
254
+ `Reconnecting in ${delay / 1000}s... (attempt ${state.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`
255
+ connectionOverlay.classList.remove('hidden')
256
+ setTimeout(connect, delay)
257
+ } else {
258
+ connectionOverlay.querySelector('p').textContent = 'Connection lost. Please refresh the page.'
259
+ connectionOverlay.classList.remove('hidden')
260
+ addErrorMessage('Connection lost. Refresh the page to reconnect.')
261
+ }
262
+ }
263
+
264
+ ws.onerror = () => {
265
+ // onclose will fire after this
266
+ }
267
+ }
@@ -0,0 +1,19 @@
1
+ /* ── WebSocket Reference & Send ── */
2
+
3
+ let ws = null
4
+
5
+ export function getWs() {
6
+ return ws
7
+ }
8
+
9
+ export function setWs(newWs) {
10
+ ws = newWs
11
+ }
12
+
13
+ export function sendWsMsg(data) {
14
+ if (ws && ws.readyState === WebSocket.OPEN) {
15
+ ws.send(JSON.stringify(data))
16
+ return true
17
+ }
18
+ return false
19
+ }