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
@@ -1,38 +0,0 @@
1
- # Plan: Web UI 状态栏添加工作目录显示
2
-
3
- ## 修改文件
4
-
5
- ### 1. `src/web/index.ts` — 后端
6
- 在 `hello` 消息中增加 `cwd` 字段:
7
- ```ts
8
- cwd: config.cwd,
9
- ```
10
- 添加在 `version` 之后。
11
-
12
- ### 2. `src/web/public/index.html` — HTML
13
- 在状态栏左侧(`<div class="status-left">` 内)添加:
14
- ```html
15
- <span class="separator">|</span>
16
- <span id="cwd-display" class="cwd-text"></span>
17
- ```
18
- 放在 `mode-display` 和 `</div>` 之间。
19
-
20
- ### 3. `src/web/public/app.js` — 前端 JS
21
- - 添加 DOM 引用:`const cwdDisplay = document.getElementById('cwd-display')`
22
- - 在 `hello` 消息处理中添加:
23
- ```js
24
- cwdDisplay.textContent = msg.cwd || ''
25
- ```
26
-
27
- ### 4. `src/web/public/style.css` — 样式
28
- 添加:
29
- ```css
30
- .cwd-text { color: var(--text-dim); font-size: 12px; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
31
- ```
32
-
33
- ## Todo List
34
- - [x] `src/web/index.ts` — hello 消息添加 `cwd: config.cwd`
35
- - [x] `src/web/public/index.html` — 状态栏添加 cwd 元素
36
- - [x] `src/web/public/app.js` — 添加 DOM 引用和消息处理
37
- - [x] `src/web/public/style.css` — 添加 cwd 样式
38
- - [x] 构建并验证
package/src/tools/exec.ts DELETED
@@ -1,348 +0,0 @@
1
- import * as vm from 'node:vm'
2
- import { fmtErr } from './errors.js'
3
- import type { Tool, ToolDefinition, ToolResult } from './types.js'
4
-
5
- // ── Types ────────────────────────────────────────────────────────────────────
6
-
7
- interface ExecPragma {
8
- timeout_ms?: number
9
- yield_time_ms?: number
10
- max_output_tokens?: number
11
- }
12
-
13
- interface ExecContext {
14
- tools: Record<string, (input: unknown) => Promise<string>>
15
- store: Map<string, unknown>
16
- output: string[]
17
- notified: boolean
18
- }
19
-
20
- const PRAGMA_PREFIX = '// @exec:'
21
- const DEFAULT_TIMEOUT_MS = 30_000
22
-
23
- // ── Pragma parsing ──────────────────────────────────────────────────────────
24
-
25
- function parsePragma(input: string): { code: string; pragma: ExecPragma } {
26
- const lines = input.split('\n')
27
- const firstLine = lines[0]?.trim() ?? ''
28
- const pragma: ExecPragma = {}
29
-
30
- if (firstLine.startsWith(PRAGMA_PREFIX)) {
31
- const jsonStr = firstLine.slice(PRAGMA_PREFIX.length).trim()
32
- if (jsonStr) {
33
- try {
34
- const parsed = JSON.parse(jsonStr) as ExecPragma
35
- // Validate fields
36
- for (const key of Object.keys(parsed)) {
37
- if (!['timeout_ms', 'yield_time_ms', 'max_output_tokens'].includes(key)) {
38
- throw new Error(
39
- `exec pragma only supports \`timeout_ms\`, \`yield_time_ms\`, and \`max_output_tokens\`; got \`${key}\``,
40
- )
41
- }
42
- }
43
- if (parsed.timeout_ms !== undefined) {
44
- if (!Number.isSafeInteger(parsed.timeout_ms) || parsed.timeout_ms < 0) {
45
- throw new Error('exec pragma field `timeout_ms` must be a non-negative safe integer')
46
- }
47
- }
48
- if (parsed.yield_time_ms !== undefined) {
49
- if (!Number.isSafeInteger(parsed.yield_time_ms) || parsed.yield_time_ms < 0) {
50
- throw new Error('exec pragma field `yield_time_ms` must be a non-negative safe integer')
51
- }
52
- }
53
- if (parsed.max_output_tokens !== undefined) {
54
- if (!Number.isSafeInteger(parsed.max_output_tokens) || parsed.max_output_tokens < 0) {
55
- throw new Error(
56
- 'exec pragma field `max_output_tokens` must be a non-negative safe integer',
57
- )
58
- }
59
- }
60
- Object.assign(pragma, parsed)
61
- } catch (err) {
62
- throw new Error(`exec pragma parse error: ${fmtErr(err)}`)
63
- }
64
- }
65
- return { code: lines.slice(1).join('\n').trim(), pragma }
66
- }
67
-
68
- return { code: input, pragma }
69
- }
70
-
71
- // ── TypeScript type rendering ────────────────────────────────────────────────
72
-
73
- function renderTsType(param: Record<string, unknown>, depth = 0): string {
74
- const indent = ' '.repeat(depth + 1)
75
- const closeIndent = ' '.repeat(depth)
76
-
77
- if (param.type === 'string') return 'string'
78
- if (param.type === 'number' || param.type === 'integer') return 'number'
79
- if (param.type === 'boolean') return 'boolean'
80
- if (param.type === 'array') {
81
- if (param.items) return `Array<${renderTsType(param.items as Record<string, unknown>, depth)}>`
82
- return 'unknown[]'
83
- }
84
- if (param.type === 'object' || (param.properties as Record<string, unknown> | undefined)) {
85
- const props = param.properties as Record<string, Record<string, unknown>> | undefined
86
- const additionalProps = param.additionalProperties
87
- if (!props && !additionalProps) return 'Record<string, unknown>'
88
-
89
- const lines: string[] = ['{']
90
- if (props) {
91
- for (const [key, value] of Object.entries(props)) {
92
- const isRequired =
93
- Array.isArray(param.required) && (param.required as string[]).includes(key)
94
- const opt = isRequired ? '' : '?'
95
- const desc = value.description ? ` // ${value.description}` : ''
96
- const valType = renderTsType(value, depth + 1)
97
- lines.push(`${indent}${JSON.stringify(key)}${opt}: ${valType};${desc}`)
98
- }
99
- }
100
- if (additionalProps && additionalProps !== false) {
101
- const valType =
102
- typeof additionalProps === 'object'
103
- ? renderTsType(additionalProps as Record<string, unknown>, depth + 1)
104
- : 'unknown'
105
- lines.push(`${indent}[key: string]: ${valType};`)
106
- }
107
- lines.push(`${closeIndent}}`)
108
- return lines.join('\n')
109
- }
110
-
111
- return 'unknown'
112
- }
113
-
114
- function renderToolDeclaration(tool: ToolDefinition): string {
115
- const params = tool.parameters
116
- const hasParams = params && Object.keys(params).length > 0
117
- const paramType = hasParams
118
- ? renderTsType({
119
- type: 'object',
120
- properties: params,
121
- required: Object.entries(params)
122
- .filter(([_, v]) => v.required)
123
- .map(([k]) => k),
124
- } as unknown as Record<string, unknown>)
125
- : '{}'
126
-
127
- return `${tool.name}(args: ${paramType}): Promise<string>;`
128
- }
129
-
130
- // ── Build the exec tool description with TypeScript declarations ────────────
131
-
132
- function buildExecDescription(toolDefs: ToolDefinition[]): string {
133
- const toolDeclarations = toolDefs
134
- .filter(t => t.name !== 'exec')
135
- .map(t => ` ${renderToolDeclaration(t)}`)
136
- .join('\n')
137
-
138
- return `Run JavaScript code to orchestrate/compose tool calls.
139
- - Evaluates the provided JavaScript code in a fresh V8 sandbox as an async function.
140
- - All tools are available on the global \`tools\` object, for example \`await tools.read({paths: ["file.ts"]})\`.
141
- - Tool methods take an object as their input argument and return a string.
142
- - Runs raw JavaScript — no \`require\`, no file system, no network access, no \`console\` (use \`text()\` instead).
143
- - Accepts raw JavaScript source text, not JSON, quoted strings, or markdown code fences.
144
- - You may optionally start the tool input with a first-line pragma like \`// @exec: {"timeout_ms": 30000, "max_output_tokens": 1000}\`.
145
- - \`timeout_ms\` sets the maximum execution time (default: 30000).
146
- - \`max_output_tokens\` sets the token budget for results (approximate character limit).
147
-
148
- Global helpers:
149
- - \`text(value)\`: Appends a string to the output. Non-string values are stringified.
150
- - \`exit()\`: Immediately ends the current script successfully.
151
- - \`store(key, value)\`: Stores a value under a string key for later \`exec\` calls in the same session.
152
- - \`load(key)\`: Returns the stored value, or \`undefined\` if missing.
153
-
154
- Available tool declarations:
155
- \`\`\`ts
156
- declare const tools: {
157
- ${toolDeclarations}
158
- };
159
- \`\`\``
160
- }
161
-
162
- // ── Exec tool factory ───────────────────────────────────────────────────────
163
-
164
- export function createExecTool(getTools: () => Tool[]): Tool {
165
- // Shared store across exec calls in the same session
166
- const sharedStore = new Map<string, unknown>()
167
-
168
- return {
169
- definition: {
170
- name: 'exec',
171
- description: 'Run JavaScript code to orchestrate tool calls in a sandboxed V8 isolate.',
172
- parameters: {
173
- code: {
174
- type: 'string',
175
- description:
176
- 'JavaScript source code to execute. Optionally starts with // @exec: {"timeout_ms": 30000} pragma.',
177
- required: true,
178
- },
179
- },
180
- },
181
- async execute(input: Record<string, unknown>): Promise<ToolResult> {
182
- const rawCode = input.code as string
183
- if (!rawCode || typeof rawCode !== 'string') {
184
- return {
185
- success: false,
186
- output: '',
187
- error: 'exec expects a `code` string parameter with JavaScript source text.',
188
- }
189
- }
190
-
191
- // Parse pragma
192
- let code: string
193
- let pragma: ExecPragma
194
- try {
195
- const parsed = parsePragma(rawCode)
196
- code = parsed.code
197
- pragma = parsed.pragma
198
- } catch (err) {
199
- return { success: false, output: '', error: fmtErr(err) }
200
- }
201
-
202
- if (!code.trim()) {
203
- return {
204
- success: false,
205
- output: '',
206
- error: 'exec expects non-empty JavaScript source text.',
207
- }
208
- }
209
-
210
- // Build context
211
- const ctx: ExecContext = {
212
- tools: {},
213
- store: sharedStore,
214
- output: [],
215
- notified: false,
216
- }
217
-
218
- // Wrap each registered tool as an async function available in the sandbox
219
- const allTools = getTools()
220
- for (const tool of allTools) {
221
- if (tool.definition.name === 'exec') continue // don't expose exec itself
222
- ctx.tools[tool.definition.name] = async (toolInput: unknown): Promise<string> => {
223
- const result = await tool.execute((toolInput ?? {}) as Record<string, unknown>)
224
- if (!result.success) {
225
- throw new Error(`Tool "${tool.definition.name}" failed: ${result.error}`)
226
- }
227
- return result.output
228
- }
229
- }
230
-
231
- // Wrap the code in an async function that receives the context
232
- const wrappedCode = `
233
- (async () => {
234
- const tools = context.tools;
235
- const store = context.store;
236
- const output = context.output;
237
-
238
- function text(value) {
239
- if (value === null || value === undefined) output.push(String(value));
240
- else if (typeof value === 'object') output.push(JSON.stringify(value));
241
- else output.push(String(value));
242
- }
243
-
244
- function exit() {
245
- context.done = true;
246
- return;
247
- }
248
-
249
- function store(key, value) {
250
- context.store.set(key, value);
251
- }
252
-
253
- function load(key) {
254
- return context.store.get(key);
255
- }
256
-
257
- ${code}
258
- })()
259
- `
260
-
261
- // Create sandbox context
262
- const sandbox = {
263
- context: ctx,
264
- setTimeout: setTimeout.bind(globalThis),
265
- clearTimeout: clearTimeout.bind(globalThis),
266
- console: {
267
- log: (...args: unknown[]) => ctx.output.push(args.map(a => String(a)).join(' ')),
268
- error: (...args: unknown[]) =>
269
- ctx.output.push(`ERROR: ${args.map(a => String(a)).join(' ')}`),
270
- warn: (...args: unknown[]) =>
271
- ctx.output.push(`WARN: ${args.map(a => String(a)).join(' ')}`),
272
- info: (...args: unknown[]) => ctx.output.push(args.map(a => String(a)).join(' ')),
273
- },
274
- // Safe globals
275
- Array,
276
- Object,
277
- String,
278
- Number,
279
- Boolean,
280
- Date,
281
- Math,
282
- JSON,
283
- RegExp,
284
- Map,
285
- Set,
286
- Promise,
287
- Error,
288
- TypeError,
289
- RangeError,
290
- SyntaxError,
291
- ReferenceError,
292
- parseInt,
293
- parseFloat,
294
- isNaN,
295
- isFinite,
296
- encodeURI,
297
- decodeURI,
298
- encodeURIComponent,
299
- decodeURIComponent,
300
- // TextEncoder/Decoder for buffer handling
301
- TextEncoder,
302
- TextDecoder,
303
- }
304
-
305
- const timeout = pragma.timeout_ms ?? DEFAULT_TIMEOUT_MS
306
- const maxOutputTokens = pragma.max_output_tokens ?? 10_000
307
- const maxChars = maxOutputTokens * 4 // rough estimate
308
-
309
- try {
310
- const script = new vm.Script(wrappedCode, {
311
- filename: 'exec-sandbox.js',
312
- })
313
-
314
- const vmContext = vm.createContext(sandbox)
315
- await script.runInContext(vmContext, {
316
- timeout: timeout,
317
- breakOnSigint: true,
318
- })
319
-
320
- let output = ctx.output.join('')
321
- if (output.length > maxChars) {
322
- output = `${output.slice(0, maxChars)}\n\n... (output truncated)`
323
- }
324
-
325
- return { success: true, output: output || '(exec completed with no output)' }
326
- } catch (err) {
327
- const msg = fmtErr(err)
328
- if (msg.includes('timed out') || msg.includes('timeout')) {
329
- return {
330
- success: false,
331
- output: ctx.output.join(''),
332
- error: `exec timed out after ${timeout}ms`,
333
- }
334
- }
335
- return {
336
- success: false,
337
- output: ctx.output.join(''),
338
- error: `exec error: ${msg}`,
339
- }
340
- }
341
- },
342
- }
343
- }
344
-
345
- /** Rebuild the exec tool definition (called when tools change) */
346
- export function updateExecToolDefinition(execTool: Tool, toolDefs: ToolDefinition[]): void {
347
- execTool.definition.description = buildExecDescription(toolDefs)
348
- }