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.
- package/README.md +326 -326
- package/dist/agent/llm.d.ts +4 -0
- package/dist/agent/llm.d.ts.map +1 -1
- package/dist/agent/prompt-builder.d.ts.map +1 -1
- package/dist/agent/prompt-builder.js +13 -9
- package/dist/agent/prompt-builder.js.map +1 -1
- package/dist/agent/providers/google.d.ts.map +1 -1
- package/dist/agent/providers/google.js.map +1 -1
- package/dist/agent/providers/openai.d.ts.map +1 -1
- package/dist/agent/providers/openai.js +43 -3
- package/dist/agent/providers/openai.js.map +1 -1
- package/dist/agent/session.d.ts +4 -0
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +45 -7
- package/dist/agent/session.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +7 -1
- package/dist/config/index.js.map +1 -1
- package/dist/tools/__tests__/bash.test.js +1 -1
- package/dist/tools/__tests__/bash.test.js.map +1 -1
- package/dist/tools/__tests__/edit.test.js +875 -371
- package/dist/tools/__tests__/edit.test.js.map +1 -1
- package/dist/tools/__tests__/grep.test.js +32 -0
- package/dist/tools/__tests__/grep.test.js.map +1 -1
- package/dist/tools/__tests__/sed.test.d.ts +2 -0
- package/dist/tools/__tests__/sed.test.d.ts.map +1 -0
- package/dist/tools/__tests__/sed.test.js +228 -0
- package/dist/tools/__tests__/sed.test.js.map +1 -0
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +83 -10
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +50 -9
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +328 -158
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +54 -30
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/registry.d.ts +0 -6
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +2 -29
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/sed.d.ts +4 -0
- package/dist/tools/sed.d.ts.map +1 -0
- package/dist/tools/sed.js +121 -0
- package/dist/tools/sed.js.map +1 -0
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +26 -3
- package/dist/web/index.js.map +1 -1
- package/dist/web/public/app.js +5 -855
- package/dist/web/public/confirm.js +70 -0
- package/dist/web/public/index.html +121 -120
- package/dist/web/public/input.js +107 -0
- package/dist/web/public/messages.js +395 -0
- package/dist/web/public/sidebar.js +82 -0
- package/dist/web/public/state.js +72 -0
- package/dist/web/public/style.css +996 -949
- package/dist/web/public/utils.js +37 -0
- package/dist/web/public/websocket.js +267 -0
- package/dist/web/public/ws.js +19 -0
- package/dist/web/session-bridge.d.ts.map +1 -1
- package/dist/web/session-bridge.js +3 -7
- package/dist/web/session-bridge.js.map +1 -1
- package/package.json +55 -54
- package/scripts/copy-native.mjs +24 -0
- package/scripts/copy-web.mjs +15 -0
- package/src/agent/index.ts +25 -25
- package/src/agent/llm.ts +4 -0
- package/src/agent/prompt-builder.ts +13 -9
- package/src/agent/providers/anthropic.ts +179 -179
- package/src/agent/providers/google.ts +210 -211
- package/src/agent/providers/ollama.ts +186 -186
- package/src/agent/providers/openai.ts +61 -5
- package/src/agent/session.ts +59 -3
- package/src/config/index.ts +342 -335
- package/src/tools/__tests__/bash.test.ts +1 -1
- package/src/tools/__tests__/edit.test.ts +981 -385
- package/src/tools/__tests__/grep.test.ts +35 -0
- package/src/tools/bash.ts +97 -11
- package/src/tools/edit.ts +362 -169
- package/src/tools/git.ts +76 -76
- package/src/tools/grep.ts +57 -30
- package/src/tools/registry.ts +2 -31
- package/src/tui/index.ts +786 -786
- package/src/web/index.ts +25 -3
- package/src/web/public/app.js +5 -855
- package/src/web/public/confirm.js +70 -0
- package/src/web/public/index.html +121 -120
- package/src/web/public/input.js +107 -0
- package/src/web/public/messages.js +395 -0
- package/src/web/public/sidebar.js +82 -0
- package/src/web/public/state.js +72 -0
- package/src/web/public/style.css +996 -949
- package/src/web/public/utils.js +37 -0
- package/src/web/public/websocket.js +267 -0
- package/src/web/public/ws.js +19 -0
- package/src/web/session-bridge.ts +193 -194
- package/.claude/settings.local.json +0 -14
- package/.kilo/plans/1780064105789-mighty-pixel.md +0 -171
- package/.kilo/plans/1780293725888-quick-wizard.md +0 -62
- package/.lonny/plan-web-cwd-status.md +0 -38
- package/src/tools/exec.ts +0 -348
|
@@ -1,194 +1,193 @@
|
|
|
1
|
-
import { EventChannels, getGlobalEventBus } from '../agent/event-bus.js'
|
|
2
|
-
import type { Session } from '../agent/session.js'
|
|
3
|
-
import type { Config } from '../config/index.js'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Bridge between Session/EventBus and WebSocket.
|
|
7
|
-
* Listens to EventBus events and forwards them as JSON messages.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/** Strip ANSI escape codes from a string (terminal colors are meaningless in the browser) */
|
|
11
|
-
function stripAnsi(text: string): string {
|
|
12
|
-
return text.replace(/\x1b\[[0-9;]*m/g, '')
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface WsMessage {
|
|
16
|
-
type: string
|
|
17
|
-
[key: string]: unknown
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export type SendFn = (msg: WsMessage) => void
|
|
21
|
-
|
|
22
|
-
const WS_PROTOCOL_VERSION = 1
|
|
23
|
-
|
|
24
|
-
export function startSessionBridge(
|
|
25
|
-
session: Session,
|
|
26
|
-
config: Config,
|
|
27
|
-
send: SendFn,
|
|
28
|
-
): { close: () => void; sendMessage: (text: string) => Promise<void> } {
|
|
29
|
-
const bus = getGlobalEventBus()
|
|
30
|
-
|
|
31
|
-
// Send initial handshake with current token stats
|
|
32
|
-
send({
|
|
33
|
-
type: 'hello',
|
|
34
|
-
version: WS_PROTOCOL_VERSION,
|
|
35
|
-
mode: config.mode,
|
|
36
|
-
model: config.model,
|
|
37
|
-
provider: config.provider,
|
|
38
|
-
totalIn: session.totalInputTokens,
|
|
39
|
-
totalOut: session.totalOutputTokens,
|
|
40
|
-
totalApi: session.totalApiCalls,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (cmd === '
|
|
123
|
-
session.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
'/
|
|
141
|
-
'/
|
|
142
|
-
'/
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
send({ type: '
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
1
|
+
import { EventChannels, getGlobalEventBus } from '../agent/event-bus.js'
|
|
2
|
+
import type { Session } from '../agent/session.js'
|
|
3
|
+
import type { Config } from '../config/index.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Bridge between Session/EventBus and WebSocket.
|
|
7
|
+
* Listens to EventBus events and forwards them as JSON messages.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Strip ANSI escape codes from a string (terminal colors are meaningless in the browser) */
|
|
11
|
+
function stripAnsi(text: string): string {
|
|
12
|
+
return text.replace(/\x1b\[[0-9;]*m/g, '')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface WsMessage {
|
|
16
|
+
type: string
|
|
17
|
+
[key: string]: unknown
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type SendFn = (msg: WsMessage) => void
|
|
21
|
+
|
|
22
|
+
const WS_PROTOCOL_VERSION = 1
|
|
23
|
+
|
|
24
|
+
export function startSessionBridge(
|
|
25
|
+
session: Session,
|
|
26
|
+
config: Config,
|
|
27
|
+
send: SendFn,
|
|
28
|
+
): { close: () => void; sendMessage: (text: string) => Promise<void> } {
|
|
29
|
+
const bus = getGlobalEventBus()
|
|
30
|
+
|
|
31
|
+
// Send initial handshake with current token stats
|
|
32
|
+
send({
|
|
33
|
+
type: 'hello',
|
|
34
|
+
version: WS_PROTOCOL_VERSION,
|
|
35
|
+
mode: config.mode,
|
|
36
|
+
model: config.model,
|
|
37
|
+
provider: config.provider,
|
|
38
|
+
totalIn: session.totalInputTokens,
|
|
39
|
+
totalOut: session.totalOutputTokens,
|
|
40
|
+
totalApi: session.totalApiCalls,
|
|
41
|
+
totalCacheHit: session.totalCacheHitTokens || undefined,
|
|
42
|
+
totalCacheMiss: session.totalCacheMissTokens || undefined,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// ── Subscribe to EventBus ──
|
|
46
|
+
|
|
47
|
+
const unsubTurnStart = bus.on(EventChannels.TURN_START, data => {
|
|
48
|
+
const d = data as { prompt?: string }
|
|
49
|
+
send({ type: 'turn_start', prompt: d.prompt || '' })
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const unsubToolCall = bus.on(EventChannels.TOOL_CALL, data => {
|
|
53
|
+
const d = data as { name: string; input: Record<string, unknown>; id: string }
|
|
54
|
+
send({ type: 'tool_call', name: d.name, input: d.input, id: d.id })
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const unsubToolResult = bus.on(EventChannels.TOOL_RESULT, data => {
|
|
58
|
+
const d = data as { name: string; id: string; output: string }
|
|
59
|
+
send({
|
|
60
|
+
type: 'tool_result',
|
|
61
|
+
name: d.name,
|
|
62
|
+
id: d.id,
|
|
63
|
+
success: true,
|
|
64
|
+
output: d.name === 'edit' ? d.output : stripAnsi(d.output),
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const unsubToolError = bus.on(EventChannels.TOOL_ERROR, data => {
|
|
69
|
+
const d = data as { name: string; id: string; error: string }
|
|
70
|
+
send({ type: 'tool_result', name: d.name, id: d.id, success: false, error: stripAnsi(d.error) })
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const unsubTurnEnd = bus.on(EventChannels.TURN_END, data => {
|
|
74
|
+
const d = data as { iterations: number; toolCallCount: number }
|
|
75
|
+
send({ type: 'turn_end', iterations: d.iterations, toolCallCount: d.toolCallCount })
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const unsubThinking = bus.on(EventChannels.THINKING, data => {
|
|
79
|
+
const d = data as { text: string }
|
|
80
|
+
send({ type: 'thinking', text: d.text })
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const unsubThinkingEnd = bus.on(EventChannels.THINKING_END, () => {
|
|
84
|
+
send({ type: 'thinking_end' })
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const unsubCompaction = bus.on(EventChannels.COMPACTION_TRIGGERED, data => {
|
|
88
|
+
const d = data as { before: number; after: number }
|
|
89
|
+
send({ type: 'compaction', before: d.before, after: d.after })
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
const unsubTokenStats = bus.on(EventChannels.TOKEN_STATS, data => {
|
|
93
|
+
const d = data as {
|
|
94
|
+
turnIn: number
|
|
95
|
+
turnOut: number
|
|
96
|
+
totalIn: number
|
|
97
|
+
totalOut: number
|
|
98
|
+
turnApi: number
|
|
99
|
+
totalApi: number
|
|
100
|
+
turnCacheHit?: number
|
|
101
|
+
turnCacheMiss?: number
|
|
102
|
+
totalCacheHit?: number
|
|
103
|
+
totalCacheMiss?: number
|
|
104
|
+
}
|
|
105
|
+
send({ type: 'token_stats', ...d })
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// ── Handle incoming messages from client ──
|
|
109
|
+
|
|
110
|
+
async function handleClientMessage(msg: WsMessage): Promise<void> {
|
|
111
|
+
switch (msg.type) {
|
|
112
|
+
case 'message': {
|
|
113
|
+
const text = String(msg.text || '')
|
|
114
|
+
if (!text.trim()) return
|
|
115
|
+
|
|
116
|
+
// Handle slash commands
|
|
117
|
+
if (text.startsWith('/')) {
|
|
118
|
+
const parts = text.slice(1).split(/\s+/)
|
|
119
|
+
const cmd = parts[0]
|
|
120
|
+
const arg = parts.slice(1).join(' ')
|
|
121
|
+
|
|
122
|
+
if (cmd === 'mode' && (arg === 'code' || arg === 'plan' || arg === 'ask')) {
|
|
123
|
+
session.setMode(arg as 'code' | 'plan' | 'ask')
|
|
124
|
+
send({ type: 'mode_changed', mode: arg })
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (cmd === 'model' && arg) {
|
|
129
|
+
session.config.model = arg
|
|
130
|
+
session.setMode(session.config.mode)
|
|
131
|
+
send({ type: 'model_changed', model: arg })
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (cmd === 'help') {
|
|
136
|
+
send({
|
|
137
|
+
type: 'help',
|
|
138
|
+
commands: [
|
|
139
|
+
'/mode code|plan|ask - Switch mode',
|
|
140
|
+
'/model <name> - Switch model',
|
|
141
|
+
'/new - Start a new session',
|
|
142
|
+
'/help - Show this help',
|
|
143
|
+
],
|
|
144
|
+
})
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
send({ type: 'error', message: `Unknown command: ${cmd}` })
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Send user message to session
|
|
153
|
+
try {
|
|
154
|
+
await session.chat(text)
|
|
155
|
+
send({ type: 'done', reason: 'stop' })
|
|
156
|
+
} catch (err) {
|
|
157
|
+
const errMsg = err instanceof Error ? err.message : String(err)
|
|
158
|
+
send({ type: 'error', message: errMsg })
|
|
159
|
+
send({ type: 'done', reason: 'error' })
|
|
160
|
+
}
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
case 'ping':
|
|
165
|
+
send({ type: 'pong' })
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
case 'stop':
|
|
169
|
+
session.stop()
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
default:
|
|
173
|
+
send({ type: 'error', message: `Unknown message type: ${msg.type}` })
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
close: () => {
|
|
179
|
+
unsubTurnStart()
|
|
180
|
+
unsubToolCall()
|
|
181
|
+
unsubToolResult()
|
|
182
|
+
unsubToolError()
|
|
183
|
+
unsubTurnEnd()
|
|
184
|
+
unsubThinking()
|
|
185
|
+
unsubThinkingEnd()
|
|
186
|
+
unsubCompaction()
|
|
187
|
+
unsubTokenStats()
|
|
188
|
+
},
|
|
189
|
+
sendMessage: async (text: string) => {
|
|
190
|
+
await handleClientMessage({ type: 'message', text })
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(npm run:*)",
|
|
5
|
-
"Bash(grep:*)",
|
|
6
|
-
"Bash(npx tsc:*)",
|
|
7
|
-
"Bash(npm test:*)",
|
|
8
|
-
"Bash(gh release:*)",
|
|
9
|
-
"Read(//c/Program Files/GitHub CLI//**)",
|
|
10
|
-
"Read(//c/Program Files \\(x86\\)/**)",
|
|
11
|
-
"Bash(\"/c/Program Files/GitHub CLI/gh.exe\" release create 0.2.4 --title \"v0.2.4\" --generate-notes)"
|
|
12
|
-
]
|
|
13
|
-
}
|
|
14
|
-
}
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
# Plan: Vendor `@earendil-works/pi-tui` source
|
|
2
|
-
|
|
3
|
-
## Objective
|
|
4
|
-
|
|
5
|
-
Replace the `@earendil-works/pi-tui` npm dependency with vendored source code from [pi/packages/tui](https://github.com/earendil-works/pi/tree/main/packages/tui), so the project no longer depends on that npm package.
|
|
6
|
-
|
|
7
|
-
## Current state
|
|
8
|
-
|
|
9
|
-
- `package.json` depends on `"@earendil-works/pi-tui": "0.75.5"`
|
|
10
|
-
- A patch file `patches/@earendil-works+pi-tui+0.75.5.patch` applies 2 modifications (autocomplete above editor, alternate screen buffer on start/stop)
|
|
11
|
-
- `patch-package` is in devDependencies for applying the patch
|
|
12
|
-
- Only 2 source files import from `@earendil-works/pi-tui`:
|
|
13
|
-
- `src/tui/index.ts` (type imports + 11 class exports)
|
|
14
|
-
- `src/tui/components.ts` (Component type + SelectList class)
|
|
15
|
-
- Internal Editor access: `(editor as any).killRing`, `.undoStack`, `.history`
|
|
16
|
-
|
|
17
|
-
## Steps
|
|
18
|
-
|
|
19
|
-
### 1. Download pi-tui source from GitHub
|
|
20
|
-
|
|
21
|
-
Clone a shallow copy of the repo to get the source at the matching version or the main branch:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
# From project root temp dir
|
|
25
|
-
git clone --depth 1 --branch main https://github.com/earendil-works/pi.git /tmp/pi-source
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
Alternatively, if git clone is not feasible, download each file individually from GitHub.
|
|
29
|
-
|
|
30
|
-
Source files needed from `packages/tui/src/`:
|
|
31
|
-
```
|
|
32
|
-
src/
|
|
33
|
-
index.ts - barrel exports
|
|
34
|
-
autocomplete.ts - CombinedAutocompleteProvider, AutocompleteProvider, etc.
|
|
35
|
-
editor-component.ts - EditorComponent type
|
|
36
|
-
fuzzy.ts - fuzzyFilter, fuzzyMatch
|
|
37
|
-
keybindings.ts - KeybindingsManager, TUI_KEYBINDINGS, etc.
|
|
38
|
-
keys.ts - Key, matchesKey, parseKey, etc.
|
|
39
|
-
kill-ring.ts - KillRing
|
|
40
|
-
native-modifiers.ts - native keyboard modifiers
|
|
41
|
-
stdin-buffer.ts - StdinBuffer
|
|
42
|
-
terminal-image.ts - image rendering
|
|
43
|
-
terminal.ts - ProcessTerminal, Terminal interface
|
|
44
|
-
tui.ts - TUI, Container, Component, OverlayHandle, OverlayOptions, Focusable, CURSOR_MARKER, etc.
|
|
45
|
-
undo-stack.ts - UndoStack
|
|
46
|
-
utils.ts - visibleWidth, truncateToWidth, wrapTextWithAnsi, etc.
|
|
47
|
-
word-navigation.ts - word boundary navigation
|
|
48
|
-
components/
|
|
49
|
-
box.ts
|
|
50
|
-
cancellable-loader.ts
|
|
51
|
-
editor.ts
|
|
52
|
-
image.ts
|
|
53
|
-
input.ts
|
|
54
|
-
loader.ts
|
|
55
|
-
markdown.ts
|
|
56
|
-
select-list.ts
|
|
57
|
-
settings-list.ts
|
|
58
|
-
spacer.ts
|
|
59
|
-
text.ts
|
|
60
|
-
truncated-text.ts
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Also copy `native/` directory (Windows VT input helper):
|
|
64
|
-
```
|
|
65
|
-
native/win32/prebuilds/win32-x64/win32-console-mode.node
|
|
66
|
-
native/win32/prebuilds/win32-arm64/win32-console-mode.node
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### 2. Place vendored code at `src/pi-tui/`
|
|
70
|
-
|
|
71
|
-
```
|
|
72
|
-
src/
|
|
73
|
-
pi-tui/
|
|
74
|
-
index.ts
|
|
75
|
-
autocomplete.ts
|
|
76
|
-
editor-component.ts
|
|
77
|
-
fuzzy.ts
|
|
78
|
-
keybindings.ts
|
|
79
|
-
keys.ts
|
|
80
|
-
kill-ring.ts
|
|
81
|
-
native-modifiers.ts
|
|
82
|
-
stdin-buffer.ts
|
|
83
|
-
terminal-image.ts
|
|
84
|
-
terminal.ts
|
|
85
|
-
tui.ts
|
|
86
|
-
undo-stack.ts
|
|
87
|
-
utils.ts
|
|
88
|
-
word-navigation.ts
|
|
89
|
-
components/
|
|
90
|
-
box.ts
|
|
91
|
-
cancellable-loader.ts
|
|
92
|
-
editor.ts
|
|
93
|
-
image.ts
|
|
94
|
-
input.ts
|
|
95
|
-
loader.ts
|
|
96
|
-
markdown.ts
|
|
97
|
-
select-list.ts
|
|
98
|
-
settings-list.ts
|
|
99
|
-
spacer.ts
|
|
100
|
-
text.ts
|
|
101
|
-
truncated-text.ts
|
|
102
|
-
native/
|
|
103
|
-
win32/prebuilds/win32-x64/win32-console-mode.node
|
|
104
|
-
win32/prebuilds/win32-arm64/win32-console-mode.node
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### 3. Apply the two manual patches to the vendored source
|
|
108
|
-
|
|
109
|
-
The patch file modifies the **compiled dist JS**, but we need to apply equivalent changes to the **TypeScript source**:
|
|
110
|
-
|
|
111
|
-
**Patch 1** — `components/editor.ts`: Move autocomplete rendering above the editor
|
|
112
|
-
- Find the `render()` method where autocomplete lines are appended
|
|
113
|
-
- Change `result.push(...acLines)` → `result.unshift(...acLines)` (or equivalent logic)
|
|
114
|
-
|
|
115
|
-
**Patch 2** — `tui.ts`: Add alternate screen buffer on start/stop
|
|
116
|
-
- In `start()`: add `this.terminal.write("\x1b[?1049h");` before `this.terminal.start(…)`
|
|
117
|
-
- In `stop()`: add `this.terminal.write("\x1b[?1049l");` before cursor/timer cleanup
|
|
118
|
-
|
|
119
|
-
### 4. Update import paths
|
|
120
|
-
|
|
121
|
-
**`src/tui/index.ts`** — change:
|
|
122
|
-
```ts
|
|
123
|
-
import type { EditorTheme, MarkdownTheme, OverlayHandle, SelectItem, SelectListTheme, SlashCommand } from '@earendil-works/pi-tui'
|
|
124
|
-
import { Box, CombinedAutocompleteProvider, Container, Editor, Loader, Markdown, ProcessTerminal, Text, TUI } from '@earendil-works/pi-tui'
|
|
125
|
-
```
|
|
126
|
-
to:
|
|
127
|
-
```ts
|
|
128
|
-
import type { EditorTheme, MarkdownTheme, OverlayHandle, SelectItem, SelectListTheme, SlashCommand } from '../pi-tui/index.js'
|
|
129
|
-
import { Box, CombinedAutocompleteProvider, Container, Editor, Loader, Markdown, ProcessTerminal, Text, TUI } from '../pi-tui/index.js'
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
**`src/tui/components.ts`** — change:
|
|
133
|
-
```ts
|
|
134
|
-
import type { Component, SelectItem, SelectListTheme } from '@earendil-works/pi-tui'
|
|
135
|
-
import { SelectList } from '@earendil-works/pi-tui'
|
|
136
|
-
```
|
|
137
|
-
to:
|
|
138
|
-
```ts
|
|
139
|
-
import type { Component, SelectItem, SelectListTheme } from '../pi-tui/index.js'
|
|
140
|
-
import { SelectList } from '../pi-tui/index.js'
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
### 5. Keep `marked` and `get-east-asian-width` as runtime dependencies
|
|
144
|
-
|
|
145
|
-
These are pi-tui's own runtime dependencies used by `Markdown` and `visibleWidth` respectively. They remain in `dependencies`.
|
|
146
|
-
|
|
147
|
-
### 6. Remove pi-tui-related npm setup
|
|
148
|
-
|
|
149
|
-
- `package.json`: Remove `"@earendil-works/pi-tui": "0.75.5"` from `dependencies`
|
|
150
|
-
- `package.json`: Remove `"patch-package": "^8.0.1"` from `devDependencies` (only existed for pi-tui patching)
|
|
151
|
-
- `package.json`: Change `"postinstall": "npx patch-package"` to `"postinstall": "husky"` (or remove entirely if not needed)
|
|
152
|
-
- Delete `patches/@earendil-works+pi-tui+0.75.5.patch`
|
|
153
|
-
- Delete `patches/` directory if empty
|
|
154
|
-
- Run `npm uninstall @earendil-works/pi-tui` and `npm uninstall patch-package` to update lockfile
|
|
155
|
-
|
|
156
|
-
### 7. Update `package-lock.json`
|
|
157
|
-
|
|
158
|
-
Run `npm install` to regenerate `package-lock.json` without pi-tui/patch-package.
|
|
159
|
-
|
|
160
|
-
### 8. Verify
|
|
161
|
-
|
|
162
|
-
- `npm run build` — tsc should compile successfully
|
|
163
|
-
- `npm run lint` — biome should pass
|
|
164
|
-
- `npm test` — vitest tests should pass
|
|
165
|
-
- Verify the TUI starts correctly with `npm run dev`
|
|
166
|
-
|
|
167
|
-
## Open questions
|
|
168
|
-
|
|
169
|
-
1. Should we use the exact version `0.75.5` source (may need to checkout a tag) or the latest `main` branch source? **Answer: main branch is fine unless it introduces breaking changes.**
|
|
170
|
-
2. The `native-modifiers.ts` file on GitHub may export functions used by `terminal.ts`. If unavailable, we can stub it or inline the native module loading logic.
|
|
171
|
-
3. If the GitHub raw URLs are flaky, alternative: download from npm tarball and reverse-engineer from dist JS (less ideal).
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# Plan: Optimize edit tool error handling for model
|
|
2
|
-
|
|
3
|
-
## Problem
|
|
4
|
-
The model calls the `edit` tool with errors very frequently. The most common failures:
|
|
5
|
-
1. `old_string not found` (whitespace issues, stale content, hallucinated content)
|
|
6
|
-
2. `old_string appears MULTIPLE times` (insufficient context)
|
|
7
|
-
3. File doesn't exist / already exists
|
|
8
|
-
4. Input format misuse (already mostly auto-corrected)
|
|
9
|
-
|
|
10
|
-
Error messages are returned to the model (via `session.ts:687` as `ERROR: ${result.error}`), but they lack actionable recovery guidance, and noise obscures the core issue.
|
|
11
|
-
|
|
12
|
-
## Changes
|
|
13
|
-
|
|
14
|
-
### 1. Use `FileReadTracker.checkModified()` to detect stale content
|
|
15
|
-
The `FileReadTracker` has a `checkModified()` method that is never called in production code. The edit tool should call it before attempting edits to detect:
|
|
16
|
-
- File was never read in this session → suggest `read` first
|
|
17
|
-
- File was externally modified since last read → suggest re-reading
|
|
18
|
-
|
|
19
|
-
This catches a major source of `old_string not found` errors — the model editing content it hasn't read, or editing stale content.
|
|
20
|
-
|
|
21
|
-
**Location**: `edit.ts`, in the `for (const [resolved, group] of fileGroups)` loop, before processing edits.
|
|
22
|
-
|
|
23
|
-
### 2. Include proximity hint in `firstError`
|
|
24
|
-
Currently the hint (near-line snippet / file content) is only in `results.push()` but NOT in `firstError`. This means the compact "First error:" line at the bottom of the batch error message lacks the most useful context.
|
|
25
|
-
|
|
26
|
-
**Fix**: Update lines ~339-342 to include hint in `firstError`.
|
|
27
|
-
|
|
28
|
-
### 3. Add actionable "Suggestion:" line to failure messages
|
|
29
|
-
When `old_string not found`, the error should tell the model what to do:
|
|
30
|
-
- "Suggestion: Read the file again with `read({ paths: [...] })` to get current content, then retry with exact matching text. Include 2-3 lines of surrounding context for uniqueness."
|
|
31
|
-
- "Suggestion: Whitespace-tolerant matching was attempted but no match found. The text content differs — not just whitespace."
|
|
32
|
-
|
|
33
|
-
When `old_string appears MULTIPLE times`:
|
|
34
|
-
- "Suggestion: Include more surrounding context lines (2-3 lines before and after) in old_string to make the match unique."
|
|
35
|
-
|
|
36
|
-
When `File not found`:
|
|
37
|
-
- "Suggestion: Check the file path or create the file first with old_string: '' (empty string)."
|
|
38
|
-
|
|
39
|
-
When `File already exists` (on create):
|
|
40
|
-
- "Suggestion: Use edit with old_string to replace existing content, or use a different file path."
|
|
41
|
-
|
|
42
|
-
### 4. Clean up batch failure output
|
|
43
|
-
When a batch fails, the current output includes ALL results (even successful edits that were rolled back). This is noisy.
|
|
44
|
-
|
|
45
|
-
**Fix**: Only show FAIL lines in the batch error, plus a brief summary like "N edit(s) before failure were rolled back."
|
|
46
|
-
|
|
47
|
-
### 5. Improve "multiple matches" error with location hints
|
|
48
|
-
When old_string matches multiple times, show the file content at each match location (similar to the "near line" hint for not-found).
|
|
49
|
-
|
|
50
|
-
### 6. Better `edits` format error
|
|
51
|
-
Tighten the "edits array required" error to be more compact.
|
|
52
|
-
|
|
53
|
-
## Files to modify
|
|
54
|
-
- `src/tools/edit.ts` — all the above changes
|
|
55
|
-
|
|
56
|
-
## Tests
|
|
57
|
-
- All existing tests in `src/tools/__tests__/edit.test.ts` must pass
|
|
58
|
-
- Some test assertions may need updating if error message content changes
|
|
59
|
-
- Specifically:
|
|
60
|
-
- `expect(r.error).toContain('rolled back')` — still valid since rolled-back summary stays
|
|
61
|
-
- `expect(r.error).toContain('hello')` — still valid since hint stays in error
|
|
62
|
-
- Other `toContain('not found')`, `toContain('MULTIPLE times')` — still valid
|